【问题标题】:AWS Lambda using Winston logging loses Request ID使用 Winston 日志记录的 AWS Lambda 丢失请求 ID
【发布时间】:2019-10-26 23:34:22
【问题描述】:

当使用 console.log 将日志行添加到 AWS CloudWatch 时,Lambda 请求 ID 将作为 described in the docs 添加到每一行中

基于上述文档的简化示例

exports.handler = async function(event, context) {
  console.log("Hello");
  return context.logStreamName
};

会产生诸如

之类的输出

START RequestId:c793869b-ee49-115b-a5b6-4fd21e8dedac 版本:$LATEST

2019-06-07T19:11:20.562Z c793869b-ee49-115b-a5b6-4fd21e8dedac 信息您好

END RequestId: c793869b-ee49-115b-a5b6-4fd21e8dedac

报告请求 ID:c793869b-ee49-115b-a5b6-4fd21e8dedac 持续时间:170.19 毫秒计费持续时间:200 毫秒内存大小:128 MB 最大已用内存:73 MB

此处有关此问题的相关详细信息是请求 ID,c793869b-ee49-115b-a5b6-4fd21e8dedac,它添加在带有“Hello”的行的时间戳之后。

AWS 文档说明

要从函数代码输出日志,您可以使用控制台对象上的方法,或任何写入 stdout 或 stderr 的日志库。

Node.js 运行时记录每次调用的 START、END 和 REPORT 行,并为函数记录的每个条目添加时间戳、请求 ID 和日志级别。

当使用 Winston 作为记录器时,请求 ID 会丢失。可能是使用格式化程序或传输器发出的。记录器的创建方式类似于

const logger = createLogger({
    level: 'debug',
    format: combine(
      timestamp(),
      printf(
        ({ timestamp, level, message }) => `${timestamp} ${level}: ${message}`
      )
    ),
    transports: [new transports.Console()]
  });

我还尝试了simple() 格式化程序而不是printf(),但这对请求 ID 是否存在没有影响。同样完全删除格式仍然会打印纯文本,即没有时间戳或请求 ID。

我还检查了 Winston 控制台传输的源代码,它使用 console._stdout.write(如果存在)或 console.log 进行编写,这就是 AWS 文档所说的支持。

有没有办法配置 Winston 以将 AWS Lambda 请求 ID 作为消息的一部分?

附:我知道 AWS CloudWatch 有单独的 Winston 传输,但它们需要其他设置功能,我希望尽可能避免。而且由于请求 ID 很容易获得,因此它们看起来有点过头了。

附言请求 ID 也可以从 Lambda 上下文和用它初始化的自定义记录器对象中获取,但我也想避免这种情况,几乎出于相同的原因:为应该随时可用的东西做额外的工作。

【问题讨论】:

    标签: amazon-web-services aws-lambda winston amazon-cloudwatchlogs


    【解决方案1】:

    问题在于 console._stdout.write() / process._stdout.write() 的使用,Winston 内置控制台传输在存在时使用。

    由于某些原因,写入标准输出的行按原样转到 CloudWatch,并且时间戳/请求 ID 不会像 console.log() 调用那样添加到日志行中。

    有一个discussion on Github about making this a constructor option 可以在创建传输时选择,但由于与特定 IDE 以及它们如何处理标准输出日志有关的问题而被关闭。 AWS Lambdas 的问题仅在讨论中作为旁注提及。

    我的解决方案是为 Winston 制作一个自定义传输,它始终使用 console.log() 来编写消息并留下时间戳和请求 ID 以由 AWS Lambda 节点运行时填写。

    5/2020 补充: 以下是我的解决方案的示例。不幸的是,我不记得这个实现的很多细节,但我几乎看过Winston sources in Github,并采取了最低限度的实现并强制使用console.log

    'use strict';
    
    const TransportStream = require('winston-transport');
    
    class SimpleConsole extends TransportStream {
      constructor(options = {}) {
        super(options);
        this.name = options.name || 'simple-console';
      }
    
      log(info, callback) {
        setImmediate(() => this.emit('logged', info));
    
        const MESSAGE = Symbol.for('message');
        console.log(info[MESSAGE]);
    
        if (callback) {
          callback();
        }
      }
    };
    
    const logger = createLogger({
      level: 'debug',
      format: combine(
        printf(({ level, message }) => `${level.toUpperCase()}: ${message}`)
      ),
      transports: [new SimpleConsole()]
    });
    
    const debug = (...args) => logger.debug(args);
    // ... And similar definition to other logging levels, info, warn, error etc
    
    module.exports = {
      debug
      // Also export other logging levels..
    };
    

    另一种选择

    正如cmets中@sanrodari所指出的,同样可以通过直接覆盖内置控制台传输中的日志方法并强制使用console.log来实现。

    const logger = winston.createLogger({
      transports: [
        new winston.transports.Console({
          log(info, callback) {
            setImmediate(() => this.emit('logged', info));
    
            if (this.stderrLevels[info[LEVEL]]) {
              console.error(info[MESSAGE]);
    
              if (callback) {
                callback();
              }
              return;
            }
    
            console.log(info[MESSAGE]);
    
            if (callback) {
              callback();
            }
          }
        })
      ]
    });
    

    full example for more details

    【讨论】:

    • 这仍然是处理问题的首选方式吗?
    • 如果没有这种解决方法,我还没有测试过 AWS 日志记录。据我了解,温斯顿并没有像他们认为的那样发生变化,问题不在日志库中。 Github 上的一些 cmets 表示 console.log 比 process._stdout 等慢。 CloudWatch 执行此异步 AFAIK,因此可能无需担心速度。 TL;博士;如果 AWS 发生了变化并更好地支持标准输出方法,它可能会起作用。否则包装自定义传输或检查问题中提到的库。如果你让它在没有黑客攻击的情况下工作,请回帖并让其他人也知道。
    • 我的猜测是 lambda 运行时重载了控制台对象以添加此信息...
    • @kaskelotti 你能分享你的自定义传输吗?
    • @Jason 我更新了答案并添加了自定义传输及其使用方式。
    【解决方案2】:

    根据AWS docs

    要从函数代码输出日志,您可以使用控制台对象上的方法,或任何写入 stdout 或 stderr 的日志库。

    我在 lambda 中使用以下 Winston 设置进行了快速测试:

    const path = require('path');
    const { createLogger, format, transports } = require('winston');
    const { combine, errors, timestamp } = format;
    
    const baseFormat = combine(
      timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
      errors({ stack: true }),
      format((info) => {
        info.level = info.level.toUpperCase();
        return info;
      })(),
    );
    
    const splunkFormat = combine(
      baseFormat,
      format.json(),
    );
    
    const prettyFormat = combine(
      baseFormat,
      format.prettyPrint(),
    );
    
    const createCustomLogger = (moduleName) => createLogger({
      level: process.env.LOG_LEVEL,
      format: process.env.PRETTY_LOGS ? prettyFormat : splunkFormat,
      defaultMeta: { module: path.basename(moduleName) },
      transports: [
        new transports.Console(),
      ],
    });
    
    module.exports = createCustomLogger;
    

    在 CloudWatch 中,我没有获取我的请求 ID。我从自己的日志中获取时间戳,所以我不太关心它。没有得到请求 ID 让我很困扰

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-20
      • 2019-06-30
      • 2016-10-08
      • 1970-01-01
      • 2015-09-14
      • 2019-09-29
      相关资源
      最近更新 更多