通过Serilog UDP接收器记录AWS Lambda和logstash静默失败

时间:2019-07-26 14:08:23

标签: aws-lambda logstash serilog

我们有一个.NET Core 2.1 AWS Lambda,我正尝试将其连接到我们现有的日志记录系统中。

我正在尝试使用UDP接收器通过Serilog登录到我们的logstash实例,以将其吸收到我们的ElasticSearch日志记录数据库中。通过控制台在本地运行,可以很好地记录到控制台本身,也可以通过UDP记录到Elastic中。但是,当它作为Lambda运行时,它仅登录到控制台(即CloudWatch),并且不会输出任何表明有问题的信息。可能是因为UDP是无状态的吗?

NuGet软件包和版本:

  • Serilog 2.7.1
  • Serilog.Sinks.Udp 5.0.1

这是我们正在使用的日志记录代码:

        public static void Configure(string udpHost, int udpPort, string environment)
        {
            var udpFormatter = new JsonFormatter(renderMessage: true);

            var loggerConfig = new LoggerConfiguration()
                .Enrich.FromLogContext()
                .MinimumLevel.Information()
                .Enrich.WithProperty("applicationName", Assembly.GetExecutingAssembly().GetName().Name)
                .Enrich.WithProperty("applicationVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString())
                .Enrich.WithProperty("tags", environment);

                loggerConfig
                    .WriteTo.Console(outputTemplate: "[{Level:u}]: {Message}{N---ewLine}{Exception}")
                    .WriteTo.Udp(udpHost, udpPort, udpFormatter);

              var logger = loggerConfig.CreateLogger();
              Serilog.Log.Logger = logger;
              Serilog.Debugging.SelfLog.Enable(Console.Error);
        }
          // this is output in the console from the lambda, but doesn't appear in the Database from the lambda
          // when run locally, appears in both
          Serilog.Log.Logger.Information("Hello from Serilog!");
          ...
          // at end of lambda
          Serilog.Log.CloseAndFlush();

这是我们在logstash上的UDP输入:

     udp {                                                                                                                                                                                                                                    
         port => 5000
         tags => [ 'systest', 'serilog-nested' ]                                                                                                                                                                                              
         codec => json                                                                                                                                                                                                                        
     }                                                                                                                                                                                                                                        

有人知道我将如何解决这个问题?甚至只是看看有什么特别的错误,以便我开始寻找解决方案。

到目前为止,尝试过的事情包括:

  • 从lambda ping logtash-不可能,lambda没有ICMP
  • 各种尝试使UDP接收器输出错误的方法,如上所述,对此进行了各种尝试。即使输入完全伪造的地址也不会产生错误
  • 将lambda添加到我知道可以从中进行记录的VPC
  • 在lambda末尾睡觉。这样日志就可以在lambda退出之前有时间通过​​
  • 检查logstash日志以查看是否看起来很奇怪。并非如此。而且,当地人的情况还算不错,这让我觉得不是那样。
  • 许多诅咒和咒骂

2 个答案:

答案 0 :(得分:0)

根据我上面的评论,您可以创建日志订阅并像这样流式传输到ES,我知道这是NodeJS,因此答案不是很正确,但是您可以从这里找到答案:< / p>

/* eslint-disable */
// Eslint disabled as this is adapted AWS code.

const zlib = require('zlib')
const { Client } = require('@elastic/elasticsearch')
const elasticsearch = new Client({ ES_CLUSTER_DETAILS })
/**
 * This is an example function to stream CloudWatch logs to ElasticSearch.
 * @param event
 * @param context
 * @param callback
 */
export default (event, context, callback) => {
    context.callbackWaitsForEmptyEventLoop = true

    const payload = new Buffer(event.awslogs.data, 'base64')

    zlib.gunzip(payload, (err, result) => {

        if (err) {
            return callback(null, err)
        }

        const logObject = JSON.parse(result.toString('utf8'))

        const elasticsearchBulkData = transform(logObject)

        const params = { body: [] }
        params.body.push(elasticsearchBulkData)

        esClient.bulk(params, (err, resp) => {

            if (err) {
        callback(null, 'success')
        return
    }

        })

        callback(null, 'success')
    })
}

function transform(payload) {
    if (payload.messageType === 'CONTROL_MESSAGE') {
        return null
    }

    let bulkRequestBody = ''

    payload.logEvents.forEach((logEvent) => {
        const timestamp = new Date(1 * logEvent.timestamp)

        // index name format: cwl-YYYY.MM.DD
        const indexName = [
            `cwl-${process.env.NODE_ENV}-${timestamp.getUTCFullYear()}`,              // year
            (`0${timestamp.getUTCMonth() + 1}`).slice(-2),  // month
            (`0${timestamp.getUTCDate()}`).slice(-2),          // day
        ].join('.')

        const source = buildSource(logEvent.message, logEvent.extractedFields)
        source['@id'] = logEvent.id
        source['@timestamp'] = new Date(1 * logEvent.timestamp).toISOString()
        source['@message'] = logEvent.message
        source['@owner'] = payload.owner
        source['@log_group'] = payload.logGroup
        source['@log_stream'] = payload.logStream

        const action = { index: {} }
        action.index._index = indexName
        action.index._type = 'lambdaLogs'
        action.index._id = logEvent.id

        bulkRequestBody += `${[
            JSON.stringify(action),
            JSON.stringify(source),
        ].join('\n')}\n`
    })
    return bulkRequestBody
}

function buildSource(message, extractedFields) {
    if (extractedFields) {
        const source = {}

        for (const key in extractedFields) {
            if (extractedFields.hasOwnProperty(key) && extractedFields[key]) {
                const value = extractedFields[key]

                if (isNumeric(value)) {
                    source[key] = 1 * value
                    continue
                }

                const jsonSubString = extractJson(value)
                if (jsonSubString !== null) {
                    source[`$${key}`] = JSON.parse(jsonSubString)
                }

                source[key] = value
            }
        }
        return source
    }

    const jsonSubString = extractJson(message)
    if (jsonSubString !== null) {
        return JSON.parse(jsonSubString)
    }

    return {}
}

function extractJson(message) {
    const jsonStart = message.indexOf('{')
    if (jsonStart < 0) return null
    const jsonSubString = message.substring(jsonStart)
    return isValidJson(jsonSubString) ? jsonSubString : null
}

function isValidJson(message) {
    try {
        JSON.parse(message)
    } catch (e) { return false }
    return true
}

function isNumeric(n) {
    return !isNaN(parseFloat(n)) && isFinite(n)
}

答案 1 :(得分:0)

我的一位同事帮助我完成了大部分工作,然后我设法弄清了最后一点。

  • 我将Serilog.Sinks.Udp更新为6.0.0
  • 我更新了UDP设置代码,以使用AddressFamily.InterNetwork说明符,我认为5.0.1中不可用。
  • 我删除了在日志消息中添加“标签”的原因,因为我认为该消息在UDP终结点上以某种方式引起了某种冲突,并且我已经看到它停止了记录而没有任何痕迹。

瞧!

这是新的日志记录设置代码:

loggerConfig
    .WriteTo.Udp(udpHost, udpPort, AddressFamily.InterNetwork, udpFormatter)
    .WriteTo.Console(outputTemplate: "[{Level:u}]: {Message}{NewLine}{Exception}");