Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import {
SpanProcessor,
TraceIdRatioBasedSampler,
} from '@opentelemetry/sdk-trace-base';

import {
BatchLogRecordProcessor,
ConsoleLogRecordExporter,
Expand Down Expand Up @@ -82,6 +81,7 @@ import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys';
import { AwsCloudWatchOtlpBatchLogRecordProcessor } from './exporter/otlp/aws/logs/aws-cw-otlp-batch-log-record-processor';
import { ConsoleEMFExporter } from './exporter/aws/metrics/console-emf-exporter';
import { EMFExporterBase } from './exporter/aws/metrics/emf-exporter-base';
import { CompressedConsoleLogRecordExporter } from './exporter/console/logs/compressed-console-log-exporter';

const AWS_TRACES_OTLP_ENDPOINT_PATTERN = '^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$';
const AWS_LOGS_OTLP_ENDPOINT_PATTERN = '^https://logs\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/logs$';
Expand Down Expand Up @@ -613,7 +613,16 @@ export class AwsLoggerProcessorProvider {
}
}
} else if (exporter === 'console') {
exporters.push(new ConsoleLogRecordExporter());
let logExporter: LogRecordExporter | undefined = undefined;
if (isLambdaEnvironment()) {
diag.debug(
'Lambda environment detected, using CompressedConsoleLogRecordExporter instead of ConsoleLogRecordExporter'
);
logExporter = new CompressedConsoleLogRecordExporter();
} else {
logExporter = new ConsoleLogRecordExporter();
}
exporters.push(logExporter);
} else {
diag.warn(`Unsupported OTEL_LOGS_EXPORTER value: "${exporter}". Supported values are: otlp, console, none.`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
import { ConsoleLogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs';

export class CompressedConsoleLogRecordExporter extends ConsoleLogRecordExporter {
override export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void {
this._sendLogRecordsToLambdaConsole(logs, resultCallback);
}

private _sendLogRecordsToLambdaConsole(logRecords: ReadableLogRecord[], done?: (result: ExportResult) => void): void {
for (const logRecord of logRecords) {
console.log(this['_exportInfo'](logRecord));
}
done?.({ code: ExportResultCode.SUCCESS });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { expect } from 'expect';
import * as sinon from 'sinon';
import { ExportResultCode } from '@opentelemetry/core';
import { ReadableLogRecord } from '@opentelemetry/sdk-logs';
import { Resource } from '@opentelemetry/resources';
import { CompressedConsoleLogRecordExporter } from '../../../../src/exporter/console/logs/compressed-console-log-exporter';
import { Attributes } from '@opentelemetry/api';

describe('CompressedConsoleLogRecordExporter', () => {
let exporter: CompressedConsoleLogRecordExporter;
let consoleLogSpy: sinon.SinonSpy;

const createMockLogRecord = (body: string, attributes: Attributes = {}): ReadableLogRecord => ({
hrTime: [1640995200, 0],
hrTimeObserved: [1640995200, 0],
body,
severityText: 'INFO',
attributes,
resource: Resource.empty(),
instrumentationScope: { name: 'test', version: '1.0.0' },
droppedAttributesCount: 0,
});

beforeEach(() => {
exporter = new CompressedConsoleLogRecordExporter();
consoleLogSpy = sinon.spy(console, 'log');
});

afterEach(() => {
sinon.restore();
});

it('should export logs and call callback with success', done => {
const mockLogRecord = createMockLogRecord('test log message');
const logs = [mockLogRecord];

exporter.export(logs, result => {
expect(result.code).toBe(ExportResultCode.SUCCESS);
expect(consoleLogSpy.calledOnce).toBeTruthy();

const loggedContent = consoleLogSpy.firstCall.args[0];
expect(typeof loggedContent).toBe('object');
expect(loggedContent.body).toBe('test log message');
expect(loggedContent.severityText).toBe('INFO');
expect(loggedContent.instrumentationScope.name).toBe('test');
expect(loggedContent.instrumentationScope.version).toBe('1.0.0');

done();
});
});

it('should export multiple logs', done => {
const mockLogRecords = [createMockLogRecord('log 1'), createMockLogRecord('log 2')];

exporter.export(mockLogRecords, result => {
expect(result.code).toBe(ExportResultCode.SUCCESS);
expect(consoleLogSpy.callCount).toBe(2);

const firstLogContent = consoleLogSpy.firstCall.args[0];
const secondLogContent = consoleLogSpy.secondCall.args[0];

expect(firstLogContent.body).toBe('log 1');
expect(secondLogContent.body).toBe('log 2');

done();
});
});

it('should handle empty logs array', done => {
exporter.export([], result => {
expect(result.code).toBe(ExportResultCode.SUCCESS);
expect(consoleLogSpy.called).toBeFalsy();
done();
});
});

it('should work without callback', () => {
const mockLogRecord = createMockLogRecord('test log message');

expect(() => {
exporter.export([mockLogRecord], () => {});
}).not.toThrow();
expect(consoleLogSpy.calledOnce).toBeTruthy();

const loggedContent = consoleLogSpy.firstCall.args[0];
expect(loggedContent.body).toBe('test log message');
});

it('should handle undefined callback gracefully', () => {
const mockLogRecord = createMockLogRecord('test log message');

expect(() => {
exporter['_sendLogRecordsToLambdaConsole']([mockLogRecord]);
}).not.toThrow();
expect(consoleLogSpy.calledOnce).toBeTruthy();

const loggedContent = consoleLogSpy.firstCall.args[0];
expect(loggedContent.body).toBe('test log message');
});

it('should format log record with all expected fields', done => {
const mockLogRecord = createMockLogRecord('detailed test message', {
customKey: 'customValue',
requestId: '12345',
});

exporter.export([mockLogRecord], result => {
expect(result.code).toBe(ExportResultCode.SUCCESS);

const loggedContent = consoleLogSpy.firstCall.args[0];
expect(typeof loggedContent).toBe('object');
expect(loggedContent.body).toBe('detailed test message');
expect(loggedContent.severityText).toBe('INFO');
expect(loggedContent.instrumentationScope.name).toBe('test');
expect(loggedContent.instrumentationScope.version).toBe('1.0.0');
expect(loggedContent.attributes).toEqual({ customKey: 'customValue', requestId: '12345' });

done();
});
});
});
Loading