I have a Node.js app which runs in a Docker container in Google Kubernetes Engine. I have set up a logging class which uses Winston
(v3.2.1) with two transports defined; one to log to the console and one to log to Stackdriver (using @google-cloud/logging-winston
(v3.0.0)).
With both transports defined, all is good and I can see the logs in Stackdriver. The console logs go to projects/[project-id]/logs/stdout
and the Stackdriver logs go to projects/[project-id]/logs/winston_log
.
However, I want to configure the logger so that when debugging locally, logs are only sent to the console and when running in GKE, logs are only sent to Stackdriver, as follows:
// Configure console logger
private readonly consoleLogger = new winston.transports.Console({
format: combine(
colorize(),
simple(),
printf(context => {
return `[${context.level}]${context.message}`;
}),
),
});
// Configure Stackdriver logger
private readonly stackdriverLogger = new LoggingWinston({
serviceContext: {
service: this.serviceName,
},
});
// Create Winston logger
private readonly logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: json(),
defaultMeta: {
service: this.serviceName,
},
// This line does not work:
transports: [process.env.NODE_ENV === 'development' ? this.consoleLogger : this.stackdriverLogger],
});
The aim here is that if the NODE_ENV
is development
, use the console logger, otherwise use the Stackdriver logger. However, when I deploy this to GKE, I see the following errors in the Stackdriver console log (and nothing in projects/[project-id]/logs/winston_log
):
[winston] Attempt to write logs with no transports { // Logged message }
When I run this code locally on my dev machine with NODE_ENV=development
, I see the logs in my local console and if I set NODE_ENV=production
I see the logs in Stackdriver.
If I remove the ternary operator and have both transports defined and deploy to GKE, I do not see the above error and logging works correctly to both transports:
transports: [this.consoleLogger, this.stackdriverLogger],
Can anyone help me to configure this correctly?
Added the full Logger.ts
file for context:
import { LoggerService } from '@nestjs/common';
import * as winston from 'winston';
const { colorize, combine, json, printf, simple } = winston.format;
import { LoggingWinston } from '@google-cloud/logging-winston';
import cls from 'cls-hooked';
import { ConfigManager } from '../config';
import { TraceId } from '../middleware/traceId/constants';
export class Logger implements LoggerService {
private readonly serviceName: string = process.env.SERVICE_NAME;
private readonly consoleLogger = new winston.transports.Console({
format: combine(
colorise(),
simple(),
printf(context => {
return `[${context.level}]${context.message}`;
}),
),
});
private stackdriverLogger = new LoggingWinston({
serviceContext: {
service: this.serviceName,
},
});
private readonly logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: json(),
defaultMeta: {
service: this.serviceName,
},
transports: [process.env.NODE_ENV === 'development' ? this.consoleLogger : this.stackdriverLogger]
});
constructor(private readonly context?: string) {}
public verbose(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.verbose(log.message, log.metadata);
}
public debug(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.debug(log.message, log.metadata);
}
public log(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.info(log.message, log.metadata);
}
public warn(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.warn(log.message, log.metadata);
}
public error(message: string, trace?: string, context?: string) {
const log = this.buildLog(message, context, trace);
this.logger.error(log.message, log.metadata);
}
private buildLog(message: string, context?: string, trace?: string) {
const ctx = context || this.context;
const traceId = this.getTraceId();
return {
message: `[${ctx}] ${message}`,
metadata: {
traceId,
source: ctx,
stackTrace: trace,
},
};
}
private getTraceId(): string {
const clsNamespace = cls.getNamespace(TraceId.Namespace);
if (!clsNamespace) {
return null;
}
return clsNamespace.get(TraceId.Key);
}
}
So it turns out the problem was the @google-cloud/logging-winston
package had a bug in it which caused it to throw this error:
UnhandledPromiseRejectionWarning: FetchError: request to http://169.254.169.254/computeMetadata/v1/instance failed, reason: connect ECONNREFUSED 169.254.169.254:80
This has now been fixed in version 3.0.6 - see https://github.com/googleapis/nodejs-logging-winston/issues/389#issuecomment-593727968.
After updating @google-cloud/logging-winston
, the Winston logging is working correctly for me in Stackdriver.