You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS Lambda自定义S3日志器执行异常排查求助

问题排查:AWS Lambda搭配自定义日志器后续调用返回null的问题

我通过Serverless Framework部署了一个AWS Lambda处理器,搭配自定义日志器,每次执行会将日志写入唯一的AWS S3对象。首次通过HTTP客户端调用函数端点时一切正常,但后续调用均返回null,仅在间隔较长时间后才会恢复正常输出。移除该日志器后则无此问题,现附上Logger类及处理器代码,请求协助排查日志器的问题:

Logger类代码

const AWS = require('aws-sdk');
var os = require("os");
var stream = require("stream");
AWS.config.update({accessKeyId: process.env.ACCESS_KEY, secretAccessKey: process.env.SECRET_ACCESS_KEY, region: 'ap-south-1'});
const s3 = new AWS.S3();
const BUCKET = process.env.S3_BUCKET;
var s3Stream = require('s3-upload-stream')(new AWS.S3())

class Logger{
    constructor(id){
        var date = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'});
        var currentDate = new Date(date);
        var month = currentDate.getMonth() + 1;
        var date = currentDate.getDate()
        this.file = currentDate.getFullYear().toString() + ((month.toString().length < 2)? ('0' + month) : ('' + month)) + ((date.toString().length < 2)? ('0' + date) : ('' + date)) + "-" + id;
        console.log("FILE: "+this.file)
        this.bucket = BUCKET;
        this.prefix = "logs/";
        this.stream = new stream.PassThrough();
        this.id = id;
        this.logContent = "";
    }

    streamLog(type,component,data){
        var date = new Date();
        var millis = date.getTime();
        this.stream.write(millis.toString() + " - " + this.id + " - [" + type + "]" + " - (" + component + "): " + data + os.EOL);
        this.logContent = this.logContent + millis.toString() + " - " + this.id + " - [" + type + "]" + " - (" + component + "): " + data + os.EOL;
    }

    uploadLogToS3(){
        this.stream.end();
        var upload = s3Stream.upload({
            "Bucket": BUCKET,
            "Key": this.prefix + this.file + ".log"
        });
        return new Promise((resolve,reject) => {
            console.log("ENTERED PROMISE");
            upload.on('error',function(error){
                console.log("ERROR STREAMING LOG TO S3: " + error);
                reject(0);
            })
            upload.on('uploaded',function(details){
                console.log("log successfully streamed to S3");
                resolve(1);
            })
            upload.on('part',function(details){
                console.log("UPLOADING LOG TO S3");
            })
            this.stream.pipe(upload);
        })
    }
    async finish(){
        try {
            console.log("ENTERED LOGGER FINISH");
            await this.uploadLogToS3();
        } catch (error) {
            console.log("WRITING THE LOG STREAM TO CONSOLE");
            console.log(this.logContent);
        }
    }
}

处理器代码

module.exports.server = async function(event,context){
  console.log("ENTERED HANDLER");
  const MODULE_DESCRIPTION = "CONTROLLER GET BIRTHDAYS METHOD";
  var birthDays;
  var date = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'});
  var currentDate = new Date(date);
  try {
    console.log("ENTERED TRY OF CONTROLLER GET BIRTHDAYS");
    [birthDays] = await Promise.all([API_LIB.getBirthDays(parseInt(currentDate.getDate()), parseInt(currentDate.getMonth() + 1),LOGGER)]);
    if (birthDays.length == 1){
      if (birthDays[0].hasOwnProperty("error")){
        LOGGER.streamLog("ERROR",MODULE_DESCRIPTION, birthDays[0].error || "ERROR with API call");
        await LOGGER.finish();
        return {status: 501,body: {error: 1,message: birthDays[0].error || "Internal server error"}}
      } else{
        LOGGER.streamLog("INFO",MODULE_DESCRIPTION,birthDays.toString());
        await LOGGER.finish();
        return {status: 200,body: birthDays}
      }
    } else{
      LOGGER.streamLog("INFO",MODULE_DESCRIPTION,birthDays.toString());
      await LOGGER.finish();
      return {status: 200,body: birthDays}
    }
  } catch (error) {
    LOGGER.streamLog("ERROR",MODULE_DESCRIPTION,error || "Internal server error");
    await LOGGER.finish();
    return {status: 501,body: {error: 1,message: error || "Internal server error"}}
  }
}

我在整个代码中使用Logger.streamLog()写入日志,恳请协助排查该日志器的问题,谢谢!


问题根源与修复方案

核心问题

  1. Lambda环境复用导致流资源耗尽:Lambda冷启动后会保持执行环境(暖状态),如果LOGGER是全局实例,第一次调用finish()时执行了this.stream.end()关闭了PassThrough流,后续调用再执行streamLog()时,向已关闭的流写入数据会静默失败,导致后续逻辑卡住,最终函数返回null。
  2. 上传流绑定逻辑错误:uploadLogToS3()中先调用this.stream.end()再绑定上传流,会导致流数据无法正常传递到S3上传流,Promise可能一直处于pending状态,Lambda超时无响应。

修复步骤

  1. 每次调用创建独立Logger实例
    不要使用全局LOGGER,在处理器内部每次调用时生成新实例,确保资源独立:

    module.exports.server = async function(event,context){
      console.log("ENTERED HANDLER");
      const MODULE_DESCRIPTION = "CONTROLLER GET BIRTHDAYS METHOD";
      // 用请求ID作为唯一标识,每次调用生成新Logger
      const LOGGER = new Logger(context.awsRequestId);
      var birthDays;
      var date = new Date().toLocaleString('en-US', {timeZone: 'Asia/Kolkata'});
      var currentDate = new Date(date);
      // 后续逻辑保持不变...
    }
    
  2. 调整流结束时机
    在uploadLogToS3()中,先绑定上传流再结束PassThrough流,确保数据完整传递:

    uploadLogToS3(){
        var upload = s3Stream.upload({
            "Bucket": BUCKET,
            "Key": this.prefix + this.file + ".log"
        });
        return new Promise((resolve,reject) => {
            console.log("ENTERED PROMISE");
            upload.on('error',function(error){
                console.log("ERROR STREAMING LOG TO S3: " + error);
                reject(0);
            })
            upload.on('uploaded',function(details){
                console.log("log successfully streamed to S3");
                resolve(1);
            })
            upload.on('part',function(details){
                console.log("UPLOADING LOG TO S3");
            })
            this.stream.pipe(upload);
            // 绑定流之后再结束
            this.stream.end();
        })
    }
    
  3. 增加流状态校验(可选)
    如果需要复用Logger实例,在streamLog()中检查流是否可写,不可写则重新创建:

    streamLog(type,component,data){
        if (!this.stream.writable){
            this.stream = new stream.PassThrough();
        }
        var date = new Date();
        var millis = date.getTime();
        this.stream.write(millis.toString() + " - " + this.id + " - [" + type + "]" + " - (" + component + "): " + data + os.EOL);
        this.logContent = this.logContent + millis.toString() + " - " + this.id + " - [" + type + "]" + " - (" + component + "): " + data + os.EOL;
    }
    

内容的提问来源于stack exchange,提问作者kumarp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 19:37:09