【问题标题】:AWS Firehose newline CharacterAWS Firehose 换行符
【发布时间】:2019-09-26 09:06:53
【问题描述】:

我已经阅读了很多关于向 firehose 添加换行符的类似问题,但它们都是围绕将换行符添加到源代码。问题是我无权访问源,并且第三方正在将数据传输到我们的 Kinesis 实例,我无法将“\n”添加到源。

我尝试使用以下代码进行 Firehose 数据转换:

'use strict';
console.log('Loading function');

exports.handler = (event, context, callback) => {
    /* Process the list of records and transform them */
    const output = [];
    event.records.forEach((record) => {
        const results = {
        /* This transformation is the "identity" transformation, the data is left intact */
            recordId: record.recordId,
            result: record.data.event_type === 'alert' ? 'Dropped' : 'Ok',
            data: record.data + '\n'
        };
        output.push(results);
    });
    console.log(`Processing completed.  Successful records ${output.length}.`);
    callback(null, { records: output });
};

但换行符仍然丢失。我也尝试过JSON.stringify(record.data) + '\n',但随后出现Invalid output structure 错误。

【问题讨论】:

    标签: amazon-web-services amazon-kinesis amazon-kinesis-firehose


    【解决方案1】:

    尝试解码record.data 添加新行 然后将其再次编码为 base 64。

    这是python但是思路是一样的

    for record in event['records']:
        payload = base64.b64decode(record['data'])
        # Do custom processing on the payload here
        payload = payload + '\n'
        output_record = {
            'recordId': record['recordId'],
            'result': 'Ok',
            'data': base64.b64encode(json.dumps(payload))
        }
        output.append(output_record)
    return {'records': output}
    

    来自@Matt Westlake 的评论:

    对于那些寻找节点答案的人来说,它是

    常量数据 = JSON.parse(new Buffer.from(record.data,'base64').toString('utf8'));

    new Buffer.from(JSON.stringify(data) + '\n').toString('base64')

    【讨论】:

    • 对于那些寻找节点答案的人,它是 const data = JSON.parse(new Buffer.from(record.data,'base64').toString('utf8'));new Buffer.from(JSON.stringify(data) + '\n').toString('base64')
    • newBuffer.from(record.data,'base64')newBuffer 之间缺少一个空格
    【解决方案2】:

    kinesis-firehose-cloudwatch-logs-processor 蓝图 lambda 执行此操作(对 cloudwatch 日志进行一些额外处理)。

    这是截至今天的蓝图中的 lambda 代码:

    /*
    For processing data sent to Firehose by Cloudwatch Logs subscription filters.
    
    Cloudwatch Logs sends to Firehose records that look like this:
    
    {
      "messageType": "DATA_MESSAGE",
      "owner": "123456789012",
      "logGroup": "log_group_name",
      "logStream": "log_stream_name",
      "subscriptionFilters": [
        "subscription_filter_name"
      ],
      "logEvents": [
        {
          "id": "01234567890123456789012345678901234567890123456789012345",
          "timestamp": 1510109208016,
          "message": "log message 1"
        },
        {
          "id": "01234567890123456789012345678901234567890123456789012345",
          "timestamp": 1510109208017,
          "message": "log message 2"
        }
        ...
      ]
    }
    
    The data is additionally compressed with GZIP.
    
    The code below will:
    
    1) Gunzip the data
    2) Parse the json
    3) Set the result to ProcessingFailed for any record whose messageType is not DATA_MESSAGE, thus redirecting them to the
       processing error output. Such records do not contain any log events. You can modify the code to set the result to
       Dropped instead to get rid of these records completely.
    4) For records whose messageType is DATA_MESSAGE, extract the individual log events from the logEvents field, and pass
       each one to the transformLogEvent method. You can modify the transformLogEvent method to perform custom
       transformations on the log events.
    5) Concatenate the result from (4) together and set the result as the data of the record returned to Firehose. Note that
       this step will not add any delimiters. Delimiters should be appended by the logic within the transformLogEvent
       method.
    6) Any additional records which exceed 6MB will be re-ingested back into Firehose.
    */
    const zlib = require('zlib');
    const AWS = require('aws-sdk');
    
    /**
     * logEvent has this format:
     *
     * {
     *   "id": "01234567890123456789012345678901234567890123456789012345",
     *   "timestamp": 1510109208016,
     *   "message": "log message 1"
     * }
     *
     * The default implementation below just extracts the message and appends a newline to it.
     *
     * The result must be returned in a Promise.
     */
    function transformLogEvent(logEvent) {
        return Promise.resolve(`${logEvent.message}\n`);
    }
    
    function putRecordsToFirehoseStream(streamName, records, client, resolve, reject, attemptsMade, maxAttempts) {
        client.putRecordBatch({
            DeliveryStreamName: streamName,
            Records: records,
        }, (err, data) => {
            const codes = [];
            let failed = [];
            let errMsg = err;
    
            if (err) {
                failed = records;
            } else {
                for (let i = 0; i < data.RequestResponses.length; i++) {
                    const code = data.RequestResponses[i].ErrorCode;
                    if (code) {
                        codes.push(code);
                        failed.push(records[i]);
                    }
                }
                errMsg = `Individual error codes: ${codes}`;
            }
    
            if (failed.length > 0) {
                if (attemptsMade + 1 < maxAttempts) {
                    console.log('Some records failed while calling PutRecordBatch, retrying. %s', errMsg);
                    putRecordsToFirehoseStream(streamName, failed, client, resolve, reject, attemptsMade + 1, maxAttempts);
                } else {
                    reject(`Could not put records after ${maxAttempts} attempts. ${errMsg}`);
                }
            } else {
                resolve('');
            }
        });
    }
    
    function putRecordsToKinesisStream(streamName, records, client, resolve, reject, attemptsMade, maxAttempts) {
        client.putRecords({
            StreamName: streamName,
            Records: records,
        }, (err, data) => {
            const codes = [];
            let failed = [];
            let errMsg = err;
    
            if (err) {
                failed = records;
            } else {
                for (let i = 0; i < data.Records.length; i++) {
                    const code = data.Records[i].ErrorCode;
                    if (code) {
                        codes.push(code);
                        failed.push(records[i]);
                    }
                }
                errMsg = `Individual error codes: ${codes}`;
            }
    
            if (failed.length > 0) {
                if (attemptsMade + 1 < maxAttempts) {
                    console.log('Some records failed while calling PutRecords, retrying. %s', errMsg);
                    putRecordsToKinesisStream(streamName, failed, client, resolve, reject, attemptsMade + 1, maxAttempts);
                } else {
                    reject(`Could not put records after ${maxAttempts} attempts. ${errMsg}`);
                }
            } else {
                resolve('');
            }
        });
    }
    
    function createReingestionRecord(isSas, originalRecord) {
        if (isSas) {
            return {
                Data: new Buffer(originalRecord.data, 'base64'),
                PartitionKey: originalRecord.kinesisRecordMetadata.partitionKey,
            };
        } else {
            return {
                Data: new Buffer(originalRecord.data, 'base64'),
            };
        }
    }
    
    
    function getReingestionRecord(isSas, reIngestionRecord) {
        if (isSas) {
            return {
                Data: reIngestionRecord.Data,
                PartitionKey: reIngestionRecord.PartitionKey,
            };
        } else {
            return {
                Data: reIngestionRecord.Data,
            };
        }
    }
    
    exports.handler = (event, context, callback) => {
        Promise.all(event.records.map(r => {
            const buffer = new Buffer(r.data, 'base64');
            const decompressed = zlib.gunzipSync(buffer);
            const data = JSON.parse(decompressed);
            // CONTROL_MESSAGE are sent by CWL to check if the subscription is reachable.
            // They do not contain actual data.
            if (data.messageType === 'CONTROL_MESSAGE') {
                return Promise.resolve({
                    recordId: r.recordId,
                    result: 'Dropped',
                });
            } else if (data.messageType === 'DATA_MESSAGE') {
                const promises = data.logEvents.map(transformLogEvent);
                return Promise.all(promises)
                    .then(transformed => {
                        const payload = transformed.reduce((a, v) => a + v, '');
                        const encoded = new Buffer(payload).toString('base64');
                        return {
                            recordId: r.recordId,
                            result: 'Ok',
                            data: encoded,
                        };
                    });
            } else {
                return Promise.resolve({
                    recordId: r.recordId,
                    result: 'ProcessingFailed',
                });
            }
        })).then(recs => {
            const isSas = Object.prototype.hasOwnProperty.call(event, 'sourceKinesisStreamArn');
            const streamARN = isSas ? event.sourceKinesisStreamArn : event.deliveryStreamArn;
            const region = streamARN.split(':')[3];
            const streamName = streamARN.split('/')[1];
            const result = { records: recs };
            let recordsToReingest = [];
            const putRecordBatches = [];
            let totalRecordsToBeReingested = 0;
            const inputDataByRecId = {};
            event.records.forEach(r => inputDataByRecId[r.recordId] = createReingestionRecord(isSas, r));
    
            let projectedSize = recs.filter(rec => rec.result === 'Ok')
                                  .map(r => r.recordId.length + r.data.length)
                                  .reduce((a, b) => a + b);
            // 6000000 instead of 6291456 to leave ample headroom for the stuff we didn't account for
            for (let idx = 0; idx < event.records.length && projectedSize > 6000000; idx++) {
                const rec = result.records[idx];
                if (rec.result === 'Ok') {
                    totalRecordsToBeReingested++;
                    recordsToReingest.push(getReingestionRecord(isSas, inputDataByRecId[rec.recordId]));
                    projectedSize -= rec.data.length;
                    delete rec.data;
                    result.records[idx].result = 'Dropped';
    
                    // split out the record batches into multiple groups, 500 records at max per group
                    if (recordsToReingest.length === 500) {
                        putRecordBatches.push(recordsToReingest);
                        recordsToReingest = [];
                    }
                }
            }
    
            if (recordsToReingest.length > 0) {
                // add the last batch
                putRecordBatches.push(recordsToReingest);
            }
    
            if (putRecordBatches.length > 0) {
                new Promise((resolve, reject) => {
                    let recordsReingestedSoFar = 0;
                    for (let idx = 0; idx < putRecordBatches.length; idx++) {
                        const recordBatch = putRecordBatches[idx];
                        if (isSas) {
                            const client = new AWS.Kinesis({ region: region });
                            putRecordsToKinesisStream(streamName, recordBatch, client, resolve, reject, 0, 20);
                        } else {
                            const client = new AWS.Firehose({ region: region });
                            putRecordsToFirehoseStream(streamName, recordBatch, client, resolve, reject, 0, 20);
                        }
                        recordsReingestedSoFar += recordBatch.length;
                        console.log('Reingested %s/%s records out of %s in to %s stream', recordsReingestedSoFar, totalRecordsToBeReingested, event.records.length, streamName);
                    }
                }).then(
                  () => {
                      console.log('Reingested all %s records out of %s in to %s stream', totalRecordsToBeReingested, event.records.length, streamName);
                      callback(null, result);
                  },
                  failed => {
                      console.log('Failed to reingest records. %s', failed);
                      callback(failed, null);
                  });
            } else {
                console.log('No records needed to be reingested.');
                callback(null, result);
            }
        }).catch(ex => {
            console.log('Error: ', ex);
            callback(ex, null);
        });
    };
    
    

    【讨论】:

      猜你喜欢
      • 2018-02-15
      • 2017-10-30
      • 2017-12-04
      • 2016-12-05
      • 1970-01-01
      • 2018-10-11
      • 2018-07-02
      • 2021-10-31
      • 2018-06-28
      相关资源
      最近更新 更多