@@ -80,6 +80,8 @@ import { BaggageSpanProcessor } from '@opentelemetry/baggage-span-processor';
8080import { logs } from '@opentelemetry/api-logs' ;
8181import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys' ;
8282import { AwsCloudWatchOtlpBatchLogRecordProcessor } from './exporter/otlp/aws/logs/aws-cw-otlp-batch-log-record-processor' ;
83+ import { ConsoleEMFExporter } from './exporter/aws/metrics/console-emf-exporter' ;
84+ import { EMFExporterBase } from './exporter/aws/metrics/emf-exporter-base' ;
8385
8486const AWS_TRACES_OTLP_ENDPOINT_PATTERN = '^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$' ;
8587const AWS_LOGS_OTLP_ENDPOINT_PATTERN = '^https://logs\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/logs$' ;
@@ -382,14 +384,17 @@ export class AwsOpentelemetryConfigurator {
382384 }
383385
384386 private customizeMetricReader ( isEmfEnabled : boolean ) {
387+ let exporter : PushMetricExporter | undefined = undefined ;
388+
385389 if ( isEmfEnabled ) {
386- const emfExporter = createEmfExporter ( ) ;
387- if ( emfExporter ) {
388- const periodicExportingMetricReader = new PeriodicExportingMetricReader ( {
389- exporter : emfExporter ,
390- } ) ;
391- this . metricReader = periodicExportingMetricReader ;
392- }
390+ exporter = createEmfExporter ( ) ;
391+ }
392+
393+ if ( exporter ) {
394+ const periodicExportingMetricReader = new PeriodicExportingMetricReader ( {
395+ exporter : exporter ,
396+ } ) ;
397+ this . metricReader = periodicExportingMetricReader ;
393398 }
394399 }
395400
@@ -523,8 +528,7 @@ export class AwsLoggerProcessorProvider {
523528 return exporters . map ( exporter => {
524529 if ( exporter instanceof ConsoleLogRecordExporter ) {
525530 return new SimpleLogRecordProcessor ( exporter ) ;
526- }
527- if ( exporter instanceof OTLPAwsLogExporter && isAgentObservabilityEnabled ( ) ) {
531+ } else if ( exporter instanceof OTLPAwsLogExporter && isAgentObservabilityEnabled ( ) ) {
528532 return new AwsCloudWatchOtlpBatchLogRecordProcessor ( exporter ) ;
529533 }
530534 return new BatchLogRecordProcessor ( exporter ) ;
@@ -561,42 +565,52 @@ export class AwsLoggerProcessorProvider {
561565 case 'http/json' :
562566 exporters . push ( new OTLPHttpLogExporter ( ) ) ;
563567 break ;
564- case 'http/protobuf' :
565- if (
566- otlpExporterLogsEndpoint &&
567- isAwsOtlpEndpoint ( otlpExporterLogsEndpoint , 'logs' ) &&
568- validateAndFetchLogsHeader ( ) . isValid
569- ) {
568+ case 'http/protobuf' : {
569+ let logExporter : LogRecordExporter | undefined = undefined ;
570+ if ( otlpExporterLogsEndpoint && isAwsOtlpEndpoint ( otlpExporterLogsEndpoint , 'logs' ) ) {
570571 diag . debug ( 'Detected CloudWatch Logs OTLP endpoint. Switching exporter to OTLPAwsLogExporter' ) ;
571- exporters . push (
572- new OTLPAwsLogExporter ( otlpExporterLogsEndpoint . toLowerCase ( ) , {
572+ if ( validateAndFetchLogsHeader ( ) . isValid ) {
573+ logExporter = new OTLPAwsLogExporter ( otlpExporterLogsEndpoint . toLowerCase ( ) , {
573574 compression : CompressionAlgorithm . GZIP ,
574- } )
575- ) ;
576- } else {
577- exporters . push ( new OTLPProtoLogExporter ( ) ) ;
575+ } ) ;
576+ } else {
577+ diag . warn (
578+ `Invalid configuration for OTLPAwsLogExporter, please configure the environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS to have values for ${ AWS_OTLP_LOGS_GROUP_HEADER } and ${ AWS_OTLP_LOGS_STREAM_HEADER } . Falling back to OTLPProtoLogExporter`
579+ ) ;
580+ }
581+ }
582+
583+ if ( ! logExporter ) {
584+ logExporter = new OTLPProtoLogExporter ( ) ;
578585 }
586+ exporters . push ( logExporter ) ;
579587 break ;
588+ }
580589 case undefined :
581590 case '' :
582591 exporters . push ( new OTLPProtoLogExporter ( ) ) ;
583592 break ;
584- default :
593+ default : {
585594 diag . warn ( `Unsupported OTLP logs protocol: "${ protocol } ". Using http/protobuf.` ) ;
586- if (
587- otlpExporterLogsEndpoint &&
588- isAwsOtlpEndpoint ( otlpExporterLogsEndpoint , 'logs' ) &&
589- validateAndFetchLogsHeader ( ) . isValid
590- ) {
595+ let logExporter : LogRecordExporter | undefined = undefined ;
596+ if ( otlpExporterLogsEndpoint && isAwsOtlpEndpoint ( otlpExporterLogsEndpoint , 'logs' ) ) {
591597 diag . debug ( 'Detected CloudWatch Logs OTLP endpoint. Switching exporter to OTLPAwsLogExporter' ) ;
592- exporters . push (
593- new OTLPAwsLogExporter ( otlpExporterLogsEndpoint . toLowerCase ( ) , {
598+ if ( validateAndFetchLogsHeader ( ) . isValid ) {
599+ logExporter = new OTLPAwsLogExporter ( otlpExporterLogsEndpoint . toLowerCase ( ) , {
594600 compression : CompressionAlgorithm . GZIP ,
595- } )
596- ) ;
597- } else {
598- exporters . push ( new OTLPProtoLogExporter ( ) ) ;
601+ } ) ;
602+ } else {
603+ diag . warn (
604+ `Invalid configuration for OTLPAwsLogExporter, please configure the environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS to have values for ${ AWS_OTLP_LOGS_GROUP_HEADER } and ${ AWS_OTLP_LOGS_STREAM_HEADER } . Falling back to OTLPProtoLogExporter`
605+ ) ;
606+ }
607+ }
608+
609+ if ( ! logExporter ) {
610+ logExporter = new OTLPProtoLogExporter ( ) ;
599611 }
612+ exporters . push ( logExporter ) ;
613+ }
600614 }
601615 } else if ( exporter === 'console' ) {
602616 exporters . push ( new ConsoleLogRecordExporter ( ) ) ;
@@ -951,23 +965,24 @@ export function validateAndFetchLogsHeader(): OtlpLogHeaderSetting {
951965 const logHeaders = process . env . OTEL_EXPORTER_OTLP_LOGS_HEADERS ;
952966
953967 if ( ! logHeaders ) {
954- diag . warn (
955- 'Missing required configuration: The environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS must be set with ' +
956- `required headers ${ AWS_OTLP_LOGS_GROUP_HEADER } and ${ AWS_OTLP_LOGS_STREAM_HEADER } . ` +
957- `Example: OTEL_EXPORTER_OTLP_LOGS_HEADERS="${ AWS_OTLP_LOGS_GROUP_HEADER } =my-log-group,${ AWS_OTLP_LOGS_STREAM_HEADER } =my-log-stream"`
958- ) ;
968+ if ( ! isLambdaEnvironment ( ) ) {
969+ diag . warn (
970+ 'Missing required configuration: The environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS must be set with ' +
971+ `required headers ${ AWS_OTLP_LOGS_GROUP_HEADER } and ${ AWS_OTLP_LOGS_STREAM_HEADER } . ` +
972+ `Example: OTEL_EXPORTER_OTLP_LOGS_HEADERS="${ AWS_OTLP_LOGS_GROUP_HEADER } =my-log-group,${ AWS_OTLP_LOGS_STREAM_HEADER } =my-log-stream"`
973+ ) ;
974+ }
959975 return {
960- logGroup : '' ,
961- logStream : '' ,
962- namespace : '' ,
976+ logGroup : undefined ,
977+ logStream : undefined ,
978+ namespace : undefined ,
963979 isValid : false ,
964980 } ;
965981 }
966982
967983 let logGroup : string | undefined = undefined ;
968984 let logStream : string | undefined = undefined ;
969985 let namespace : string | undefined = undefined ;
970- let filteredLogHeadersCount : number = 0 ;
971986
972987 for ( const pair of logHeaders . split ( ',' ) ) {
973988 const splitIndex = pair . indexOf ( '=' ) ;
@@ -977,23 +992,15 @@ export function validateAndFetchLogsHeader(): OtlpLogHeaderSetting {
977992
978993 if ( key === AWS_OTLP_LOGS_GROUP_HEADER && value ) {
979994 logGroup = value ;
980- filteredLogHeadersCount ++ ;
981995 } else if ( key === AWS_OTLP_LOGS_STREAM_HEADER && value ) {
982996 logStream = value ;
983- filteredLogHeadersCount ++ ;
984997 } else if ( key === AWS_EMF_METRICS_NAMESPACE && value ) {
985998 namespace = value ;
986999 }
9871000 }
9881001 }
9891002
990- const isValid = filteredLogHeadersCount === 2 && ! ! logGroup && ! ! logStream ;
991- if ( ! isValid ) {
992- diag . warn (
993- 'Incomplete configuration: Please configure the environment variable OTEL_EXPORTER_OTLP_LOGS_HEADERS ' +
994- `to have values for ${ AWS_OTLP_LOGS_GROUP_HEADER } and ${ AWS_OTLP_LOGS_STREAM_HEADER } `
995- ) ;
996- }
1003+ const isValid = ! ! logGroup && ! ! logStream ;
9971004
9981005 return {
9991006 logGroup : logGroup ,
@@ -1029,16 +1036,27 @@ export function checkEmfExporterEnabled(): boolean {
10291036 return true ;
10301037}
10311038
1032- export function createEmfExporter ( ) : AWSCloudWatchEMFExporter | undefined {
1033- const headersResult = validateAndFetchLogsHeader ( ) ;
1034- if ( ! headersResult . isValid ) {
1035- return undefined ;
1039+ /**
1040+ * Create the appropriate EMF exporter based on the environment and configuration.
1041+ *
1042+ * @returns {EMFExporterBase | undefined }
1043+ */
1044+ export function createEmfExporter ( ) : EMFExporterBase | undefined {
1045+ let exporter : EMFExporterBase | undefined = undefined ;
1046+ const otlpLogHeaderSetting = validateAndFetchLogsHeader ( ) ;
1047+
1048+ if ( isLambdaEnvironment ( ) && ! otlpLogHeaderSetting . isValid ) {
1049+ // Lambda without valid logs http headers - use Console EMF exporter
1050+ exporter = new ConsoleEMFExporter ( otlpLogHeaderSetting . namespace ) ;
1051+ } else if ( otlpLogHeaderSetting . isValid ) {
1052+ // Non-Lambda environment - use CloudWatch EMF exporter
1053+ // If headersResult.isValid is true, then headersResult.logGroup and headersResult.logStream are guaranteed to be strings
1054+ exporter = new AWSCloudWatchEMFExporter (
1055+ otlpLogHeaderSetting . namespace ,
1056+ otlpLogHeaderSetting . logGroup as string ,
1057+ otlpLogHeaderSetting . logStream as string
1058+ ) ;
10361059 }
10371060
1038- // If headersResult.isValid is true, then headersResult.logGroup and headersResult.logStream are guaranteed to be strings
1039- return new AWSCloudWatchEMFExporter (
1040- headersResult . namespace ,
1041- headersResult . logGroup as string ,
1042- headersResult . logStream as string
1043- ) ;
1061+ return exporter ;
10441062}
0 commit comments