From e02a42d8e2ec58478c1a5a4c71a0f4ff577efa82 Mon Sep 17 00:00:00 2001 From: jjllee Date: Fri, 20 Jun 2025 09:08:24 -0700 Subject: [PATCH] auto-config agent observability --- .../src/register.ts | 61 +++++++++++-- .../src/utils.ts | 33 +++++++ .../test/register.test.ts | 85 ++++++++++++++++++- .../test/utils.test.ts | 55 ++++++++++++ 4 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 aws-distro-opentelemetry-node-autoinstrumentation/test/utils.test.ts diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts index bdea7eb9..a0ade9da 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/register.ts @@ -19,6 +19,7 @@ import { Instrumentation } from '@opentelemetry/instrumentation'; import * as opentelemetry from '@opentelemetry/sdk-node'; import { AwsOpentelemetryConfigurator } from './aws-opentelemetry-configurator'; import { applyInstrumentationPatches, customExtractor } from './patches/instrumentation-patch'; +import { getAwsRegionFromEnvironment, isAgentObservabilityEnabled } from './utils'; diag.setLogger(new DiagConsoleLogger(), opentelemetry.core.getEnv().OTEL_LOG_LEVEL); @@ -36,18 +37,68 @@ This file may also be used to apply patches to upstream instrumentation - usuall long-term changes to upstream. */ -export function setAwsDefaultEnvironmentVariables(): void { +export function setAwsDefaultEnvironmentVariables() { if (!process.env.OTEL_EXPORTER_OTLP_PROTOCOL) { process.env.OTEL_EXPORTER_OTLP_PROTOCOL = 'http/protobuf'; } if (!process.env.OTEL_PROPAGATORS) { process.env.OTEL_PROPAGATORS = 'xray,tracecontext'; } - // Disable the following instrumentations by default - // This auto-instrumentation for the `fs` module generates many low-value spans. `dns` is similar. - // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1344#issuecomment-1618993178 if (!process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS) { - process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS = 'fs,dns'; + if (isAgentObservabilityEnabled()) { + // Assume users only need instrumentations that are manually set-up outside of OpenTelemetry + process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS = + 'amqplib,aws-lambda,aws-sdk,bunyan,cassandra-driver,connect,cucumber,dataloader,dns,express,fastify,fs,generic-pool,graphql,grpc,hapi,http,ioredis,kafkajs,knex,koa,lru-memoizer,memcached,mongodb,mongoose,mysql2,mysql,nestjs-core,net,pg,pino,redis,redis-4,restify,router,socket.io,tedious,undici,winston'; + } else { + // Disable the following instrumentations by default + // This auto-instrumentation for the `fs` module generates many low-value spans. `dns` is similar. + // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1344#issuecomment-1618993178 + process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS = 'fs,dns'; + } + } + + if (isAgentObservabilityEnabled()) { + // Set exporter defaults + if (!process.env.OTEL_TRACES_EXPORTER) { + process.env.OTEL_TRACES_EXPORTER = 'otlp'; + } + if (!process.env.OTEL_LOGS_EXPORTER) { + process.env.OTEL_LOGS_EXPORTER = 'otlp'; + } + if (!process.env.OTEL_METRICS_EXPORTER) { + process.env.OTEL_METRICS_EXPORTER = 'awsemf'; + } + + // Set GenAI capture content default + if (!process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT) { + process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = 'true'; + } + + // Set sampler default + if (!process.env.OTEL_TRACES_SAMPLER && !useXraySampler) { + process.env.OTEL_TRACES_SAMPLER = 'parentbased_always_on'; + } + + // Disable AWS Application Signals by default + if (!process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED) { + process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED = 'false'; + } + + // Set OTLP endpoints with AWS region if not already set + const region = getAwsRegionFromEnvironment(); + if (region) { + if (!process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) { + process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = `https://xray.${region}.amazonaws.com/v1/traces`; + } + + if (!process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) { + process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = `https://logs.${region}.amazonaws.com/v1/logs`; + } + } else { + diag.error( + 'AWS region could not be determined. OTLP endpoints will not be automatically configured. Please set AWS_REGION environment variable or configure OTLP endpoints manually.' + ); + } } } setAwsDefaultEnvironmentVariables(); diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts b/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts index 0fd74c0d..f47f1d28 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/src/utils.ts @@ -1,6 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { diag } from '@opentelemetry/api'; + +const AGENT_OBSERVABILITY_ENABLED = 'AGENT_OBSERVABILITY_ENABLED'; + export const getNodeVersion = () => { const nodeVersion = process.versions.node; const versionParts = nodeVersion.split('.'); @@ -17,3 +21,32 @@ export const getNodeVersion = () => { return majorVersion; }; + +export const isAgentObservabilityEnabled = () => { + const agentObservabilityEnabled: string | undefined = process.env[AGENT_OBSERVABILITY_ENABLED]; + if (agentObservabilityEnabled === undefined) { + return false; + } + + return agentObservabilityEnabled.toLowerCase() === 'true'; +}; + +/** + * Get AWS region from environment or boto3 session. + * Returns the AWS region in the following priority order: + * 1. AWS_REGION environment variable + * 2. AWS_DEFAULT_REGION environment variable + * 3. undefined if no region can be determined + */ +export const getAwsRegionFromEnvironment = (): string | undefined => { + const region = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION; + if (region) { + return region; + } + + diag.warn( + 'AWS region not found in environment variables (AWS_REGION, AWS_DEFAULT_REGION). Please set AWS_REGION environment variable explicitly.' + ); + + return undefined; +}; diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/register.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/register.test.ts index 9e6e2512..e9b227bc 100644 --- a/aws-distro-opentelemetry-node-autoinstrumentation/test/register.test.ts +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/register.test.ts @@ -23,18 +23,30 @@ describe('Register', function () { NodeSDK.prototype.start = originalPrototypeStart; }); - it('Tests AWS Default Environment Variables', () => { - this.beforeEach(() => { + describe('Tests AWS Default Environment Variables', () => { + beforeEach(() => { delete process.env.OTEL_EXPORTER_OTLP_PROTOCOL; delete process.env.OTEL_PROPAGATORS; delete process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS; + + delete process.env.AWS_REGION; + delete process.env.AGENT_OBSERVABILITY_ENABLED; + delete process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT; + delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete process.env.OTEL_TRACES_EXPORTER; + delete process.env.OTEL_LOGS_EXPORTER; + delete process.env.OTEL_METRICS_EXPORTER; + + delete process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT; + delete process.env.OTEL_TRACES_SAMPLER; + delete process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED; }); it('sets AWS Default Environment Variables', () => { setAwsDefaultEnvironmentVariables(); expect(process.env.OTEL_EXPORTER_OTLP_PROTOCOL).toEqual('http/protobuf'); expect(process.env.OTEL_PROPAGATORS).toEqual('xray,tracecontext'); - expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual('fs'); + expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual('fs,dns'); }); it('Does not set AWS Default Environment Variables', () => { @@ -46,6 +58,71 @@ describe('Register', function () { expect(process.env.OTEL_PROPAGATORS).toEqual('customPropagators'); expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual('customDisabledInstrumentations'); }); + + it('Configures with AgentObservabilityEnabled with unset region', () => { + process.env.AGENT_OBSERVABILITY_ENABLED = 'true'; + + setAwsDefaultEnvironmentVariables(); + + expect(process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT).toBeUndefined(); + expect(process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT).toBeUndefined(); + }); + + it('Configures with AgentObservabilityEnabled with set region', () => { + process.env.AWS_REGION = 'us-west-2'; + process.env.AGENT_OBSERVABILITY_ENABLED = 'true'; + + setAwsDefaultEnvironmentVariables(); + + expect(process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT).toEqual('https://xray.us-west-2.amazonaws.com/v1/traces'); + expect(process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT).toEqual('https://logs.us-west-2.amazonaws.com/v1/logs'); + }); + + it('Configures defaults when AgentObservabilityEnabled is true', () => { + process.env.AWS_REGION = 'us-east-1'; + process.env.AGENT_OBSERVABILITY_ENABLED = 'true'; + + setAwsDefaultEnvironmentVariables(); + + expect(process.env.OTEL_TRACES_EXPORTER).toEqual('otlp'); + expect(process.env.OTEL_LOGS_EXPORTER).toEqual('otlp'); + expect(process.env.OTEL_METRICS_EXPORTER).toEqual('awsemf'); + expect(process.env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT).toEqual('true'); + expect(process.env.OTEL_TRACES_SAMPLER).toEqual('parentbased_always_on'); + expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual( + 'amqplib,aws-lambda,aws-sdk,bunyan,cassandra-driver,connect,cucumber,dataloader,dns,express,fastify,fs,generic-pool,graphql,grpc,hapi,http,ioredis,kafkajs,knex,koa,lru-memoizer,memcached,mongodb,mongoose,mysql2,mysql,nestjs-core,net,pg,pino,redis,redis-4,restify,router,socket.io,tedious,undici,winston' + ); + expect(process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED).toEqual('false'); + expect(process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT).toEqual('https://xray.us-east-1.amazonaws.com/v1/traces'); + expect(process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT).toEqual('https://logs.us-east-1.amazonaws.com/v1/logs'); + }); + + it('Respects user configuration when AgentObservabilityEnabled is false', () => { + process.env.AWS_REGION = 'us-east-1'; + delete process.env.AGENT_OBSERVABILITY_ENABLED; + process.env.OTEL_TRACES_SAMPLER = 'traceidratio'; + + setAwsDefaultEnvironmentVariables(); + expect(process.env.OTEL_TRACES_SAMPLER).toEqual('traceidratio'); + expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual('fs,dns'); + expect(process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED).toBeUndefined(); + }); + + it('Respects user configuration when AgentObservabilityEnabled is true', () => { + // Enable agent observability + process.env.AGENT_OBSERVABILITY_ENABLED = 'true'; + + // Set custom values for some environment variables + process.env.OTEL_TRACES_SAMPLER = 'traceidratio'; + process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS = 'a,b,c,d'; + process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED = 'true'; + + setAwsDefaultEnvironmentVariables(); + + expect(process.env.OTEL_TRACES_SAMPLER).toEqual('traceidratio'); + expect(process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS).toEqual('a,b,c,d'); + expect(process.env.OTEL_AWS_APPLICATION_SIGNALS_ENABLED).toEqual('true'); + }); }); it('can load auto instrumentation from command line', () => { @@ -89,7 +166,7 @@ describe('Register', function () { ); // eslint-disable-next-line @typescript-eslint/typedef - const packageJson = require('./../../package.json'); + const packageJson = require('./../package.json'); const DISTRO_VERSION: string = packageJson.version; assert.ok( proc.stdout.includes(`'telemetry.auto.version': '${DISTRO_VERSION}-aws'`), diff --git a/aws-distro-opentelemetry-node-autoinstrumentation/test/utils.test.ts b/aws-distro-opentelemetry-node-autoinstrumentation/test/utils.test.ts new file mode 100644 index 00000000..bfffe670 --- /dev/null +++ b/aws-distro-opentelemetry-node-autoinstrumentation/test/utils.test.ts @@ -0,0 +1,55 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import expect from 'expect'; +import { getAwsRegionFromEnvironment, isAgentObservabilityEnabled } from '../src/utils'; + +describe('Utils', function () { + beforeEach(() => { + delete process.env.AGENT_OBSERVABILITY_ENABLED; + delete process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT; + delete process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT; + delete process.env.AWS_REGION; + delete process.env.AWS_DEFAULT_REGION; + }); + + it('Test isAgentObservabilityEnabled to be True', () => { + process.env.AGENT_OBSERVABILITY_ENABLED = 'true'; + expect(isAgentObservabilityEnabled()).toBeTruthy(); + + process.env.AGENT_OBSERVABILITY_ENABLED = 'True'; + expect(isAgentObservabilityEnabled()).toBeTruthy(); + + process.env.AGENT_OBSERVABILITY_ENABLED = 'TRUE'; + expect(isAgentObservabilityEnabled()).toBeTruthy(); + }); + + it('Test isAgentObservabilityEnabled to be False', () => { + process.env.AGENT_OBSERVABILITY_ENABLED = 'false'; + expect(isAgentObservabilityEnabled()).toBeFalsy(); + + process.env.AGENT_OBSERVABILITY_ENABLED = 'False'; + expect(isAgentObservabilityEnabled()).toBeFalsy(); + + process.env.AGENT_OBSERVABILITY_ENABLED = 'FALSE'; + expect(isAgentObservabilityEnabled()).toBeFalsy(); + + process.env.AGENT_OBSERVABILITY_ENABLED = 'anything else'; + expect(isAgentObservabilityEnabled()).toBeFalsy(); + + delete process.env.AGENT_OBSERVABILITY_ENABLED; + expect(isAgentObservabilityEnabled()).toBeFalsy(); + }); + + it('Test getAwsRegion from AWS_REGION env var', () => { + process.env.AWS_REGION = 'us-west-2'; + process.env.AWS_DEFAULT_REGION = 'eu-west-1'; + expect(getAwsRegionFromEnvironment()).toEqual('us-west-2'); + }); + + it('Test getAwsRegion from AWS_REGION env var', () => { + delete process.env.AWS_REGION; + process.env.AWS_DEFAULT_REGION = 'eu-west-1'; + expect(getAwsRegionFromEnvironment()).toEqual('eu-west-1'); + }); +});