Skip to content

Commit 637a4f3

Browse files
authored
Merge branch 'main' into lambda-log-support
2 parents a74e9fb + 99ed284 commit 637a4f3

File tree

8 files changed

+901
-657
lines changed

8 files changed

+901
-657
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/aws-opentelemetry-configurator.ts

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ import { BaggageSpanProcessor } from '@opentelemetry/baggage-span-processor';
8080
import { logs } from '@opentelemetry/api-logs';
8181
import { AWS_ATTRIBUTE_KEYS } from './aws-attribute-keys';
8282
import { 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

8486
const AWS_TRACES_OTLP_ENDPOINT_PATTERN = '^https://xray\\.([a-z0-9-]+)\\.amazonaws\\.com/v1/traces$';
8587
const 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

Comments
 (0)