#How do I add a custom log reporter to nuxt?

18 messages · Page 1 of 1 (latest)

worn venture
#

I have tried using a module to call the addReporter function on the logger instance like so:

import { useLogger } from "nuxt/kit";
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: [() => {
    const logger = useLogger();
    logger.addReporter({
      log(logObj) {
        console.log(JSON.stringify(logObj));
      }
    })
  }]
})

But when running a build, I see a maximum call stack error

digital basaltBOT
#

Hi @worn venture. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the information provided in the knowledge sources, I don't have enough specific details about adding a custom log reporter to Nuxt in the way you've described. However, I can provide some relevant information about logging in Nuxt and suggest an alternative approach.

Nuxt provides a logger instance that you can use to log messages with extra features. Instead of trying to add a custom reporter, you might want to consider using the useLogger function provided by Nuxt Kit. Here's how you can use it:

import { defineNuxtModule, useLogger } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const logger = useLogger('my-module')

    logger.info('Hello from my module!')
  }
})

This approach allows you to create a logger instance with a custom tag and use it to log messages Nuxt Kit Logging (https://nuxt.com/docs/api/kit/logging).

If you need more control over the logging behavior, you can also pass options to the useLogger function:

const logger = useLogger('my-module', { level: options.quiet ? 0 : 3 })

This allows you to customize the logging level based on your needs Nuxt Kit Logging (https://nuxt.com/docs/api/kit/logging).

Regarding the maximum call stack error you're encountering, it's difficult to diagnose without more context. However, it's worth noting that there have been issues related to logging and infinite loops in the past GitHub PR #27309 (https://github.com/nuxt/nuxt/pull/27309).

If you continue to face issues, you might want to consider:

#
  1. Checking if your logging logic is causing an infinite loop.
  2. Ensuring you're using the latest version of Nuxt, as some logging-related bugs have been fixed in recent versions.
  3. If the problem persists, you may want to report it as a potential bug, following the guidelines for reporting bugs in Nuxt Reporting Bugs (https://nuxt.com/docs/community/reporting-bugs).

Remember to create a minimal reproduction of the issue if you decide to report it, as this will help the Nuxt team to investigate and potentially fix the problem more quickly.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@worn venture Kapa.ai is still learning and improving, please let me know how I did by reacting below.

worn venture
#

I was able to accomplish this in a few steps.

  1. Create a shared util in ~/shared/utils/logging.ts.
  2. Create a plugin that exposes the logger to be used in app land.
  3. Create a server util to use the logger directly in nitro land.

shared util:

import { createConsola } from 'consola';

export const createLogger = () => {
  const logger = createConsola({ fancy: true, level: 5, formatOptions: { colors: true, date: true } });
  if (!import.meta.dev) {
    logger.setReporters([
      {
        log({ type, tag, args, date }) {
          const newLogObj = {
            level: type,
            tag,
            args,
            date,
          };
          console.log(newLogObj)
        },
      },
    ]);
  }
  return logger;
};

plugin:

import { createLogger } from "~/shared/utils/logging";

export default defineNuxtPlugin(nuxtApp => {
  const logger = createLogger();
  nuxtApp.provide('logger', logger);

  const customFetch = $fetch.create({
    onRequest: ({ request }) => {
      logger.info(import.meta.server ? "server" : "client", "fetch request", request);
    },
  })
  nuxtApp.provide('cfetch', customFetch);
})

server/utils/logger.ts

import { createLogger } from '~/shared/utils/logging';

export const logger = createLogger();

(ssr usage)
server/plugins/logger.ts

export default defineNitroPlugin(nitroApp => {
  nitroApp.hooks.hook('beforeResponse', (event) => {
    const headers = getHeaders(event);
    logger.log("on before response", event.path, { headers });
  });
  nitroApp.hooks.hook('afterResponse', (event) => {
    const headers = getHeaders(event);
    logger.info("on after response", event.path, { headers });
  });
  nitroApp.hooks.hook('request', (event) => {
    logger.info("on request", event.path);
  });
})
#

This has the benefit of using normal consola when running the dev server and structured JSON when using a prod build.

#

cc @slate notch

#

next steps might be to package this all up in a module. Add options, and maybe a reporter formatted for OTEL output. (https://opentelemetry.io/docs/specs/otel/logs/data-model/#log-and-event-record-definition)

OpenTelemetry

Status: Stable
This is a data model and semantic conventions that allow to represent logs from various sources: application log files, machine generated events, system logs, etc. Existing log formats can be unambiguously mapped to this data model. Reverse mapping from this data model is also possible to the extent that the target log format has ...

slate notch
#

that'd be a great module

worn venture
#

I’m getting resistance from my devops team about using consola. They think it’s not viable for outputting structured logs.

slate notch
#

what kind of reasons do they have?

worn venture
#

Hello everyone,

I researched your current issue with logs and OpenTelemetry, and here is my summary: you should migrate from using console logging (via console.xxx) to a framework that supports structured formats.

Initially, I considered Winston, but I am leaning toward log4js-node since it has appender configurations similar to log4java.

What does this mean? You can have human-readable logs displayed in the console (as you prefer) while simultaneously configuring OpenTelemetry to export logs in a JSON-structured format. This will ensure proper export to Datadog, including all required attributes such as spans, etc.

#

More information:

This article compares the top 8 Node.js logging libraries, discussing their features, pros and cons, and providing recommendations for which library is right for you

OpenTelemetry

Status: Stable
This is a data model and semantic conventions that allow to represent logs from various sources: application log files, machine generated events, system logs, etc. Existing log formats can be unambiguously mapped to this data model. Reverse mapping from this data model is also possible to the extent that the target log format has ...

#

I will be digging into this more

#

otel can ingest stdout which is where console/consola writes to, and logging objects should be all that’s needed

#

Right?

worn venture
#

Turns out, otel doesn't ingest stdout. There are many types of exporters using their sdk, one being console. It doesn't actually go to the collector, however. I was finally able to figure out how to log to the collector using their official sdk.
If anyone is curious..

import logsAPI from '@opentelemetry/api-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import {
  LoggerProvider,
  SimpleLogRecordProcessor,
  ConsoleLogRecordExporter,
  // BatchLogRecordProcessor,
} from '@opentelemetry/sdk-logs';

// To start a logger, you first need to initialize the Logger provider.
const loggerProvider = new LoggerProvider({
  resource: {
    attributes: {
      'service.name': 'test-service',
    }
  }
});
// Add a processor to export log record
loggerProvider.addLogRecordProcessor(
  new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())
);

const loggerExporter = new OTLPLogExporter();
loggerProvider.addLogRecordProcessor(
  new SimpleLogRecordProcessor(loggerExporter)
);

//  To create a log record, you first need to get a Logger instance
const logger = loggerProvider.getLogger('default');

// You can also use global singleton
// logsAPI.logs.setGlobalLoggerProvider(loggerProvider);

// emit a log record
logger.emit({
  severityNumber: logsAPI.SeverityNumber.INFO,
  severityText: 'INFO',
  body: 'this is a log record body',
  attributes: { 'log.type': 'LogRecord' },
});