Skip to content

Commit

Permalink
feat(sdk-node): configure no-op sdk with OTEL_SDK_DISABLED environm…
Browse files Browse the repository at this point in the history
…ent variable (#3485)


Co-authored-by: legendecas <[email protected]>
  • Loading branch information
RazGvili and legendecas committed Jan 2, 2023
1 parent 1c3af6c commit eecb800
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 2 deletions.
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);
});

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

0 comments on commit eecb800

Please sign in to comment.