N
Nuxt2mo ago
dmarr

How do I add a custom log reporter to nuxt?

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));
}
})
}]
})
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
10 Replies
kapa.ai
kapa.ai2mo ago
Hi @dmarr. 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!')
}
})
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 })
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: ----------------------
kapa.ai
kapa.ai2mo ago
<@139883350096740354> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
dmarr
dmarrOP2mo ago
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;
};
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);
})
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();
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);
});
})
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 @danielroe
dmarr
dmarrOP2mo ago
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
Logs Data Model
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 ...
danielroe
danielroe2mo ago
that'd be a great module
dmarr
dmarrOP2mo ago
I’m getting resistance from my devops team about using consola. They think it’s not viable for outputting structured logs.
danielroe
danielroe2mo ago
what kind of reasons do they have?
dmarr
dmarrOP2mo ago
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.
dmarr
dmarrOP2mo ago
More information: - what is structured log format https://opentelemetry.io/docs/specs/otel/logs/data-model/#log-and-event-record-definition - really nice page with short explanation what are benefits of log4js-node https://betterstack.com/community/guides/logging/best-nodejs-logging-libraries/ (#3)
Logging in Node.js: A Comparison of the Top 8 Libraries | Better St...
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
Logs Data Model
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 ...
dmarr
dmarrOP2mo ago
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? 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' },
});
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' },
});
much simpler using winston
import winston from 'winston';
import {OpenTelemetryTransportV3} from '@opentelemetry/winston-transport';

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'test-service' },
transports: [
new OpenTelemetryTransportV3(),
],
});

logger.info('this is a log record body');
import winston from 'winston';
import {OpenTelemetryTransportV3} from '@opentelemetry/winston-transport';

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'test-service' },
transports: [
new OpenTelemetryTransportV3(),
],
});

logger.info('this is a log record body');

Did you find this page helpful?