Skip to content

Commit a2c25d0

Browse files
hectorhdzgZirak
authored andcommitted
feat(api-logs): Add delegating no-op logger provider (open-telemetry#4861)
1 parent 1aeb93b commit a2c25d0

File tree

9 files changed

+276
-11
lines changed

9 files changed

+276
-11
lines changed

experimental/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ All notable changes to experimental packages in this project will be documented
99

1010
### :rocket: (Enhancement)
1111

12+
* feat(api-logs): Add delegating no-op logger provider [#4861](https://github.com/open-telemetry/opentelemetry-js/pull/4861) @hectorhdzg
13+
1214
### :bug: (Bug Fix)
1315

1416
* fix(sampler-jaeger-remote): fixes an issue where package could emit unhandled promise rejections @Just-Sieb
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { NOOP_LOGGER } from './NoopLogger';
18+
import { Logger } from './types/Logger';
19+
import { LoggerOptions } from './types/LoggerOptions';
20+
import { LogRecord } from './types/LogRecord';
21+
22+
export class ProxyLogger implements Logger {
23+
// When a real implementation is provided, this will be it
24+
private _delegate?: Logger;
25+
26+
constructor(
27+
private _provider: LoggerDelegator,
28+
public readonly name: string,
29+
public readonly version?: string | undefined,
30+
public readonly options?: LoggerOptions | undefined
31+
) {}
32+
33+
/**
34+
* Emit a log record. This method should only be used by log appenders.
35+
*
36+
* @param logRecord
37+
*/
38+
emit(logRecord: LogRecord): void {
39+
this._getLogger().emit(logRecord);
40+
}
41+
42+
/**
43+
* Try to get a logger from the proxy logger provider.
44+
* If the proxy logger provider has no delegate, return a noop logger.
45+
*/
46+
private _getLogger() {
47+
if (this._delegate) {
48+
return this._delegate;
49+
}
50+
const logger = this._provider.getDelegateLogger(
51+
this.name,
52+
this.version,
53+
this.options
54+
);
55+
if (!logger) {
56+
return NOOP_LOGGER;
57+
}
58+
this._delegate = logger;
59+
return this._delegate;
60+
}
61+
}
62+
63+
export interface LoggerDelegator {
64+
getDelegateLogger(
65+
name: string,
66+
version?: string,
67+
options?: LoggerOptions
68+
): Logger | undefined;
69+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { LoggerProvider } from './types/LoggerProvider';
18+
import { Logger } from './types/Logger';
19+
import { LoggerOptions } from './types/LoggerOptions';
20+
import { NOOP_LOGGER_PROVIDER } from './NoopLoggerProvider';
21+
import { ProxyLogger } from './ProxyLogger';
22+
23+
export class ProxyLoggerProvider implements LoggerProvider {
24+
private _delegate?: LoggerProvider;
25+
26+
getLogger(
27+
name: string,
28+
version?: string | undefined,
29+
options?: LoggerOptions | undefined
30+
): Logger {
31+
return (
32+
this.getDelegateLogger(name, version, options) ??
33+
new ProxyLogger(this, name, version, options)
34+
);
35+
}
36+
37+
getDelegate(): LoggerProvider {
38+
return this._delegate ?? NOOP_LOGGER_PROVIDER;
39+
}
40+
41+
/**
42+
* Set the delegate logger provider
43+
*/
44+
setDelegate(delegate: LoggerProvider) {
45+
this._delegate = delegate;
46+
}
47+
48+
getDelegateLogger(
49+
name: string,
50+
version?: string | undefined,
51+
options?: LoggerOptions | undefined
52+
): Logger | undefined {
53+
return this._delegate?.getLogger(name, version, options);
54+
}
55+
}

experimental/packages/api-logs/src/api/logs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ import { LoggerProvider } from '../types/LoggerProvider';
2424
import { NOOP_LOGGER_PROVIDER } from '../NoopLoggerProvider';
2525
import { Logger } from '../types/Logger';
2626
import { LoggerOptions } from '../types/LoggerOptions';
27+
import { ProxyLoggerProvider } from '../ProxyLoggerProvider';
2728

2829
export class LogsAPI {
2930
private static _instance?: LogsAPI;
3031

32+
private _proxyLoggerProvider = new ProxyLoggerProvider();
33+
3134
private constructor() {}
3235

3336
public static getInstance(): LogsAPI {
@@ -48,6 +51,7 @@ export class LogsAPI {
4851
provider,
4952
NOOP_LOGGER_PROVIDER
5053
);
54+
this._proxyLoggerProvider.setDelegate(provider);
5155

5256
return provider;
5357
}
@@ -60,7 +64,7 @@ export class LogsAPI {
6064
public getLoggerProvider(): LoggerProvider {
6165
return (
6266
_global[GLOBAL_LOGS_API_KEY]?.(API_BACKWARDS_COMPATIBILITY_VERSION) ??
63-
NOOP_LOGGER_PROVIDER
67+
this._proxyLoggerProvider
6468
);
6569
}
6670

@@ -80,5 +84,6 @@ export class LogsAPI {
8084
/** Remove the global logger provider */
8185
public disable(): void {
8286
delete _global[GLOBAL_LOGS_API_KEY];
87+
this._proxyLoggerProvider = new ProxyLoggerProvider();
8388
}
8489
}

experimental/packages/api-logs/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export { LoggerOptions } from './types/LoggerOptions';
2626
export { AnyValue, AnyValueMap } from './types/AnyValue';
2727
export { NOOP_LOGGER, NoopLogger } from './NoopLogger';
2828
export { NOOP_LOGGER_PROVIDER, NoopLoggerProvider } from './NoopLoggerProvider';
29+
export { ProxyLogger } from './ProxyLogger';
30+
export { ProxyLoggerProvider } from './ProxyLoggerProvider';
2931

3032
import { LogsAPI } from './api/logs';
3133
export const logs = LogsAPI.getInstance();

experimental/packages/api-logs/test/api/api.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import { Logger, logs } from '../../src';
18+
import { Logger, ProxyLoggerProvider, logs } from '../../src';
1919
import { NoopLogger } from '../../src/NoopLogger';
2020
import { NoopLoggerProvider } from '../../src/NoopLoggerProvider';
2121

2222
describe('API', () => {
2323
const dummyLogger = new NoopLogger();
2424

2525
it('should expose a logger provider via getLoggerProvider', () => {
26-
const provider = logs.getLoggerProvider();
27-
assert.ok(provider);
28-
assert.strictEqual(typeof provider, 'object');
26+
assert.ok(logs.getLoggerProvider() instanceof ProxyLoggerProvider);
27+
assert.ok(
28+
(logs.getLoggerProvider() as ProxyLoggerProvider).getDelegate() instanceof
29+
NoopLoggerProvider
30+
);
2931
});
3032

3133
describe('GlobalLoggerProvider', () => {

experimental/packages/api-logs/test/internal/global.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import * as assert from 'assert';
1818
import { _global, GLOBAL_LOGS_API_KEY } from '../../src/internal/global-utils';
1919
import { NoopLoggerProvider } from '../../src/NoopLoggerProvider';
20+
import { ProxyLoggerProvider } from '../../src';
2021

2122
const api1 = require('../../src') as typeof import('../../src');
2223

@@ -66,11 +67,10 @@ describe('Global Utils', () => {
6667
assert.strictEqual(original, api1.logs.getLoggerProvider());
6768
});
6869

69-
it('should return the module NoOp implementation if the version is a mismatch', () => {
70-
const original = api1.logs.getLoggerProvider();
71-
api1.logs.setGlobalLoggerProvider(new NoopLoggerProvider());
70+
it('should return the module no op implementation if the version is a mismatch', () => {
71+
api1.logs.setGlobalLoggerProvider(new ProxyLoggerProvider());
7272
const afterSet = _global[GLOBAL_LOGS_API_KEY]!(-1);
7373

74-
assert.strictEqual(original, afterSet);
74+
assert.ok(afterSet instanceof NoopLoggerProvider);
7575
});
7676
});
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import * as sinon from 'sinon';
19+
import {
20+
Logger,
21+
LoggerProvider,
22+
ProxyLogger,
23+
ProxyLoggerProvider,
24+
} from '../../src';
25+
import { NoopLogger } from '../../src/NoopLogger';
26+
27+
describe('ProxyLogger', () => {
28+
let provider: ProxyLoggerProvider;
29+
const sandbox = sinon.createSandbox();
30+
31+
beforeEach(() => {
32+
provider = new ProxyLoggerProvider();
33+
});
34+
35+
afterEach(() => {
36+
sandbox.restore();
37+
});
38+
39+
describe('when no delegate is set', () => {
40+
it('should return proxy loggers', () => {
41+
const logger = provider.getLogger('test');
42+
assert.ok(logger instanceof ProxyLogger);
43+
});
44+
});
45+
46+
describe('when delegate is set before getLogger', () => {
47+
let delegate: LoggerProvider;
48+
let getLoggerStub: sinon.SinonStub;
49+
50+
beforeEach(() => {
51+
getLoggerStub = sandbox.stub().returns(new NoopLogger());
52+
delegate = {
53+
getLogger: getLoggerStub,
54+
};
55+
provider.setDelegate(delegate);
56+
});
57+
58+
it('should return loggers directly from the delegate', () => {
59+
const logger = provider.getLogger('test', 'v0');
60+
61+
sandbox.assert.calledOnce(getLoggerStub);
62+
assert.strictEqual(getLoggerStub.firstCall.returnValue, logger);
63+
assert.deepStrictEqual(getLoggerStub.firstCall.args, [
64+
'test',
65+
'v0',
66+
undefined,
67+
]);
68+
});
69+
70+
it('should return loggers directly from the delegate with schema url', () => {
71+
const logger = provider.getLogger('test', 'v0', {
72+
schemaUrl: 'https://opentelemetry.io/schemas/1.7.0',
73+
});
74+
75+
sandbox.assert.calledOnce(getLoggerStub);
76+
assert.strictEqual(getLoggerStub.firstCall.returnValue, logger);
77+
assert.deepStrictEqual(getLoggerStub.firstCall.args, [
78+
'test',
79+
'v0',
80+
{ schemaUrl: 'https://opentelemetry.io/schemas/1.7.0' },
81+
]);
82+
});
83+
});
84+
85+
describe('when delegate is set after getLogger', () => {
86+
let logger: Logger;
87+
let delegateProvider: LoggerProvider;
88+
89+
let delegateLogger: Logger;
90+
let emitCalled: boolean;
91+
92+
beforeEach(() => {
93+
emitCalled = false;
94+
delegateLogger = {
95+
emit() {
96+
emitCalled = true;
97+
},
98+
};
99+
100+
logger = provider.getLogger('test');
101+
102+
delegateProvider = {
103+
getLogger() {
104+
return delegateLogger;
105+
},
106+
};
107+
provider.setDelegate(delegateProvider);
108+
});
109+
110+
it('should emit from the delegate logger', () => {
111+
logger.emit({
112+
body: 'Test',
113+
});
114+
assert.ok(emitCalled);
115+
});
116+
});
117+
});

0 commit comments

Comments
 (0)