From 33f7a0478676f2d84e71aeaa70bf91b9edca46ff Mon Sep 17 00:00:00 2001 From: Naseem Date: Sat, 18 Apr 2020 12:12:15 -0400 Subject: [PATCH] feat: add OTEL_LOG_LEVEL env var User can now control the log level by means of env var. Sets default log level to INFO as per https://github.com/open-telemetry/opentelemetry-js/pull/856 Also shares mock environment functions for tests to be used in ConsoleLogger tests as well as environment tests. Signed-off-by: Naseem --- .../src/common/ConsoleLogger.ts | 3 +- .../opentelemetry-core/src/common/types.ts | 6 ++ .../src/utils/environment.ts | 74 ++++++++++++++++++- .../test/common/ConsoleLogger.test.ts | 37 ++++++++++ .../test/utils/environment.test.ts | 44 ++++------- packages/opentelemetry-metrics/src/types.ts | 4 +- packages/opentelemetry-tracing/src/config.ts | 4 +- 7 files changed, 133 insertions(+), 39 deletions(-) diff --git a/packages/opentelemetry-core/src/common/ConsoleLogger.ts b/packages/opentelemetry-core/src/common/ConsoleLogger.ts index c2cc8b58f4..d0ee72eaff 100644 --- a/packages/opentelemetry-core/src/common/ConsoleLogger.ts +++ b/packages/opentelemetry-core/src/common/ConsoleLogger.ts @@ -16,9 +16,10 @@ import { Logger } from '@opentelemetry/api'; import { LogLevel } from './types'; +import { getEnv } from '../platform'; export class ConsoleLogger implements Logger { - constructor(level: LogLevel = LogLevel.INFO) { + constructor(level: LogLevel = getEnv().OTEL_LOG_LEVEL) { if (level >= LogLevel.DEBUG) { this.debug = (...args) => { console.debug(...args); diff --git a/packages/opentelemetry-core/src/common/types.ts b/packages/opentelemetry-core/src/common/types.ts index 4de46a327b..399ed9d5c1 100644 --- a/packages/opentelemetry-core/src/common/types.ts +++ b/packages/opentelemetry-core/src/common/types.ts @@ -20,6 +20,12 @@ export enum LogLevel { DEBUG, } +/** + * This is equivalent to: + * type LogLevelString = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; + */ +export type LogLevelString = keyof typeof LogLevel; + /** * This interface defines a fallback to read a timeOrigin when it is not available on performance.timeOrigin, * this happens for example on Safari Mac diff --git a/packages/opentelemetry-core/src/utils/environment.ts b/packages/opentelemetry-core/src/utils/environment.ts index 9ada4030cd..f7c9d15e56 100644 --- a/packages/opentelemetry-core/src/utils/environment.ts +++ b/packages/opentelemetry-core/src/utils/environment.ts @@ -28,7 +28,6 @@ export interface ENVIRONMENT { } const ENVIRONMENT_NUMBERS: Partial[] = [ - 'OTEL_LOG_LEVEL', 'OTEL_SAMPLING_PROBABILITY', ]; @@ -37,7 +36,7 @@ const ENVIRONMENT_NUMBERS: Partial[] = [ */ export const DEFAULT_ENVIRONMENT: Required = { OTEL_NO_PATCH_MODULES: '', - OTEL_LOG_LEVEL: LogLevel.ERROR, + OTEL_LOG_LEVEL: LogLevel.INFO, OTEL_SAMPLING_PROBABILITY: 1, }; @@ -64,6 +63,41 @@ function parseNumber( } } +/** + * Environmentally sets log level if valid log level string is provided + * @param key + * @param environment + * @param values + */ +function setLogLevelFromEnv( + key: keyof ENVIRONMENT, + environment: ENVIRONMENT_MAP | ENVIRONMENT, + values: ENVIRONMENT_MAP +) { + const value = values[key]; + switch (typeof value === 'string' ? value.toUpperCase() : value) { + case 'DEBUG': + environment[key] = LogLevel.DEBUG; + break; + + case 'INFO': + environment[key] = LogLevel.INFO; + break; + + case 'WARN': + environment[key] = LogLevel.WARN; + break; + + case 'ERROR': + environment[key] = LogLevel.ERROR; + break; + + default: + // do nothing + break; + } +} + /** * Parses environment values * @param values @@ -79,7 +113,7 @@ export function parseEnvironment(values: ENVIRONMENT_MAP): ENVIRONMENT { break; case 'OTEL_LOG_LEVEL': - parseNumber(key, environment, values, LogLevel.ERROR, LogLevel.DEBUG); + setLogLevelFromEnv(key, environment, values); break; default: @@ -95,3 +129,37 @@ export function parseEnvironment(values: ENVIRONMENT_MAP): ENVIRONMENT { return environment; } + +let lastMock: ENVIRONMENT_MAP = {}; + +/** + * Mocks environment used for tests. + */ +export function mockEnvironment(values: ENVIRONMENT_MAP) { + lastMock = values; + if (typeof process !== 'undefined') { + Object.keys(values).forEach(key => { + process.env[key] = String(values[key]); + }); + } else { + Object.keys(values).forEach(key => { + ((window as unknown) as ENVIRONMENT_MAP)[key] = String(values[key]); + }); + } +} + +/** + * Removes mocked environment ussed for tests. + */ +export function removeMockEnvironment() { + if (typeof process !== 'undefined') { + Object.keys(lastMock).forEach(key => { + delete process.env[key]; + }); + } else { + Object.keys(lastMock).forEach(key => { + delete ((window as unknown) as ENVIRONMENT_MAP)[key]; + }); + } + lastMock = {}; +} diff --git a/packages/opentelemetry-core/test/common/ConsoleLogger.test.ts b/packages/opentelemetry-core/test/common/ConsoleLogger.test.ts index b00900e39f..524f7c22c0 100644 --- a/packages/opentelemetry-core/test/common/ConsoleLogger.test.ts +++ b/packages/opentelemetry-core/test/common/ConsoleLogger.test.ts @@ -17,6 +17,10 @@ import * as assert from 'assert'; import { ConsoleLogger } from '../../src/common/ConsoleLogger'; import { LogLevel } from '../../src/common/types'; +import { + removeMockEnvironment, + mockEnvironment, +} from '../../src/utils/environment'; describe('ConsoleLogger', () => { const origDebug = console.debug; @@ -54,6 +58,7 @@ describe('ConsoleLogger', () => { console.info = origInfo; console.warn = origWarn; console.error = origError; + removeMockEnvironment(); }); describe('constructor', () => { @@ -65,6 +70,8 @@ describe('ConsoleLogger', () => { assert.deepStrictEqual(warnCalledArgs, ['warn called %s', 'param1']); consoleLogger.info('info called %s', 'param1'); assert.deepStrictEqual(infoCalledArgs, ['info called %s', 'param1']); + consoleLogger.debug('debug called %s', 'param1'); + assert.strictEqual(debugCalledArgs, undefined); }); it('should log with debug', () => { @@ -114,5 +121,35 @@ describe('ConsoleLogger', () => { consoleLogger.debug('debug called %s', 'param1'); assert.strictEqual(debugCalledArgs, null); }); + + it('should log with environmentally set level ', () => { + mockEnvironment({ + OTEL_LOG_LEVEL: 'WARN', + }); + const consoleLogger = new ConsoleLogger(); + consoleLogger.error('error called'); + assert.deepStrictEqual(errorCalledArgs, ['error called']); + consoleLogger.warn('warn called %s', 'param1'); + assert.deepStrictEqual(warnCalledArgs, ['warn called %s', 'param1']); + consoleLogger.info('info called %s', 'param1'); + assert.deepStrictEqual(infoCalledArgs, null); + consoleLogger.debug('debug called %s', 'param1'); + assert.deepStrictEqual(debugCalledArgs, null); + }); + + it('should log with default log level if environmentally set level is invalid', () => { + mockEnvironment({ + OTEL_LOG_LEVEL: 'INVALID_VALUE', + }); + const consoleLogger = new ConsoleLogger(); + consoleLogger.error('error called'); + assert.deepStrictEqual(errorCalledArgs, ['error called']); + consoleLogger.warn('warn called %s', 'param1'); + assert.deepStrictEqual(warnCalledArgs, ['warn called %s', 'param1']); + consoleLogger.info('info called %s', 'param1'); + assert.deepStrictEqual(infoCalledArgs, ['info called %s', 'param1']); + consoleLogger.debug('debug called %s', 'param1'); + assert.deepStrictEqual(debugCalledArgs, null); + }); }); }); diff --git a/packages/opentelemetry-core/test/utils/environment.test.ts b/packages/opentelemetry-core/test/utils/environment.test.ts index 267a6bf285..30eedeb811 100644 --- a/packages/opentelemetry-core/test/utils/environment.test.ts +++ b/packages/opentelemetry-core/test/utils/environment.test.ts @@ -18,38 +18,12 @@ import { getEnv } from '../../src/platform'; import { DEFAULT_ENVIRONMENT, ENVIRONMENT, - ENVIRONMENT_MAP, + removeMockEnvironment, + mockEnvironment, } from '../../src/utils/environment'; import * as assert from 'assert'; import * as sinon from 'sinon'; - -let lastMock: ENVIRONMENT_MAP = {}; - -function mockEnvironment(values: ENVIRONMENT_MAP) { - lastMock = values; - if (typeof process !== 'undefined') { - Object.keys(values).forEach(key => { - process.env[key] = String(values[key]); - }); - } else { - Object.keys(values).forEach(key => { - ((window as unknown) as ENVIRONMENT_MAP)[key] = String(values[key]); - }); - } -} - -function removeMockEnvironment() { - if (typeof process !== 'undefined') { - Object.keys(lastMock).forEach(key => { - delete process.env[key]; - }); - } else { - Object.keys(lastMock).forEach(key => { - delete ((window as unknown) as ENVIRONMENT_MAP)[key]; - }); - } - lastMock = {}; -} +import { LogLevel } from '../../src'; describe('environment', () => { let sandbox: sinon.SinonSandbox; @@ -68,15 +42,23 @@ describe('environment', () => { mockEnvironment({ FOO: '1', OTEL_NO_PATCH_MODULES: 'a,b,c', - OTEL_LOG_LEVEL: '1', + OTEL_LOG_LEVEL: 'WARN', OTEL_SAMPLING_PROBABILITY: '0.5', }); const env = getEnv(); assert.strictEqual(env.OTEL_NO_PATCH_MODULES, 'a,b,c'); - assert.strictEqual(env.OTEL_LOG_LEVEL, 1); + assert.strictEqual(env.OTEL_LOG_LEVEL, LogLevel.WARN); assert.strictEqual(env.OTEL_SAMPLING_PROBABILITY, 0.5); }); + it('should parse OTEL_LOG_LEVEL despite casing', () => { + mockEnvironment({ + OTEL_LOG_LEVEL: 'waRn', + }); + const env = getEnv(); + assert.strictEqual(env.OTEL_LOG_LEVEL, LogLevel.WARN); + }); + it('should parse environment variables and use defaults', () => { const env = getEnv(); Object.keys(DEFAULT_ENVIRONMENT).forEach(envKey => { diff --git a/packages/opentelemetry-metrics/src/types.ts b/packages/opentelemetry-metrics/src/types.ts index 0334bac69c..cc01af9f8d 100644 --- a/packages/opentelemetry-metrics/src/types.ts +++ b/packages/opentelemetry-metrics/src/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LogLevel } from '@opentelemetry/core'; +import { LogLevel, getEnv } from '@opentelemetry/core'; import * as api from '@opentelemetry/api'; import { MetricExporter } from './export/types'; import { Resource } from '@opentelemetry/resources'; @@ -43,7 +43,7 @@ export interface MeterConfig { /** Default Meter configuration. */ export const DEFAULT_CONFIG = { - logLevel: LogLevel.INFO, + logLevel: getEnv().OTEL_LOG_LEVEL, }; /** The default metric creation options value. */ diff --git a/packages/opentelemetry-tracing/src/config.ts b/packages/opentelemetry-tracing/src/config.ts index a1d19ed125..581d5f151e 100644 --- a/packages/opentelemetry-tracing/src/config.ts +++ b/packages/opentelemetry-tracing/src/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AlwaysOnSampler, LogLevel } from '@opentelemetry/core'; +import { AlwaysOnSampler, getEnv } from '@opentelemetry/core'; /** Default limit for Message events per span */ export const DEFAULT_MAX_EVENTS_PER_SPAN = 128; @@ -31,7 +31,7 @@ export const DEFAULT_MAX_LINKS_PER_SPAN = 32; */ export const DEFAULT_CONFIG = { defaultAttributes: {}, - logLevel: LogLevel.INFO, + logLevel: getEnv().OTEL_LOG_LEVEL, sampler: new AlwaysOnSampler(), traceParams: { numberOfAttributesPerSpan: DEFAULT_MAX_ATTRIBUTES_PER_SPAN,