11// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22// SPDX-License-Identifier: Apache-2.0
33import { OTLPLogExporter as OTLPProtoLogExporter } from '@opentelemetry/exporter-logs-otlp-proto' ;
4- import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base' ;
5- import { ProtobufLogsSerializer } from '@opentelemetry/otlp-transformer' ;
4+ import { CompressionAlgorithm , OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base' ;
5+ import { gzipSync } from 'zlib' ;
6+ import { IExportLogsServiceResponse , ProtobufLogsSerializer } from '@opentelemetry/otlp-transformer' ;
67import { ReadableLogRecord } from '@opentelemetry/sdk-logs' ;
78import { AwsAuthenticator } from '../common/aws-authenticator' ;
8- import { changeUrlConfig } from '../common/utils ' ;
9- import { ExportResult } from '@opentelemetry/core ' ;
9+ import { ExportResult , ExportResultCode } from '@opentelemetry/core ' ;
10+ import { PassthroughSerializer } from '../common/passthrough-serializer ' ;
1011
1112/**
1213 * This exporter extends the functionality of the OTLPProtoLogExporter to allow spans to be exported
@@ -16,34 +17,78 @@ import { ExportResult } from '@opentelemetry/core';
1617 *
1718 * This only works with version >=16 Node.js environments.
1819 */
20+
1921export class OTLPAwsLogExporter extends OTLPProtoLogExporter {
22+ private compression : CompressionAlgorithm | undefined ;
2023 private endpoint : string ;
2124 private region : string ;
25+ private serializer : PassthroughSerializer < IExportLogsServiceResponse > ;
2226 private authenticator : AwsAuthenticator ;
2327
2428 constructor ( endpoint : string , config ?: OTLPExporterNodeConfigBase ) {
25- super ( changeUrlConfig ( endpoint , config ) ) ;
29+ const modifiedConfig : OTLPExporterNodeConfigBase = {
30+ ...config ,
31+ url : endpoint ,
32+ compression : CompressionAlgorithm . NONE ,
33+ } ;
34+
35+ super ( modifiedConfig ) ;
36+ this . compression = config ?. compression ;
2637 this . region = endpoint . split ( '.' ) [ 1 ] ;
2738 this . endpoint = endpoint ;
2839 this . authenticator = new AwsAuthenticator ( this . region , 'logs' ) ;
40+
41+ // This is used in order to prevent serializing and compressing the data twice. Once for signing Sigv4 and
42+ // once when we pass the data to super.export() which will serialize and compress the data again.
43+ this . serializer = new PassthroughSerializer ( ProtobufLogsSerializer . deserializeResponse ) ;
44+ this [ '_delegate' ] . _serializer = this . serializer ;
2945 }
3046
3147 /**
32- * Overrides the upstream implementation of export. All behaviors are the same except if the
33- * endpoint is the CloudWatch Logs OTLP endpoint, we will sign the request with SigV4 in headers before
34- * sending it to the endpoint. Otherwise, we will skip signing.
48+ * Overrides the upstream implementation of export. If the
49+ * endpoint is the CloudWatch Logs OTLP endpoint, we sign the request with SigV4 in headers.
50+ * To prevent performance degradation from serializing and compressing data twice, we handle serialization and compression
51+ * locally in this exporter and pass the pre-processed data to the upstream export functionality.
3552 */
53+
54+ // Upstream already implements a retry mechanism:
55+ // https://github.com/open-telemetry/opentelemetry-js/blob/main/experimental/packages/otlp-exporter-base/src/retrying-transport.ts
56+
3657 public override async export (
3758 items : ReadableLogRecord [ ] ,
3859 resultCallback : ( result : ExportResult ) => void
3960 ) : Promise < void > {
40- const serializedLogs : Uint8Array | undefined = ProtobufLogsSerializer . serializeRequest ( items ) ;
61+ let serializedLogs : Uint8Array | undefined = ProtobufLogsSerializer . serializeRequest ( items ) ;
62+
63+ if ( serializedLogs === undefined ) {
64+ resultCallback ( {
65+ code : ExportResultCode . FAILED ,
66+ error : new Error ( 'Nothing to send' ) ,
67+ } ) ;
68+ return ;
69+ }
70+
71+ const shouldCompress = this . compression && this . compression !== CompressionAlgorithm . NONE ;
72+ if ( shouldCompress ) {
73+ serializedLogs = gzipSync ( serializedLogs ) ;
74+ }
75+
76+ // Pass pre-processed data to passthrough serializer. When super.export() is called, the Passthrough Serializer will
77+ // use the pre-processed data instead of serializing and compressing the data again.
78+ this . serializer . setSerializedData ( serializedLogs ) ;
79+
80+ // See type: https://github.com/open-telemetry/opentelemetry-js/blob/experimental/v0.57.1/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts#L31
4181 const headers = this [ '_delegate' ] . _transport ?. _transport ?. _parameters ?. headers ( ) ;
4282
4383 if ( headers ) {
84+ if ( shouldCompress ) {
85+ headers [ 'Content-Encoding' ] = 'gzip' ;
86+ } else {
87+ delete headers [ 'Content-Encoding' ] ;
88+ }
89+
4490 const signedRequest = await this . authenticator . authenticate ( this . endpoint , headers , serializedLogs ) ;
4591
46- // See type: https://github.com/open-telemetry/opentelemetry-js/blob/experimental/v0.57.1/experimental/packages/otlp-exporter-base/src/transport/http-transport-types.ts#L31
4792 const newHeaders : ( ) => Record < string , string > = ( ) => signedRequest ;
4893 this [ '_delegate' ] . _transport . _transport . _parameters . headers = newHeaders ;
4994 }
0 commit comments