diff --git a/packages/opentelemetry-api/src/api/propagation.ts b/packages/opentelemetry-api/src/api/propagation.ts index a07b88b94d..b99dada22a 100644 --- a/packages/opentelemetry-api/src/api/propagation.ts +++ b/packages/opentelemetry-api/src/api/propagation.ts @@ -96,6 +96,13 @@ export class PropagationAPI { return this._getGlobalPropagator().extract(context, carrier, getter); } + /** + * Return a list of all fields which may be used by the propagator. + */ + public fields(): string[] { + return this._getGlobalPropagator().fields(); + } + /** Remove the global propagator */ public disable() { delete _global[GLOBAL_PROPAGATION_API_KEY]; diff --git a/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts b/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts index 59f2d39f8a..b659a9704d 100644 --- a/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts +++ b/packages/opentelemetry-api/src/context/propagation/TextMapPropagator.ts @@ -67,9 +67,6 @@ export interface TextMapPropagator { /** * Return a list of all fields which may be used by the propagator. - * - * This list should be used to clear fields before calling inject if a carrier is - * used more than once. */ fields(): string[]; } diff --git a/packages/opentelemetry-api/src/trace/ProxyTracer.ts b/packages/opentelemetry-api/src/trace/ProxyTracer.ts index c6e22433e7..591610c3ae 100644 --- a/packages/opentelemetry-api/src/trace/ProxyTracer.ts +++ b/packages/opentelemetry-api/src/trace/ProxyTracer.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Context } from '@opentelemetry/context-base'; import { Span, SpanOptions, Tracer } from '..'; import { NOOP_TRACER } from './NoopTracer'; import { ProxyTracerProvider } from './ProxyTracerProvider'; @@ -31,8 +32,8 @@ export class ProxyTracer implements Tracer { public readonly version?: string ) {} - startSpan(name: string, options?: SpanOptions): Span { - return this._getTracer().startSpan(name, options); + startSpan(name: string, options?: SpanOptions, context?: Context): Span { + return this._getTracer().startSpan(name, options, context); } /** diff --git a/packages/opentelemetry-api/test/api/api.test.ts b/packages/opentelemetry-api/test/api/api.test.ts index 01aaf32df6..cc02a460f8 100644 --- a/packages/opentelemetry-api/test/api/api.test.ts +++ b/packages/opentelemetry-api/test/api/api.test.ts @@ -150,6 +150,13 @@ describe('API', () => { assert.strictEqual(data.carrier, carrier); assert.strictEqual(data.getter, getter); }); + + it('fields', () => { + api.propagation.setGlobalPropagator(new TestTextMapPropagation()); + + const fields = api.propagation.fields(); + assert.deepStrictEqual(fields, ['TestField']); + }); }); }); }); diff --git a/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts b/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts index 41db382f3d..9170f3e44a 100644 --- a/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts +++ b/packages/opentelemetry-api/test/proxy-implementations/proxy-tracer.test.ts @@ -26,15 +26,22 @@ import { Tracer, Span, NoopTracer, + ROOT_CONTEXT, + SpanOptions, } from '../../src'; describe('ProxyTracer', () => { let provider: ProxyTracerProvider; + const sandbox = sinon.createSandbox(); beforeEach(() => { provider = new ProxyTracerProvider(); }); + afterEach(() => { + sandbox.restore(); + }); + describe('when no delegate is set', () => { it('should return proxy tracers', () => { const tracer = provider.getTracer('test'); @@ -61,7 +68,6 @@ describe('ProxyTracer', () => { describe('when delegate is set before getTracer', () => { let delegate: TracerProvider; - const sandbox = sinon.createSandbox(); let getTracerStub: sinon.SinonStub; beforeEach(() => { @@ -72,10 +78,6 @@ describe('ProxyTracer', () => { provider.setDelegate(delegate); }); - afterEach(() => { - sandbox.restore(); - }); - it('should return tracers directly from the delegate', () => { const tracer = provider.getTracer('test', 'v0'); @@ -114,5 +116,25 @@ describe('ProxyTracer', () => { assert.strictEqual(span, delegateSpan); }); + + it('should pass original arguments to DelegateTracer#startSpan', () => { + const startSpanStub = sandbox.stub(delegateTracer, 'startSpan'); + + const name = 'name1'; + const options: SpanOptions = {}; + const ctx = ROOT_CONTEXT.setValue(Symbol('test'), 1); + tracer.startSpan(name, options, ctx); + + // Assert the proxy tracer has the full API of the NoopTracer + assert.strictEqual( + NoopTracer.prototype.startSpan.length, + ProxyTracer.prototype.startSpan.length + ); + assert.deepStrictEqual(Object.getOwnPropertyNames(NoopTracer.prototype), [ + 'constructor', + 'startSpan', + ]); + sandbox.assert.calledOnceWithExactly(startSpanStub, name, options, ctx); + }); }); }); diff --git a/packages/opentelemetry-core/src/platform/browser/environment.ts b/packages/opentelemetry-core/src/platform/browser/environment.ts index e1671f40c0..ea9514e4ac 100644 --- a/packages/opentelemetry-core/src/platform/browser/environment.ts +++ b/packages/opentelemetry-core/src/platform/browser/environment.ts @@ -17,7 +17,7 @@ import { DEFAULT_ENVIRONMENT, ENVIRONMENT, - ENVIRONMENT_MAP, + RAW_ENVIRONMENT, parseEnvironment, } from '../../utils/environment'; @@ -25,7 +25,7 @@ import { * Gets the environment variables */ export function getEnv(): Required { - const _window = window as typeof window & ENVIRONMENT_MAP; + const _window = window as typeof window & RAW_ENVIRONMENT; const globalEnv = parseEnvironment(_window); return Object.assign({}, DEFAULT_ENVIRONMENT, globalEnv); } diff --git a/packages/opentelemetry-core/src/platform/node/environment.ts b/packages/opentelemetry-core/src/platform/node/environment.ts index db35828e6b..749633f08b 100644 --- a/packages/opentelemetry-core/src/platform/node/environment.ts +++ b/packages/opentelemetry-core/src/platform/node/environment.ts @@ -14,10 +14,11 @@ * limitations under the License. */ +import * as os from 'os'; import { DEFAULT_ENVIRONMENT, ENVIRONMENT, - ENVIRONMENT_MAP, + RAW_ENVIRONMENT, parseEnvironment, } from '../../utils/environment'; @@ -25,6 +26,12 @@ import { * Gets the environment variables */ export function getEnv(): Required { - const processEnv = parseEnvironment(process.env as ENVIRONMENT_MAP); - return Object.assign({}, DEFAULT_ENVIRONMENT, processEnv); + const processEnv = parseEnvironment(process.env as RAW_ENVIRONMENT); + return Object.assign( + { + HOSTNAME: os.hostname(), + }, + DEFAULT_ENVIRONMENT, + processEnv + ); } diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 0e5727966e..2088772ca2 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -16,37 +16,78 @@ import { LogLevel } from '../common/types'; -export type ENVIRONMENT_MAP = { [key: string]: string | number }; +const DEFAULT_LIST_SEPARATOR = ','; /** * Environment interface to define all names */ -export interface ENVIRONMENT { - OTEL_LOG_LEVEL?: LogLevel; - OTEL_NO_PATCH_MODULES?: string; - OTEL_SAMPLING_PROBABILITY?: number; - OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT?: number; - OTEL_SPAN_EVENT_COUNT_LIMIT?: number; - OTEL_SPAN_LINK_COUNT_LIMIT?: number; - OTEL_BSP_MAX_BATCH_SIZE?: number; - OTEL_BSP_SCHEDULE_DELAY_MILLIS?: number; -} -const ENVIRONMENT_NUMBERS: Partial[] = [ +const ENVIRONMENT_NUMBERS_KEYS = [ + 'OTEL_BSP_MAX_BATCH_SIZE', + 'OTEL_BSP_SCHEDULE_DELAY_MILLIS', 'OTEL_SAMPLING_PROBABILITY', 'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT', 'OTEL_SPAN_EVENT_COUNT_LIMIT', 'OTEL_SPAN_LINK_COUNT_LIMIT', - 'OTEL_BSP_MAX_BATCH_SIZE', - 'OTEL_BSP_SCHEDULE_DELAY_MILLIS', -]; +] as const; + +type ENVIRONMENT_NUMBERS = { + [K in typeof ENVIRONMENT_NUMBERS_KEYS[number]]?: number; +}; + +function isEnvVarANumber(key: unknown): key is keyof ENVIRONMENT_NUMBERS { + return ( + ENVIRONMENT_NUMBERS_KEYS.indexOf(key as keyof ENVIRONMENT_NUMBERS) > -1 + ); +} + +const ENVIRONMENT_LISTS_KEYS = ['OTEL_NO_PATCH_MODULES'] as const; + +type ENVIRONMENT_LISTS = { + [K in typeof ENVIRONMENT_LISTS_KEYS[number]]?: string[]; +}; + +function isEnvVarAList(key: unknown): key is keyof ENVIRONMENT_LISTS { + return ENVIRONMENT_LISTS_KEYS.indexOf(key as keyof ENVIRONMENT_LISTS) > -1; +} + +export type ENVIRONMENT = { + CONTAINER_NAME?: string; + ECS_CONTAINER_METADATA_URI_V4?: string; + ECS_CONTAINER_METADATA_URI?: string; + HOSTNAME?: string; + KUBERNETES_SERVICE_HOST?: string; + NAMESPACE?: string; + OTEL_EXPORTER_JAEGER_AGENT_HOST?: string; + OTEL_EXPORTER_JAEGER_ENDPOINT?: string; + OTEL_EXPORTER_JAEGER_PASSWORD?: string; + OTEL_EXPORTER_JAEGER_USER?: string; + OTEL_LOG_LEVEL?: LogLevel; + OTEL_RESOURCE_ATTRIBUTES?: string; +} & ENVIRONMENT_NUMBERS & + ENVIRONMENT_LISTS; + +export type RAW_ENVIRONMENT = { + [key: string]: string | number | undefined | string[]; +}; /** * Default environment variables */ export const DEFAULT_ENVIRONMENT: Required = { - OTEL_NO_PATCH_MODULES: '', + CONTAINER_NAME: '', + ECS_CONTAINER_METADATA_URI_V4: '', + ECS_CONTAINER_METADATA_URI: '', + HOSTNAME: '', + KUBERNETES_SERVICE_HOST: '', + NAMESPACE: '', + OTEL_EXPORTER_JAEGER_AGENT_HOST: '', + OTEL_EXPORTER_JAEGER_ENDPOINT: '', + OTEL_EXPORTER_JAEGER_PASSWORD: '', + OTEL_EXPORTER_JAEGER_USER: '', OTEL_LOG_LEVEL: LogLevel.INFO, + OTEL_NO_PATCH_MODULES: [], + OTEL_RESOURCE_ATTRIBUTES: '', OTEL_SAMPLING_PROBABILITY: 1, OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 1000, OTEL_SPAN_EVENT_COUNT_LIMIT: 1000, @@ -64,15 +105,14 @@ export const DEFAULT_ENVIRONMENT: Required = { * @param max */ function parseNumber( - name: keyof ENVIRONMENT, - environment: ENVIRONMENT_MAP | ENVIRONMENT, - values: ENVIRONMENT_MAP, + name: keyof ENVIRONMENT_NUMBERS, + environment: ENVIRONMENT, + values: RAW_ENVIRONMENT, min = -Infinity, max = Infinity ) { if (typeof values[name] !== 'undefined') { const value = Number(values[name] as string); - if (!isNaN(value)) { if (value < min) { environment[name] = min; @@ -85,6 +125,25 @@ function parseNumber( } } +/** + * Parses list-like strings from input into output. + * @param name + * @param environment + * @param values + * @param separator + */ +function parseStringList( + name: keyof ENVIRONMENT_LISTS, + output: ENVIRONMENT, + input: RAW_ENVIRONMENT, + separator = DEFAULT_LIST_SEPARATOR +) { + const givenValue = input[name]; + if (typeof givenValue === 'string') { + output[name] = givenValue.split(separator).map(v => v.trim()); + } +} + /** * Environmentally sets log level if valid log level string is provided * @param key @@ -93,8 +152,8 @@ function parseNumber( */ function setLogLevelFromEnv( key: keyof ENVIRONMENT, - environment: ENVIRONMENT_MAP | ENVIRONMENT, - values: ENVIRONMENT_MAP + environment: RAW_ENVIRONMENT | ENVIRONMENT, + values: RAW_ENVIRONMENT ) { const value = values[key]; switch (typeof value === 'string' ? value.toUpperCase() : value) { @@ -124,11 +183,12 @@ function setLogLevelFromEnv( * Parses environment values * @param values */ -export function parseEnvironment(values: ENVIRONMENT_MAP): ENVIRONMENT { - const environment: ENVIRONMENT_MAP = {}; +export function parseEnvironment(values: RAW_ENVIRONMENT): ENVIRONMENT { + const environment: ENVIRONMENT = {}; for (const env in DEFAULT_ENVIRONMENT) { const key = env as keyof ENVIRONMENT; + switch (key) { case 'OTEL_SAMPLING_PROBABILITY': parseNumber(key, environment, values, 0, 1); @@ -139,11 +199,14 @@ export function parseEnvironment(values: ENVIRONMENT_MAP): ENVIRONMENT { break; default: - if (ENVIRONMENT_NUMBERS.indexOf(key) >= 0) { + if (isEnvVarANumber(key)) { parseNumber(key, environment, values); + } else if (isEnvVarAList(key)) { + parseStringList(key, environment, values); } else { - if (typeof values[key] !== 'undefined') { - environment[key] = values[key]; + const value = values[key]; + if (typeof value !== 'undefined' && value !== null) { + environment[key] = String(value); } } } diff --git a/packages/opentelemetry-core/test/utils/environment.test.ts b/packages/opentelemetry-core/test/utils/environment.test.ts index 50d4bdb588..35c9bdcb5a 100644 --- a/packages/opentelemetry-core/test/utils/environment.test.ts +++ b/packages/opentelemetry-core/test/utils/environment.test.ts @@ -18,18 +18,18 @@ import { getEnv } from '../../src/platform'; import { DEFAULT_ENVIRONMENT, ENVIRONMENT, - ENVIRONMENT_MAP, + RAW_ENVIRONMENT, } from '../../src/utils/environment'; import * as assert from 'assert'; import * as sinon from 'sinon'; import { LogLevel } from '../../src'; -let lastMock: ENVIRONMENT_MAP = {}; +let lastMock: RAW_ENVIRONMENT = {}; /** * Mocks environment used for tests. */ -export function mockEnvironment(values: ENVIRONMENT_MAP) { +export function mockEnvironment(values: RAW_ENVIRONMENT) { lastMock = values; if (typeof process !== 'undefined') { Object.keys(values).forEach(key => { @@ -37,7 +37,7 @@ export function mockEnvironment(values: ENVIRONMENT_MAP) { }); } else { Object.keys(values).forEach(key => { - ((window as unknown) as ENVIRONMENT_MAP)[key] = String(values[key]); + ((window as unknown) as RAW_ENVIRONMENT)[key] = String(values[key]); }); } } @@ -52,7 +52,7 @@ export function removeMockEnvironment() { }); } else { Object.keys(lastMock).forEach(key => { - delete ((window as unknown) as ENVIRONMENT_MAP)[key]; + delete ((window as unknown) as RAW_ENVIRONMENT)[key]; }); } lastMock = {}; @@ -73,23 +73,54 @@ describe('environment', () => { describe('parseEnvironment', () => { it('should parse environment variables', () => { mockEnvironment({ + CONTAINER_NAME: 'container-1', + ECS_CONTAINER_METADATA_URI_V4: 'https://ecs.uri/v4', + ECS_CONTAINER_METADATA_URI: 'https://ecs.uri/', FOO: '1', - OTEL_NO_PATCH_MODULES: 'a,b,c', + HOSTNAME: 'hostname', + KUBERNETES_SERVICE_HOST: 'https://k8s.host/', + NAMESPACE: 'namespace', + OTEL_BSP_MAX_BATCH_SIZE: 40, + OTEL_BSP_SCHEDULE_DELAY_MILLIS: 50, + OTEL_EXPORTER_JAEGER_AGENT_HOST: 'host.domain.com', + OTEL_EXPORTER_JAEGER_ENDPOINT: 'https://example.com/endpoint', + OTEL_EXPORTER_JAEGER_PASSWORD: 'secret', + OTEL_EXPORTER_JAEGER_USER: 'whoami', OTEL_LOG_LEVEL: 'ERROR', + OTEL_NO_PATCH_MODULES: 'a,b,c', + OTEL_RESOURCE_ATTRIBUTES: '', OTEL_SAMPLING_PROBABILITY: '0.5', OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 10, OTEL_SPAN_EVENT_COUNT_LIMIT: 20, OTEL_SPAN_LINK_COUNT_LIMIT: 30, - OTEL_BSP_MAX_BATCH_SIZE: 40, - OTEL_BSP_SCHEDULE_DELAY_MILLIS: 50, }); const env = getEnv(); - assert.strictEqual(env.OTEL_NO_PATCH_MODULES, 'a,b,c'); + assert.deepStrictEqual(env.OTEL_NO_PATCH_MODULES, ['a', 'b', 'c']); assert.strictEqual(env.OTEL_LOG_LEVEL, LogLevel.ERROR); assert.strictEqual(env.OTEL_SAMPLING_PROBABILITY, 0.5); assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, 10); assert.strictEqual(env.OTEL_SPAN_EVENT_COUNT_LIMIT, 20); assert.strictEqual(env.OTEL_SPAN_LINK_COUNT_LIMIT, 30); + assert.strictEqual( + env.OTEL_EXPORTER_JAEGER_ENDPOINT, + 'https://example.com/endpoint' + ); + assert.strictEqual(env.OTEL_EXPORTER_JAEGER_USER, 'whoami'); + assert.strictEqual(env.OTEL_EXPORTER_JAEGER_PASSWORD, 'secret'); + assert.strictEqual( + env.OTEL_EXPORTER_JAEGER_AGENT_HOST, + 'host.domain.com' + ); + assert.strictEqual( + env.ECS_CONTAINER_METADATA_URI_V4, + 'https://ecs.uri/v4' + ); + assert.strictEqual(env.ECS_CONTAINER_METADATA_URI, 'https://ecs.uri/'); + assert.strictEqual(env.NAMESPACE, 'namespace'); + assert.strictEqual(env.HOSTNAME, 'hostname'); + assert.strictEqual(env.CONTAINER_NAME, 'container-1'); + assert.strictEqual(env.KUBERNETES_SERVICE_HOST, 'https://k8s.host/'); + assert.strictEqual(env.OTEL_RESOURCE_ATTRIBUTES, ''); assert.strictEqual(env.OTEL_BSP_MAX_BATCH_SIZE, 40); assert.strictEqual(env.OTEL_BSP_SCHEDULE_DELAY_MILLIS, 50); }); diff --git a/packages/opentelemetry-exporter-jaeger/README.md b/packages/opentelemetry-exporter-jaeger/README.md index a00da2606c..532392fbb0 100644 --- a/packages/opentelemetry-exporter-jaeger/README.md +++ b/packages/opentelemetry-exporter-jaeger/README.md @@ -56,7 +56,7 @@ npm install --save @opentelemetry/exporter-jaeger Install the exporter on your application and pass the options, it must contain a service name. Furthermore, the `host` option (which defaults to `localhost`), can instead be set by the -`JAEGER_AGENT_HOST` environment variable to reduce in-code config. If both are +`OTEL_EXPORTER_JAEGER_AGENT_HOST` environment variable to reduce in-code config. If both are set, the value set by the option in code is authoritative. ```js diff --git a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts index 8ae8890b63..4579e1a4b1 100644 --- a/packages/opentelemetry-exporter-jaeger/src/jaeger.ts +++ b/packages/opentelemetry-exporter-jaeger/src/jaeger.ts @@ -15,7 +15,7 @@ */ import * as api from '@opentelemetry/api'; -import { ExportResult, ExportResultCode } from '@opentelemetry/core'; +import { ExportResult, ExportResultCode, getEnv } from '@opentelemetry/core'; import { ReadableSpan, SpanExporter } from '@opentelemetry/tracing'; import { Socket } from 'dgram'; import { spanToThrift } from './transform'; @@ -43,14 +43,19 @@ export class JaegerExporter implements SpanExporter { : 2000; // https://github.com/jaegertracing/jaeger-client-node#environment-variables - // By default, the client sends traces via UDP to the agent at localhost:6832. Use JAEGER_AGENT_HOST and - // JAEGER_AGENT_PORT to send UDP traces to a different host:port. If JAEGER_ENDPOINT is set, the client sends traces - // to the endpoint via HTTP, making the JAEGER_AGENT_HOST and JAEGER_AGENT_PORT unused. If JAEGER_ENDPOINT is secured, - // HTTP basic authentication can be performed by setting the JAEGER_USER and JAEGER_PASSWORD environment variables. - localConfig.endpoint = localConfig.endpoint || process.env.JAEGER_ENDPOINT; - localConfig.username = localConfig.username || process.env.JAEGER_USER; - localConfig.password = localConfig.password || process.env.JAEGER_PASSWORD; - localConfig.host = localConfig.host || process.env.JAEGER_AGENT_HOST; + // By default, the client sends traces via UDP to the agent at localhost:6832. Use OTEL_EXPORTER_JAEGER_AGENT_HOST and + // JAEGER_AGENT_PORT to send UDP traces to a different host:port. If OTEL_EXPORTER_JAEGER_ENDPOINT is set, the client sends traces + // to the endpoint via HTTP, making the OTEL_EXPORTER_JAEGER_AGENT_HOST and JAEGER_AGENT_PORT unused. If OTEL_EXPORTER_JAEGER_ENDPOINT is secured, + // HTTP basic authentication can be performed by setting the OTEL_EXPORTER_JAEGER_USER and OTEL_EXPORTER_JAEGER_PASSWORD environment variables. + + const env = getEnv(); + localConfig.endpoint = + localConfig.endpoint || env.OTEL_EXPORTER_JAEGER_ENDPOINT; + localConfig.username = + localConfig.username || env.OTEL_EXPORTER_JAEGER_USER; + localConfig.password = + localConfig.password || env.OTEL_EXPORTER_JAEGER_PASSWORD; + localConfig.host = localConfig.host || env.OTEL_EXPORTER_JAEGER_AGENT_HOST; if (localConfig.endpoint) { this._sender = new jaegerTypes.HTTPSender(localConfig); } else { diff --git a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts index 122759e018..d5c2275c42 100644 --- a/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts +++ b/packages/opentelemetry-exporter-jaeger/test/jaeger.test.ts @@ -27,7 +27,7 @@ import * as nock from 'nock'; describe('JaegerExporter', () => { describe('constructor', () => { afterEach(() => { - delete process.env.JAEGER_AGENT_HOST; + delete process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST; }); it('should construct an exporter', () => { @@ -67,7 +67,7 @@ describe('JaegerExporter', () => { }); it('should respect jaeger host env variable', () => { - process.env.JAEGER_AGENT_HOST = 'env-set-host'; + process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST = 'env-set-host'; const exporter = new JaegerExporter({ serviceName: 'test-service', }); @@ -75,7 +75,7 @@ describe('JaegerExporter', () => { }); it('should prioritize host option over env variable', () => { - process.env.JAEGER_AGENT_HOST = 'env-set-host'; + process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST = 'env-set-host'; const exporter = new JaegerExporter({ serviceName: 'test-service', host: 'option-set-host', diff --git a/packages/opentelemetry-instrumentation/src/utils.ts b/packages/opentelemetry-instrumentation/src/utils.ts index d4406e3a85..8f1c98c1fa 100644 --- a/packages/opentelemetry-instrumentation/src/utils.ts +++ b/packages/opentelemetry-instrumentation/src/utils.ts @@ -43,6 +43,32 @@ export function safeExecuteInTheMiddle( } } +/** + * Async function to execute patched function and being able to catch errors + * @param execute - function to be executed + * @param onFinish - callback to run when execute finishes + */ +export async function safeExecuteInTheMiddleAsync( + execute: () => T, + onFinish: (e: Error | undefined, result: T | undefined) => void, + preventThrowingError?: boolean +): Promise { + let error: Error | undefined; + let result: T | undefined; + try { + result = await execute(); + } catch (e) { + error = e; + } finally { + onFinish(error, result); + if (error && !preventThrowingError) { + // eslint-disable-next-line no-unsafe-finally + throw error; + } + // eslint-disable-next-line no-unsafe-finally + return result as T; + } +} /** * Checks if certain function has been already wrapped * @param func diff --git a/packages/opentelemetry-instrumentation/test/common/utils.test.ts b/packages/opentelemetry-instrumentation/test/common/utils.test.ts index 805490ea15..cfc10fb38f 100644 --- a/packages/opentelemetry-instrumentation/test/common/utils.test.ts +++ b/packages/opentelemetry-instrumentation/test/common/utils.test.ts @@ -15,7 +15,11 @@ */ import * as assert from 'assert'; -import { isWrapped, safeExecuteInTheMiddle } from '../../src'; +import { + isWrapped, + safeExecuteInTheMiddle, + safeExecuteInTheMiddleAsync, +} from '../../src'; describe('isWrapped', () => { describe('when function is wrapped', () => { @@ -45,13 +49,12 @@ describe('isWrapped', () => { describe('safeExecuteInTheMiddle', () => { it('should not throw error', () => { - const error = new Error('test'); safeExecuteInTheMiddle( () => { - throw error; + return 'foo'; }, err => { - assert.deepStrictEqual(error, err); + assert.deepStrictEqual(err, undefined); }, true ); @@ -84,3 +87,47 @@ describe('safeExecuteInTheMiddle', () => { assert.deepStrictEqual(result, 1); }); }); + +describe('safeExecuteInTheMiddleAsync', () => { + it('should not throw error', () => { + safeExecuteInTheMiddleAsync( + async () => { + await setTimeout(() => {}, 1); + return 'foo'; + }, + err => { + assert.deepStrictEqual(err, undefined); + }, + true + ); + }); + it('should throw error', () => { + const error = new Error('test'); + try { + safeExecuteInTheMiddleAsync( + async () => { + await setTimeout(() => {}, 1); + throw error; + }, + err => { + assert.deepStrictEqual(error, err); + } + ); + } catch (err) { + assert.deepStrictEqual(error, err); + } + }); + it('should return result', async () => { + const result = await safeExecuteInTheMiddleAsync( + async () => { + await setTimeout(() => {}, 1); + return 1; + }, + (err, result) => { + assert.deepStrictEqual(err, undefined); + assert.deepStrictEqual(result, 1); + } + ); + assert.deepStrictEqual(result, 1); + }); +}); diff --git a/packages/opentelemetry-node/src/instrumentation/PluginLoader.ts b/packages/opentelemetry-node/src/instrumentation/PluginLoader.ts index 00378e66c8..618cff0918 100644 --- a/packages/opentelemetry-node/src/instrumentation/PluginLoader.ts +++ b/packages/opentelemetry-node/src/instrumentation/PluginLoader.ts @@ -16,6 +16,7 @@ import { Logger, TracerProvider } from '@opentelemetry/api'; import { Plugin, PluginConfig } from '@opentelemetry/core'; +import { getEnv } from '@opentelemetry/core'; import * as hook from 'require-in-the-middle'; import * as utils from './utils'; @@ -26,12 +27,6 @@ export enum HookState { DISABLED, } -/** - * Environment variable which will contain list of modules to not load corresponding plugins for - * e.g.OTEL_NO_PATCH_MODULES=pg,https,mongodb - */ -export const ENV_PLUGIN_DISABLED_LIST = 'OTEL_NO_PATCH_MODULES'; - /** * Wildcard symbol. If ignore list is set to this, disable all plugins */ @@ -53,18 +48,6 @@ function filterPlugins(plugins: Plugins): Plugins { }, {}); } -/** - * Parse process.env[ENV_PLUGIN_DISABLED_LIST] for a list of modules - * not to load corresponding plugins for. - */ -function getIgnoreList(): string[] | typeof DISABLE_ALL_PLUGINS { - const envIgnoreList: string = process.env[ENV_PLUGIN_DISABLED_LIST] || ''; - if (envIgnoreList === DISABLE_ALL_PLUGINS) { - return envIgnoreList; - } - return envIgnoreList.split(',').map(v => v.trim()); -} - /** * The PluginLoader class can load instrumentation plugins that use a patch * mechanism to enable automatic tracing for specific target modules. @@ -93,7 +76,7 @@ export class PluginLoader { if (this._hookState === HookState.UNINITIALIZED) { const pluginsToLoad = filterPlugins(plugins); const modulesToHook = Object.keys(pluginsToLoad); - const modulesToIgnore = getIgnoreList(); + const modulesToIgnore = getEnv().OTEL_NO_PATCH_MODULES; // Do not hook require when no module is provided. In this case it is // not necessary. With skipping this step we lower our footprint in // customer applications and require-in-the-middle won't show up in CPU @@ -137,16 +120,20 @@ export class PluginLoader { } // Skip loading of all modules if '*' is provided - if (modulesToIgnore === DISABLE_ALL_PLUGINS) { + if (modulesToIgnore[0] === DISABLE_ALL_PLUGINS) { this.logger.info( - `PluginLoader#load: skipped patching module ${name} because all plugins are disabled (${ENV_PLUGIN_DISABLED_LIST})` + `PluginLoader#load: skipped patching module ${name} because all plugins are disabled (${modulesToIgnore.join( + ',' + )})` ); return exports; } if (modulesToIgnore.includes(name)) { this.logger.info( - `PluginLoader#load: skipped patching module ${name} because it was on the ignore list (${ENV_PLUGIN_DISABLED_LIST})` + `PluginLoader#load: skipped patching module ${name} because it was on the ignore list (${modulesToIgnore.join( + ',' + )})` ); return exports; } diff --git a/packages/opentelemetry-node/test/instrumentation/PluginLoader.test.ts b/packages/opentelemetry-node/test/instrumentation/PluginLoader.test.ts index 8594b1c8b4..3efef3f0f9 100644 --- a/packages/opentelemetry-node/test/instrumentation/PluginLoader.test.ts +++ b/packages/opentelemetry-node/test/instrumentation/PluginLoader.test.ts @@ -22,7 +22,6 @@ import { PluginLoader, Plugins, searchPathForTest, - ENV_PLUGIN_DISABLED_LIST, } from '../../src/instrumentation/PluginLoader'; const INSTALLED_PLUGINS_PATH = path.join(__dirname, 'node_modules'); @@ -136,7 +135,7 @@ describe('PluginLoader', () => { describe('.load()', () => { afterEach(() => { - delete process.env[ENV_PLUGIN_DISABLED_LIST]; + delete process.env['OTEL_NO_PATCH_MODULES']; }); it('sanity check', () => { @@ -158,7 +157,7 @@ describe('PluginLoader', () => { it('should not load a plugin on the ignore list environment variable', () => { // Set ignore list env var - process.env[ENV_PLUGIN_DISABLED_LIST] = 'simple-module'; + process.env['OTEL_NO_PATCH_MODULES'] = 'simple-module'; const pluginLoader = new PluginLoader(provider, logger); pluginLoader.load({ ...simplePlugins, ...supportedVersionPlugins }); @@ -179,7 +178,7 @@ describe('PluginLoader', () => { it('should not load plugins on the ignore list environment variable', () => { // Set ignore list env var - process.env[ENV_PLUGIN_DISABLED_LIST] = 'simple-module,http'; + process.env['OTEL_NO_PATCH_MODULES'] = 'simple-module,http'; const pluginLoader = new PluginLoader(provider, logger); pluginLoader.load({ ...simplePlugins, @@ -208,7 +207,7 @@ describe('PluginLoader', () => { it('should not load any plugins if ignore list environment variable is set to "*"', () => { // Set ignore list env var - process.env[ENV_PLUGIN_DISABLED_LIST] = '*'; + process.env['OTEL_NO_PATCH_MODULES'] = '*'; const pluginLoader = new PluginLoader(provider, logger); pluginLoader.load({ ...simplePlugins, diff --git a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts index 5614f476dd..32088322e4 100644 --- a/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts +++ b/packages/opentelemetry-resource-detector-aws/src/detectors/AwsEcsDetector.ts @@ -23,6 +23,7 @@ import { import * as util from 'util'; import * as fs from 'fs'; import * as os from 'os'; +import { getEnv } from '@opentelemetry/core'; /** * The AwsEcsDetector can be used to detect if a process is running in AWS @@ -35,10 +36,8 @@ export class AwsEcsDetector implements Detector { private static readFileAsync = util.promisify(fs.readFile); async detect(config: ResourceDetectionConfigWithLogger): Promise { - if ( - !process.env.ECS_CONTAINER_METADATA_URI_V4 && - !process.env.ECS_CONTAINER_METADATA_URI - ) { + const env = getEnv(); + if (!env.ECS_CONTAINER_METADATA_URI_V4 && !env.ECS_CONTAINER_METADATA_URI) { config.logger.debug('AwsEcsDetector failed: Process is not on ECS'); return Resource.empty(); } diff --git a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts index 857e40603d..8dd102bb7e 100644 --- a/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts +++ b/packages/opentelemetry-resource-detector-aws/test/detectors/AwsEcsDetector.test.ts @@ -40,7 +40,7 @@ describe('BeanstalkResourceDetector', () => { const multiValidCgroupData = `${unexpectedCgroupdata}\n${correctCgroupData}\nbcd${unexpectedCgroupdata}`; const hostNameData = 'abcd.test.testing.com'; - let readStub, hostStub; + let readStub; let sandbox: sinon.SinonSandbox; beforeEach(() => { @@ -55,7 +55,7 @@ describe('BeanstalkResourceDetector', () => { it('should successfully return resource data', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); @@ -64,7 +64,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -75,7 +74,7 @@ describe('BeanstalkResourceDetector', () => { it('should successfully return resource data with noisy cgroup file', async () => { process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(noisyCgroupData); @@ -84,7 +83,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -95,7 +93,7 @@ describe('BeanstalkResourceDetector', () => { it('should always return first valid line of data', async () => { process.env.ECS_CONTAINER_METADATA_URI = 'ecs_metadata_v3_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(multiValidCgroupData); @@ -104,7 +102,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -113,8 +110,8 @@ describe('BeanstalkResourceDetector', () => { }); }); - it('should empty resource without environmental variable', async () => { - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + it('should empty resource without accessing files', async () => { + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); @@ -123,7 +120,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.notCalled(hostStub); sandbox.assert.notCalled(readStub); assert.ok(resource); assertEmptyResource(resource); @@ -131,7 +127,7 @@ describe('BeanstalkResourceDetector', () => { it('should return resource only with hostname attribute without cgroup file', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .rejects(errorMsg.fileNotFoundError); @@ -140,7 +136,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -150,7 +145,7 @@ describe('BeanstalkResourceDetector', () => { it('should return resource only with hostname attribute when cgroup file does not contain valid container ID', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(hostNameData); + sandbox.stub(os, 'hostname').returns(hostNameData); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(''); @@ -159,7 +154,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -169,7 +163,7 @@ describe('BeanstalkResourceDetector', () => { it('should return resource only with container ID attribute without hostname', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(''); + sandbox.stub(os, 'hostname').returns(''); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .resolves(correctCgroupData); @@ -178,7 +172,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertContainerResource(resource, { @@ -188,7 +181,7 @@ describe('BeanstalkResourceDetector', () => { it('should return empty resource when both hostname and container ID are invalid', async () => { process.env.ECS_CONTAINER_METADATA_URI_V4 = 'ecs_metadata_v4_uri'; - hostStub = sandbox.stub(os, 'hostname').returns(''); + sandbox.stub(os, 'hostname').returns(''); readStub = sandbox .stub(AwsEcsDetector, 'readFileAsync' as any) .rejects(errorMsg.fileNotFoundError); @@ -197,7 +190,6 @@ describe('BeanstalkResourceDetector', () => { logger: new NoopLogger(), }); - sandbox.assert.calledOnce(hostStub); sandbox.assert.calledOnce(readStub); assert.ok(resource); assertEmptyResource(resource); diff --git a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts index ed01accf62..271f8078ec 100644 --- a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts +++ b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import * as os from 'os'; import * as semver from 'semver'; import * as gcpMetadata from 'gcp-metadata'; import { @@ -27,6 +26,7 @@ import { K8S_RESOURCE, CONTAINER_RESOURCE, } from '@opentelemetry/resources'; +import { getEnv } from '@opentelemetry/core'; /** * The GcpDetector can be used to detect if a process is running in the Google @@ -64,7 +64,7 @@ class GcpDetector implements Detector { attributes[CLOUD_RESOURCE.ZONE] = zoneId; attributes[CLOUD_RESOURCE.PROVIDER] = 'gcp'; - if (process.env.KUBERNETES_SERVICE_HOST) + if (getEnv().KUBERNETES_SERVICE_HOST) this._addK8sAttributes(attributes, clusterName); return new Resource(attributes); @@ -75,10 +75,12 @@ class GcpDetector implements Detector { attributes: ResourceAttributes, clusterName: string ): void { + const env = getEnv(); + attributes[K8S_RESOURCE.CLUSTER_NAME] = clusterName; - attributes[K8S_RESOURCE.NAMESPACE_NAME] = process.env.NAMESPACE || ''; - attributes[K8S_RESOURCE.POD_NAME] = process.env.HOSTNAME || os.hostname(); - attributes[CONTAINER_RESOURCE.NAME] = process.env.CONTAINER_NAME || ''; + attributes[K8S_RESOURCE.NAMESPACE_NAME] = env.NAMESPACE; + attributes[K8S_RESOURCE.POD_NAME] = env.HOSTNAME; + attributes[CONTAINER_RESOURCE.NAME] = env.CONTAINER_NAME; } /** Gets project id from GCP project metadata. */ diff --git a/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts b/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts index a448a11d1f..98edcc6752 100644 --- a/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts +++ b/packages/opentelemetry-resources/src/platform/node/detectors/EnvDetector.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { getEnv } from '@opentelemetry/core'; import { Detector, Resource, @@ -54,7 +55,7 @@ class EnvDetector implements Detector { */ async detect(config: ResourceDetectionConfigWithLogger): Promise { try { - const rawAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES; + const rawAttributes = getEnv().OTEL_RESOURCE_ATTRIBUTES; if (!rawAttributes) { config.logger.debug( 'EnvDetector failed: Environment variable "OTEL_RESOURCE_ATTRIBUTES" is missing.'