Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat support otel sdk disabled env #3485

Merged
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ All notable changes to experimental packages in this project will be documented
* feat(instrumentation-grpc): added grpc metadata client side attributes in instrumentation [#3386](https://github.com/open-telemetry/opentelemetry-js/pull/3386)
* feat(instrumentation): add new `_setMeterInstruments` protected method that update the meter instruments every meter provider update.
* feat(api-logs): add the `SeverityNumber` enumeration. [#3443](https://github.com/open-telemetry/opentelemetry-js/pull/3443/) @fuaiyi
* feat(sdk-node): configure no-op sdk with `OTEL_SDK_DISABLED` environment variable [#3485](https://github.com/open-telemetry/opentelemetry-js/pull/3485/files/2211c78aec39aeb6b4b3dae71844edf8ce234d20) @RazGvili

### :bug: (Bug Fix)

Expand Down
4 changes: 4 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ Configure tracing parameters. These are the same trace parameters used to [confi

Configure the [service name](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/semantic_conventions/README.md#service).

## Disable the SDK from the environment

Disable the SDK by setting the `OTEL_SDK_DISABLED` environment variable to `true`.

## Configure Trace Exporter from Environment

This is an alternative to programmatically configuring an exporter or span processor. This package will auto setup the default `otlp` exporter with `http/protobuf` protocol if `traceExporter` or `spanProcessor` hasn't been passed into the `NodeSDK` constructor.
Expand Down
17 changes: 17 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { NodeSDKConfiguration } from './types';
import { TracerProviderWithEnvExporters } from './TracerProviderWithEnvExporter';
import { getEnv } from '@opentelemetry/core';

/** This class represents everything needed to register a fully configured OpenTelemetry Node.js SDK */

Expand Down Expand Up @@ -71,10 +72,18 @@ export class NodeSDK {
private _meterProvider?: MeterProvider;
private _serviceName?: string;

private _disabled?: boolean;

/**
* Create a new NodeJS SDK instance
*/
public constructor(configuration: Partial<NodeSDKConfiguration> = {}) {
if (getEnv().OTEL_SDK_DISABLED) {
this._disabled = true;
// Functions with possible side-effects are set
// to no-op via the _disabled flag
}

this._resource = configuration.resource ?? new Resource({});
this._resourceDetectors = configuration.resourceDetectors ?? [
envDetector,
Expand Down Expand Up @@ -175,6 +184,10 @@ export class NodeSDK {

/** Detect resource attributes */
public async detectResources(): Promise<void> {
if (this._disabled) {
return;
}

const internalConfig: ResourceDetectionConfig = {
detectors: this._resourceDetectors,
};
Expand All @@ -191,6 +204,10 @@ export class NodeSDK {
* Once the SDK has been configured, call this method to construct SDK components and register them with the OpenTelemetry API.
*/
public async start(): Promise<void> {
if (this._disabled) {
return;
}

if (this._autoDetectResources) {
await this.detectResources();
}
Expand Down
72 changes: 72 additions & 0 deletions experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,78 @@ describe('Node SDK', () => {
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
});
});

describe('A disabled SDK should be no-op', () => {
beforeEach(() => {
env.OTEL_SDK_DISABLED = 'true';
});

afterEach(() => {
delete env.OTEL_SDK_DISABLED;
});

it('should not register a trace provider', async () => {
const sdk = new NodeSDK({});
await sdk.start();

assert.strictEqual(
(trace.getTracerProvider() as ProxyTracerProvider).getDelegate(),
delegate,
'sdk.start() should not change the global tracer provider'
);

await sdk.shutdown();
});

it('should not register a meter provider if a reader is provided', async () => {
const exporter = new ConsoleMetricExporter();
const metricReader = new PeriodicExportingMetricReader({
exporter: exporter,
exportIntervalMillis: 100,
exportTimeoutMillis: 100,
});

const sdk = new NodeSDK({
metricReader: metricReader,
autoDetectResources: false,
});
await sdk.start();

assert.ok(!(metrics.getMeterProvider() instanceof MeterProvider));

await sdk.shutdown();
});

describe('detectResources should be no-op', async () => {
beforeEach(() => {
process.env.OTEL_RESOURCE_ATTRIBUTES =
'service.instance.id=627cc493,service.name=my-service,service.namespace=default,service.version=0.0.1';
});

afterEach(() => {
delete process.env.OTEL_RESOURCE_ATTRIBUTES;
});

it('detectResources will not read resources from env or manually', async () => {
const sdk = new NodeSDK({
autoDetectResources: true,
resourceDetectors: [
processDetector,
{
async detect(): Promise<Resource> {
return new Resource({ customAttr: 'someValue' });
},
},
envDetector,
],
});
await sdk.detectResources();
const resource = sdk['_resource'];

assert.deepStrictEqual(resource, Resource.empty());
});
});
});
});

describe('setup exporter from env', () => {
Expand Down
39 changes: 37 additions & 2 deletions packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ const DEFAULT_LIST_SEPARATOR = ',';
* Environment interface to define all names
*/

const ENVIRONMENT_BOOLEAN_KEYS = ['OTEL_SDK_DISABLED'] as const;

type ENVIRONMENT_BOOLEANS = {
[K in typeof ENVIRONMENT_BOOLEAN_KEYS[number]]?: boolean;
};

function isEnvVarABoolean(key: unknown): key is keyof ENVIRONMENT_BOOLEANS {
return (
ENVIRONMENT_BOOLEAN_KEYS.indexOf(key as keyof ENVIRONMENT_BOOLEANS) > -1
);
}

const ENVIRONMENT_NUMBERS_KEYS = [
'OTEL_BSP_EXPORT_TIMEOUT',
'OTEL_BSP_MAX_EXPORT_BATCH_SIZE',
Expand Down Expand Up @@ -107,7 +119,8 @@ export type ENVIRONMENT = {
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?: string;
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?: string;
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE?: string;
} & ENVIRONMENT_NUMBERS &
} & ENVIRONMENT_BOOLEANS &
ENVIRONMENT_NUMBERS &
ENVIRONMENT_LISTS;

export type RAW_ENVIRONMENT = {
Expand All @@ -122,6 +135,7 @@ export const DEFAULT_ATTRIBUTE_COUNT_LIMIT = 128;
* Default environment variables
*/
export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_SDK_DISABLED: false,
CONTAINER_NAME: '',
ECS_CONTAINER_METADATA_URI_V4: '',
ECS_CONTAINER_METADATA_URI: '',
Expand Down Expand Up @@ -182,6 +196,25 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: 'cumulative',
};

/**
* @param key
* @param environment
* @param values
*/
function parseBoolean(
key: keyof ENVIRONMENT_BOOLEANS,
environment: ENVIRONMENT,
values: RAW_ENVIRONMENT
) {
if (typeof values[key] === 'undefined') {
return;
}

const value = String(values[key]);
// support case-insensitive "true"
environment[key] = value.toLowerCase() === 'true';
}

/**
* Parses a variable as number with number validation
* @param name
Expand Down Expand Up @@ -277,7 +310,9 @@ export function parseEnvironment(values: RAW_ENVIRONMENT): ENVIRONMENT {
break;

default:
if (isEnvVarANumber(key)) {
if (isEnvVarABoolean(key)) {
parseBoolean(key, environment, values);
} else if (isEnvVarANumber(key)) {
parseNumber(key, environment, values);
} else if (isEnvVarAList(key)) {
parseStringList(key, environment, values);
Expand Down
23 changes: 23 additions & 0 deletions packages/opentelemetry-core/test/utils/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('environment', () => {
HOSTNAME: 'hostname',
KUBERNETES_SERVICE_HOST: 'https://k8s.host/',
NAMESPACE: 'namespace',
OTEL_SDK_DISABLED: 'true',
OTEL_BSP_MAX_EXPORT_BATCH_SIZE: 40,
OTEL_BSP_SCHEDULE_DELAY: 50,
OTEL_EXPORTER_JAEGER_AGENT_HOST: 'host.domain.com',
Expand All @@ -98,6 +99,7 @@ describe('environment', () => {
});
const env = getEnv();
assert.deepStrictEqual(env.OTEL_NO_PATCH_MODULES, ['a', 'b', 'c']);
assert.strictEqual(env.OTEL_SDK_DISABLED, true);
assert.strictEqual(env.OTEL_LOG_LEVEL, DiagLogLevel.ERROR);
assert.strictEqual(env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, 40);
assert.strictEqual(env.OTEL_ATTRIBUTE_COUNT_LIMIT, 50);
Expand Down Expand Up @@ -134,6 +136,27 @@ describe('environment', () => {
assert.strictEqual(env.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, 12000);
});

it('should parse OTEL_SDK_DISABLED truthy value despite casing', () => {
mockEnvironment({
OTEL_SDK_DISABLED: 'TrUe',
});
const env = getEnv();
assert.strictEqual(env.OTEL_SDK_DISABLED, true);
legendecas marked this conversation as resolved.
Show resolved Hide resolved
});

describe('OTEL_SDK_DISABLED falsy values', () => {
const falsyValues = ['False', ''];
for (const falsyValue of falsyValues) {
it(`should parse falsy value: ${falsyValue}`, () => {
mockEnvironment({
OTEL_SDK_DISABLED: falsyValue,
});
const env = getEnv();
assert.strictEqual(env.OTEL_SDK_DISABLED, false);
});
}
});

it('should parse OTEL_LOG_LEVEL despite casing', () => {
mockEnvironment({
OTEL_LOG_LEVEL: 'waRn',
Expand Down