从 GKE 登录到 Stackdriver 时如何有选择地设置 Winston 传输

How to optionally set Winston transports when logging to Stackdriver from GKE

我有一个 Node.js 应用程序,它 运行 在 Google Kubernetes Engine 的 Docker 容器中。我已经设置了一个日志记录 class,它使用 Winston (v3.2.1) 并定义了两个传输;一个登录到控制台,一个登录到 Stackdriver(使用 @google-cloud/logging-winston (v3.0.0))。

定义了两种传输方式后,一切正常,我可以在 Stackdriver 中看到日志。控制台日志转到 projects/[project-id]/logs/stdout,Stackdriver 日志转到 projects/[project-id]/logs/winston_log

但是,我想配置logger,使得在本地调试时,日志只发送到控制台,而在GKE中运行ning时,日志只发送到Stackdriver,如下:

  // 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],
  });

这里的目的是如果 NODE_ENVdevelopment,则使用控制台记录器,否则使用 Stackdriver 记录器。但是,当我将其部署到 GKE 时,我在 Stackdriver 控制台日志中看到以下错误(projects/[project-id]/logs/winston_log 中没有任何错误):

[winston] Attempt to write logs with no transports { // Logged message }

当我使用 NODE_ENV=development 在我的开发机器上本地 运行 此代码时,我会在本地控制台中看到日志,如果我设置 NODE_ENV=production 我会在 Stackdriver 中看到日志。

如果我删除三元运算符并定义两个传输并部署到 GKE,我不会看到上述错误并且日志记录对两个传输都能正常工作:

transports: [this.consoleLogger, this.stackdriverLogger],

谁能帮我正确配置一下?

编辑

为上下文添加了完整的 Logger.ts 文件:

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);
  }
}

原来问题出在 @google-cloud/logging-winston 包中有一个错误导致它抛出这个错误:

UnhandledPromiseRejectionWarning: FetchError: request to http://169.254.169.254/computeMetadata/v1/instance failed, reason: connect ECONNREFUSED 169.254.169.254:80

此问题现已在版本 3.0.6 中得到修复 - 请参阅 https://github.com/googleapis/nodejs-logging-winston/issues/389#issuecomment-593727968

更新 @google-cloud/logging-winston 后,Winston 日志记录在 Stackdriver 中为我正常工作。