packages/@aws-cdk/aws-applicationsignals-alpha/lib/enablement/ecs-sdk-instrumentation.ts (283 lines of code) (raw):

import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as constants from './constants'; import * as inst from './instrumentation-versions'; /** * Interface for environment extensions. */ export interface EnvironmentExtension { /** * The name of the environment variable. */ readonly name: string; /** * The value of the environment variable. */ readonly value: string; } /** * Injector is a base class for all SDK injects to mutate the task definition * to inject the ADOT init container and configure the application container with * the necessary environment variables. */ export abstract class Injector { protected static readonly DEFAULT_ENVS: EnvironmentExtension[] = [ { name: constants.LogsExporting.OTEL_LOGS_EXPORTER, value: constants.LogsExporting.OTEL_LOGS_EXPORTER_NONE, }, { name: constants.MetricsExporting.OTEL_METRICS_EXPORTER, value: constants.MetricsExporting.OTEL_METRICS_EXPORTER_NONE, }, { name: constants.CommonExporting.OTEL_EXPORTER_OTLP_PROTOCOL, value: constants.CommonExporting.OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF, }, { name: constants.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS, value: constants.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_ENABLED, }, { name: constants.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT, value: constants.CommonExporting.OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT_LOCAL_CWA, }, { name: constants.TraceExporting.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, value: constants.TraceExporting.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_LOCAL_CWA, }, { name: constants.TraceExporting.OTEL_TRACES_SAMPLER, value: constants.TraceExporting.OTEL_TRACES_SAMPLER_XRAY, }, { name: constants.TraceExporting.OTEL_TRACES_SAMPLER_ARG, value: constants.TraceExporting.OTEL_TRACES_SAMPLER_ARG_LOCAL_CWA, }, { name: constants.TraceExporting.OTEL_PROPAGATORS, value: constants.TraceExporting.OTEL_PROPAGATORS_APPLICATION_SIGNALS, }, ]; protected sharedVolumeName: string; protected instrumentationVersion: inst.InstrumentationVersion; private overrideEnvironments?: EnvironmentExtension[]; public constructor( sharedVolumeName: string, instrumentationVersion: inst.InstrumentationVersion, overrideEnvironments?: EnvironmentExtension[]) { this.sharedVolumeName = sharedVolumeName; this.instrumentationVersion = instrumentationVersion; this.overrideEnvironments = overrideEnvironments; } /** * The command to run the init container. */ abstract get command(): string[]; /** * The path to ADOT SDK agent in the init container. */ abstract get containerPath(): string; /** * Inject additional environment variables to the application container other than the DEFAULT_ENVS. */ protected abstract injectAdditionalEnvironments(envsToInject: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void; /** * Override environment variables in the application container. */ protected abstract overrideAdditionalEnvironments(envsToOverride: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void; /** * Inject ADOT SDK agent init container. * @param taskDefinition The TaskDefinition to render * @returns The created ContainerDefinition */ public injectInitContainer(taskDefinition: ecs.TaskDefinition): ecs.ContainerDefinition { const initContainer = taskDefinition.addContainer('adot-init', { image: ecs.ContainerImage.fromRegistry(this.instrumentationVersion.imageURI()), essential: false, command: this.command, cpu: 0, memoryLimitMiB: this.instrumentationVersion.memoryLimitMiB(), }); initContainer.addMountPoints({ sourceVolume: this.sharedVolumeName, containerPath: this.containerPath, // double check readOnly: false, }); return initContainer; } /** * Render the application container for SDK instrumentation. * @param taskDefinition The TaskDefinition to render */ public renderDefaultContainer(taskDefinition: ecs.TaskDefinition): void { let container = taskDefinition.defaultContainer!; const envsToInject: { [key: string]: string } = {}; const envsFromTaskDef = (container as any).environment; for (const env of Injector.DEFAULT_ENVS) { envsToInject[env.name] = env.value; } this.injectAdditionalEnvironments(envsToInject, envsFromTaskDef); const envsToOverride: { [key: string]: string } = {}; this.overrideAdditionalEnvironments(envsToOverride, envsFromTaskDef); for (const [key, value] of Object.entries(envsToInject)) { if (!envsFromTaskDef[key]) { container.addEnvironment(key, value); } } for (const [key, value] of Object.entries(envsToOverride)) { container.addEnvironment(key, value); } for (const env of this.overrideEnvironments ?? []) { container.addEnvironment(env.name, env.value); } if (!envsFromTaskDef[constants.CommonExporting.OTEL_SERVICE_NAME]) { let resourceAttributesVal = (envsFromTaskDef[constants.CommonExporting.OTEL_RESOURCE_ATTRIBUTES] || '') as string; if (resourceAttributesVal.indexOf('service.name') < 0) { // Configure service.name to be task definition family name if undefined. container.addEnvironment(constants.CommonExporting.OTEL_SERVICE_NAME, taskDefinition.family); } } container.addMountPoints({ sourceVolume: this.sharedVolumeName, containerPath: this.containerPath, // double check readOnly: false, }); } } /** * Java-specific implementation of the SDK injector. * Handles Java agent configuration and environment setup. */ export class JavaInjector extends Injector { get command(): string[] { return ['cp', '/javaagent.jar', `${this.containerPath}/javaagent.jar`]; } get containerPath(): string { return '/otel-auto-instrumentation'; } protected injectAdditionalEnvironments(envsToInject: { [key: string]: string }, _envsFromTaskDef: { [key: string]: string }): void { envsToInject[constants.JavaInstrumentation.JAVA_TOOL_OPTIONS] = ` -javaagent:${this.containerPath}/javaagent.jar`; } protected overrideAdditionalEnvironments(_envsToOverride: { [key: string]: string }, _overrideEnvironments: { [key: string]: string }): void { // No additional overrides needed for Java } } /** * Python-specific implementation of the SDK injector. * Handles Python auto-instrumentation setup and PYTHONPATH configuration. */ export class PythonInjector extends Injector { protected static readonly PYTHON_ENVS: EnvironmentExtension[] = [ { name: constants.PythonInstrumentation.OTEL_PYTHON_DISTRO, value: constants.PythonInstrumentation.OTEL_PYTHON_DISTRO_AWS_DISTRO, }, { name: constants.PythonInstrumentation.OTEL_PYTHON_CONFIGURATOR, value: constants.PythonInstrumentation.OTEL_PYTHON_CONFIGURATOR_AWS_CONFIGURATOR, }, ]; get command(): string[] { return ['cp', '-a', '/autoinstrumentation/.', this.containerPath]; } protected injectAdditionalEnvironments(envsToInject: { [key: string]: string }, _envsFromTaskDef: { [key: string]: string }): void { for (const env of PythonInjector.PYTHON_ENVS) { envsToInject[env.name] = env.value; } envsToInject[constants.PythonInstrumentation.PYTHONPATH] = `${this.containerPath}/opentelemetry/instrumentation/auto_instrumentation:${this.containerPath}`; } get containerPath(): string { return '/otel-auto-instrumentation-python'; } protected overrideAdditionalEnvironments(envsToOverride: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void { if (envsFromTaskDef[constants.PythonInstrumentation.PYTHONPATH]) { const pythonPath = envsFromTaskDef[constants.PythonInstrumentation.PYTHONPATH]; envsToOverride[constants.PythonInstrumentation.PYTHONPATH] = `${this.containerPath}/opentelemetry/instrumentation/auto_instrumentation:${pythonPath}:${this.containerPath}`; } } } /** * Base class for .NET SDK injectors. * Contains common .NET configuration settings used by both Windows and Linux implementations. */ export abstract class DotNetInjector extends Injector { protected static readonly DOTNET_COMMON_ENVS: EnvironmentExtension[] = [ { name: constants.DotnetInstrumentation.OTEL_DOTNET_DISTRO, value: constants.DotnetInstrumentation.OTEL_DOTNET_DISTRO_AWS_DISTRO, }, { name: constants.DotnetInstrumentation.OTEL_DOTNET_CONFIGURATOR, value: constants.DotnetInstrumentation.OTEL_DOTNET_CONFIGURATOR_AWS_CONFIGURATOR, }, { name: constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_PLUGINS, value: constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_PLUGINS_ADOT, }, ]; } /** * Linux-specific implementation of the .NET SDK injector. * Handles CoreCLR profiler setup and paths for Linux environments. */ export class DotNetLinuxInjector extends DotNetInjector { protected static readonly DOTNET_LINUX_ENVS: EnvironmentExtension[] = [ { name: constants.DotnetInstrumentation.CORECLR_ENABLE_PROFILING, value: constants.DotnetInstrumentation.CORECLR_ENABLE_PROFILING_ENABLED, }, { name: constants.DotnetInstrumentation.CORECLR_PROFILER, value: constants.DotnetInstrumentation.CORECLR_PROFILER_OTEL, }, ]; private cpuArch: ecs.CpuArchitecture; constructor( sharedVolumeName: string, instrumentationVersion: inst.InstrumentationVersion, cpuArch: ecs.CpuArchitecture, overrideEnvironments?: EnvironmentExtension[]) { super(sharedVolumeName, instrumentationVersion, overrideEnvironments); this.cpuArch = cpuArch; } get command(): string[] { return ['cp', '-a', '/autoinstrumentation/.', this.containerPath]; } protected injectAdditionalEnvironments(envsToInject: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void { if (envsFromTaskDef[constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_HOME]) { // If OTEL_DOTNET_AUTO_HOME env var is already set, we will assume that .NET Auto-instrumentation is already configured. return; } for (const env of DotNetInjector.DOTNET_COMMON_ENVS) { envsToInject[env.name] = env.value; } for (const env of DotNetLinuxInjector.DOTNET_LINUX_ENVS) { envsToInject[env.name] = env.value; } envsToInject[constants.DotnetInstrumentation.CORECLR_PROFILER_PATH] = this.getCoreCLRProfilerPath(); envsToInject[constants.DotnetInstrumentation.DOTNET_STARTUP_HOOKS] = `${this.containerPath}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll`; envsToInject[constants.DotnetInstrumentation.DOTNET_ADDITIONAL_DEPS] = `${this.containerPath}/AdditionalDeps`; envsToInject[constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_HOME] = `${this.containerPath}`; envsToInject[constants.DotnetInstrumentation.DOTNET_SHARED_STORE] = `${this.containerPath}/store`; } get containerPath(): string { return '/otel-auto-instrumentation-dotnet'; } protected overrideAdditionalEnvironments(_envsToOverride: { [key: string]: string }, _envsFromTaskDef: { [key: string]: string }): void { // No additional overrides needed for .NET on Linux } private getCoreCLRProfilerPath() { const subPath = this.cpuArch == ecs.CpuArchitecture.ARM64 ? 'linux-arm64': 'linux-x64'; return `${this.containerPath}/${subPath}/OpenTelemetry.AutoInstrumentation.Native.so`; } } /** * Windows-specific implementation of the .NET SDK injector. * Handles CoreCLR profiler setup and paths for Windows environments. */ export class DotNetWindowsInjector extends DotNetInjector { protected static readonly DOTNET_WINDOWS_ENVS: EnvironmentExtension[] = [ { name: constants.DotnetInstrumentation.CORECLR_ENABLE_PROFILING, value: constants.DotnetInstrumentation.CORECLR_ENABLE_PROFILING_ENABLED, }, { name: constants.DotnetInstrumentation.CORECLR_PROFILER, value: constants.DotnetInstrumentation.CORECLR_PROFILER_OTEL, }, ]; get command(): string[] { return ['CMD', '/c', 'xcopy', '/e', 'C:\\autoinstrumentation\\*', 'C:\\otel-auto-instrumentation-dotnet', '&&', 'icacls', 'C:\\otel-auto-instrumentation-dotnet', '/grant', '*S-1-1-0:R', '/T']; } protected injectAdditionalEnvironments(envsToInject: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void { if (envsFromTaskDef[constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_HOME]) { // If OTEL_DOTNET_AUTO_HOME env var is already set, we will assume that .NET Auto-instrumentation is already configured. return; } for (const env of DotNetInjector.DOTNET_COMMON_ENVS) { envsToInject[env.name] = env.value; } for (const env of DotNetWindowsInjector.DOTNET_WINDOWS_ENVS) { envsToInject[env.name] = env.value; } envsToInject[constants.DotnetInstrumentation.CORECLR_PROFILER_PATH] = `${this.containerPath}\\win-x64\\OpenTelemetry.AutoInstrumentation.Native.dll`; envsToInject[constants.DotnetInstrumentation.DOTNET_STARTUP_HOOKS] = `${this.containerPath}\\net\\OpenTelemetry.AutoInstrumentation.StartupHook.dll`; envsToInject[constants.DotnetInstrumentation.DOTNET_ADDITIONAL_DEPS] = `${this.containerPath}\\AdditionalDeps`; envsToInject[constants.DotnetInstrumentation.OTEL_DOTNET_AUTO_HOME] = `${this.containerPath}`; envsToInject[constants.DotnetInstrumentation.DOTNET_SHARED_STORE] = `${this.containerPath}\\store`; } get containerPath(): string { return 'C:\\otel-auto-instrumentation-dotnet'; } protected overrideAdditionalEnvironments(_envsToOverride: { [key: string]: string }, _envsFromTaskDef: { [key: string]: string }): void { // No additional overrides needed for .NET on Windows } } /** * Node.js-specific implementation of the SDK injector. * Handles Node.js auto-instrumentation setup and NODE_OPTIONS configuration. */ export class NodeInjector extends Injector { get command(): string[] { return ['cp', '-a', '/autoinstrumentation/.', this.containerPath]; } protected injectAdditionalEnvironments(envsToInject: { [key: string]: string }, _envsFromTaskDef: { [key: string]: string }): void { envsToInject[constants.NodeInstrumentation.NODE_OPTIONS] = ` --require ${this.containerPath}/autoinstrumentation.js`; } get containerPath(): string { return '/otel-auto-instrumentation-nodejs'; } protected overrideAdditionalEnvironments(envsToOverride: { [key: string]: string }, envsFromTaskDef: { [key: string]: string }): void { if (envsFromTaskDef[constants.NodeInstrumentation.NODE_OPTIONS]) { const originalNodeOptions = envsFromTaskDef[constants.NodeInstrumentation.NODE_OPTIONS] ?? ''; let renderedNodeOptions = `${originalNodeOptions} --require ${this.containerPath}/autoinstrumentation.js`; envsToOverride[constants.NodeInstrumentation.NODE_OPTIONS] = renderedNodeOptions; } } }