I am using winston
to log throughout my NextJS application. I want these logs to be collected in Azure Application Insights. Microsoft advises to do so using OpenTelemetry. My setup is as follows:
instrumentation.ts
:
import { env } from "~/env";
import { logs } from "@opentelemetry/api-logs";
import { LoggerProvider, SimpleLogRecordProcessor, ConsoleLogRecordExporter } from "@opentelemetry/sdk-logs";
import { BatchSpanProcessor, NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { Resource } from "@opentelemetry/resources";
import { registerOTel } from "@vercel/otel";
import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
export async function register() {
if (
process.env.NEXT_RUNTIME == "nodejs" &&
env.APPLICATIONINSIGHTS_CONNECTION_STRING != null
) {
console.log("Registering Application Insights");
const { AzureMonitorTraceExporter, AzureMonitorLogExporter } =
await import("@azure/monitor-opentelemetry-exporter");
const tracerProvider = new NodeTracerProvider({
resource: new Resource({
[ATTR_SERVICE_NAME]: env.APPLICATIONINSIGHTS_SERVICE_NAME,
}),
});
tracerProvider.register();
const traceExporter = new AzureMonitorTraceExporter({
connectionString: env.APPLICATIONINSIGHTS_CONNECTION_STRING,
});
tracerProvider.addSpanProcessor(
new BatchSpanProcessor(traceExporter, {
maxQueueSize: 10,
scheduledDelayMillis: 5000,
}),
);
const logExporter = new AzureMonitorLogExporter({
connectionString: env.APPLICATIONINSIGHTS_CONNECTION_STRING,
});
// Set up OpenTelemetry logging provider
const loggerProvider = new LoggerProvider();
// Add a log processor that will export the logs
loggerProvider.addLogRecordProcessor(
new SimpleLogRecordProcessor(logExporter),
);
// Add a processor to export log record
loggerProvider.addLogRecordProcessor(
new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()),
);
logs.setGlobalLoggerProvider(loggerProvider);
const instrumentation = new WinstonInstrumentation({
disableLogSending: false,
enabled: true,
disableLogCorrelation: false,
});
registerOTel({
serviceName: env.APPLICATIONINSIGHTS_SERVICE_NAME,
traceExporter,
instrumentations: [instrumentation],
});
registerInstrumentations({
instrumentations: [instrumentation],
});
} else {
}
}
My winston setup is as follows:
import { env } from "~/env";
import winston, { type Logger } from "winston";
import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
// Create a logger with custom format
const logger: Logger = winston.createLogger({
transports: [
new winston.transports.Console({
level: env.NODE_ENV == "test" ? "error" : "debug",
format: winston.format.simple(),
}),
new OpenTelemetryTransportV3(),
],
});
export default logger;
The application does show that the instrumentation code is being executed, and I do get request time information in Application Insights, but the traces
table in Application Insights remains empty. What am I doing wrong?
I found out what the correct configuration is for using NextJS with Winston and Azure Application Insights through OpenTelemetry. Here is my instrumentation.ts
import { WinstonInstrumentation } from "@opentelemetry/instrumentation-winston";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { env } from "~/env";
import { registerOTel } from "@vercel/otel";
// Prevent registering the SDK multiple times
let started = false;
export async function register() {
if (
!started &&
process.env.NEXT_RUNTIME == "nodejs" &&
env.APPLICATIONINSIGHTS_CONNECTION_STRING != null
) {
started = true;
console.log("Registering Azure Application Insights");
// Importing the Azure Monitor cannot be done when in Edge environment
const { AzureMonitorLogExporter, AzureMonitorTraceExporter } =
await import("@azure/monitor-opentelemetry-exporter");
// Configure Azure Exporters
const params = {
connectionString: env.APPLICATIONINSIGHTS_CONNECTION_STRING,
};
const traceExporter = new AzureMonitorTraceExporter(params);
const logExporter = new AzureMonitorLogExporter(params);
// Register the OpenTelemetry provider
registerOTel({
serviceName: env.APPLICATIONINSIGHTS_SERVICE_NAME,
traceExporter,
logRecordProcessor: new BatchLogRecordProcessor(logExporter, {
maxExportBatchSize: 100,
}),
instrumentations: [
new WinstonInstrumentation({
disableLogSending: false,
disableLogCorrelation: false,
enabled: true,
}),
],
});
}
}
And logger.ts
which is used to log by the rest of the code:
import { env } from "~/env";
// For if not being run through 'next start' or 'next dev'
import { register } from "~/instrumentation";
await register();
import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
import winston, { type Logger } from "winston";
import { context, trace } from "@opentelemetry/api";
// Create a logger with custom format
const logger: Logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.printf(({ level, message, timestamp }) => {
// Retrieve the current span from OpenTelemetry context
const span = trace.getSpan(context.active());
const traceId = span ? span.spanContext().traceId : "unknown";
return `${timestamp} [traceId=${traceId}] ${level}: ${message}`;
}),
),
transports: [
new winston.transports.Console({
level: env.NODE_ENV == "test" ? "error" : "debug",
format: winston.format.simple(),
}),
new OpenTelemetryTransportV3({}),
],
});
export default logger;