Skip to content

Commit

Permalink
feat: add OTEL_LOG_LEVEL env var
Browse files Browse the repository at this point in the history
User can now control the log level by means of env var.

Sets default log level to INFO as per #856

Also shares mock environment functions for tests to be used in
ConsoleLogger tests as well as environment tests.

Signed-off-by: Naseem <naseem@transit.app>
  • Loading branch information
Naseem committed Jul 25, 2020
1 parent 2d08fcd commit 189681a
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 39 deletions.
3 changes: 2 additions & 1 deletion packages/opentelemetry-core/src/common/ConsoleLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions packages/opentelemetry-core/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 71 additions & 3 deletions packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export interface ENVIRONMENT {
}

const ENVIRONMENT_NUMBERS: Partial<keyof ENVIRONMENT>[] = [
'OTEL_LOG_LEVEL',
'OTEL_SAMPLING_PROBABILITY',
];

Expand All @@ -37,7 +36,7 @@ const ENVIRONMENT_NUMBERS: Partial<keyof ENVIRONMENT>[] = [
*/
export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_NO_PATCH_MODULES: '',
OTEL_LOG_LEVEL: LogLevel.ERROR,
OTEL_LOG_LEVEL: LogLevel.INFO,
OTEL_SAMPLING_PROBABILITY: 1,
};

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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 = {};
}
38 changes: 38 additions & 0 deletions packages/opentelemetry-core/test/common/ConsoleLogger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
import * as assert from 'assert';
import { ConsoleLogger } from '../../src/common/ConsoleLogger';
import { LogLevel } from '../../src/common/types';
import console = require('console');
import {
removeMockEnvironment,
mockEnvironment,
} from '../../src/utils/environment';

describe('ConsoleLogger', () => {
const origDebug = console.debug;
Expand Down Expand Up @@ -54,6 +59,7 @@ describe('ConsoleLogger', () => {
console.info = origInfo;
console.warn = origWarn;
console.error = origError;
removeMockEnvironment();
});

describe('constructor', () => {
Expand All @@ -65,6 +71,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', () => {
Expand Down Expand Up @@ -114,5 +122,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);
});
});
});
44 changes: 13 additions & 31 deletions packages/opentelemetry-core/test/utils/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 => {
Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-metrics/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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. */
Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-tracing/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down

0 comments on commit 189681a

Please sign in to comment.