Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit ef02843

Browse files
authored
feat: debug log global registrations and logger overwrites (#63)
* feat: debug log global registrations and logger overwrites * refactor: extract `restore` as fn * refactor: remove an excessive level of describe nesting * refactor: assert multiple times instead of separating logger tests * refactor: rename restore better * test: fix tests by ignoring alerts about the logger being set * feat: move debuglogging global to the end of the registration ... that's to assure we really did register it, not only attempted. Any short-circuits will be logged as well, so the debug would be unneccessary in those cases. * test: fix tests to ignore the messages created in setup * feat: add stack trace to DiagLogger overwrite warnings * test: make sure only the second global logger logs after reregistration * feat: revert changes to test structure
1 parent 950433f commit ef02843

File tree

6 files changed

+86
-51
lines changed

6 files changed

+86
-51
lines changed

src/api/diag.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,18 @@ export class DiagAPI implements DiagLogger {
8585
return false;
8686
}
8787

88-
return registerGlobal(
89-
'diag',
90-
createLogLevelDiagLogger(logLevel, logger),
91-
true
92-
);
88+
const oldLogger = getGlobal('diag');
89+
const newLogger = createLogLevelDiagLogger(logLevel, logger);
90+
// There already is an logger registered. We'll let it know before overwriting it.
91+
if (oldLogger) {
92+
const stack = new Error().stack ?? '<failed to generate stacktrace>';
93+
oldLogger.warn(`Current logger will be overwritten from ${stack}`);
94+
newLogger.warn(
95+
`Current logger will overwrite one already registered from ${stack}`
96+
);
97+
}
98+
99+
return registerGlobal('diag', newLogger, true);
93100
};
94101

95102
self.disable = () => {

src/internal/global-utils.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@ export function registerGlobal<Type extends keyof OTelGlobalAPI>(
3535
instance: OTelGlobalAPI[Type],
3636
allowOverride = false
3737
): boolean {
38-
_global[GLOBAL_OPENTELEMETRY_API_KEY] = _global[
38+
const api = (_global[GLOBAL_OPENTELEMETRY_API_KEY] = _global[
3939
GLOBAL_OPENTELEMETRY_API_KEY
4040
] ?? {
4141
version: VERSION,
42-
};
42+
});
4343

44-
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY]!;
4544
if (!allowOverride && api[type]) {
4645
// already registered an API of this type
4746
const err = new Error(
@@ -61,6 +60,10 @@ export function registerGlobal<Type extends keyof OTelGlobalAPI>(
6160
}
6261

6362
api[type] = instance;
63+
diag.debug(
64+
`@opentelemetry/api: Registered a global for ${type} v${VERSION}.`
65+
);
66+
6467
return true;
6568
}
6669

@@ -75,6 +78,9 @@ export function getGlobal<Type extends keyof OTelGlobalAPI>(
7578
}
7679

7780
export function unregisterGlobal(type: keyof OTelGlobalAPI) {
81+
diag.debug(
82+
`@opentelemetry/api: Unregistering a global for ${type} v${VERSION}.`
83+
);
7884
const api = _global[GLOBAL_OPENTELEMETRY_API_KEY];
7985

8086
if (api) {

test/diag/ComponentLogger.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,9 @@ import { diag, DiagLogger, DiagLogLevel } from '../../src';
2020

2121
class SpyLogger implements DiagLogger {
2222
debug() {}
23-
2423
error() {}
25-
2624
info() {}
27-
2825
warn() {}
29-
3026
verbose() {}
3127
}
3228

@@ -41,6 +37,8 @@ describe('ComponentLogger', () => {
4137
logger = new SpyLogger();
4238
sandbox.spy(logger);
4339
diag.setLogger(logger, DiagLogLevel.ALL);
40+
// clean any messages up that might be there from the registration
41+
sandbox.reset();
4442
});
4543

4644
afterEach(() => {

test/diag/logLevel.test.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ describe('LogLevelFilter DiagLogger', () => {
4545
/** Simulated Legacy logger */
4646
let incompleteLogger: DiagLogger;
4747

48+
const restoreCallHistory = () => {
49+
diagLoggerFunctions.forEach(fName => {
50+
calledArgs[fName] = null;
51+
});
52+
};
53+
4854
beforeEach(() => {
4955
// Set no logger so that sinon doesn't complain about TypeError: Attempted to wrap xxxx which is already wrapped
5056
diag.disable();
@@ -66,10 +72,7 @@ describe('LogLevelFilter DiagLogger', () => {
6672
});
6773

6874
afterEach(() => {
69-
// restore
70-
diagLoggerFunctions.forEach(fName => {
71-
calledArgs[fName] = null;
72-
});
75+
restoreCallHistory();
7376
});
7477

7578
const levelMap: Array<{
@@ -170,6 +173,7 @@ describe('LogLevelFilter DiagLogger', () => {
170173
map.level,
171174
dummyLogger
172175
);
176+
restoreCallHistory();
173177
testLogger[fName](`${fName} called %s`, 'param1');
174178
diagLoggerFunctions.forEach(lName => {
175179
if (
@@ -190,6 +194,7 @@ describe('LogLevelFilter DiagLogger', () => {
190194

191195
it('diag.setLogger level and logger should log', () => {
192196
diag.setLogger(dummyLogger, map.level);
197+
restoreCallHistory();
193198
diag[fName](`${fName} called %s`, 'param1');
194199
diagLoggerFunctions.forEach(lName => {
195200
if (fName === lName && map.ignoreFuncs.indexOf(lName) === -1) {
@@ -217,6 +222,7 @@ describe('LogLevelFilter DiagLogger', () => {
217222
map.level,
218223
invalidLogger as any
219224
);
225+
restoreCallHistory();
220226

221227
testLogger[fName](`${fName} called %s`, 'param1');
222228
diagLoggerFunctions.forEach(lName => {

test/diag/logger.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ describe('DiagLogger functions', () => {
3838

3939
let dummyLogger: DiagLogger;
4040

41+
const restoreCallHistory = () => {
42+
diagLoggerFunctions.forEach(fName => {
43+
calledArgs[fName] = null;
44+
});
45+
};
46+
4147
beforeEach(() => {
4248
// mock
4349
dummyLogger = {} as DiagLogger;
@@ -49,10 +55,7 @@ describe('DiagLogger functions', () => {
4955
});
5056

5157
afterEach(() => {
52-
// restore
53-
diagLoggerFunctions.forEach(fName => {
54-
calledArgs[fName] = null;
55-
});
58+
restoreCallHistory();
5659
diag.disable();
5760
});
5861

@@ -75,6 +78,7 @@ describe('DiagLogger functions', () => {
7578

7679
it(`diag should log with ${fName} message`, () => {
7780
diag.setLogger(dummyLogger, DiagLogLevel.ALL);
81+
restoreCallHistory();
7882
diag[fName](`${fName} called %s`, 'param1');
7983
diagLoggerFunctions.forEach(lName => {
8084
if (fName === lName) {

test/internal/global.test.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as assert from 'assert';
1818
import { getGlobal } from '../../src/internal/global-utils';
1919
import { _globalThis } from '../../src/platform';
2020
import { NoopContextManager } from '../../src/context/NoopContextManager';
21+
import { DiagLogLevel } from '../../src/diag/types';
2122
import sinon = require('sinon');
2223

2324
const api1 = require('../../src') as typeof import('../../src');
@@ -32,6 +33,14 @@ const api2 = require('../../src') as typeof import('../../src');
3233
// It is intentionally not autogenerated to ensure the author of the change is aware of what they are doing.
3334
const GLOBAL_API_SYMBOL_KEY = 'io.opentelemetry.js.api.0';
3435

36+
const getMockLogger = () => ({
37+
verbose: sinon.spy(),
38+
debug: sinon.spy(),
39+
info: sinon.spy(),
40+
warn: sinon.spy(),
41+
error: sinon.spy(),
42+
});
43+
3544
describe('Global Utils', () => {
3645
// prove they are separate instances
3746
assert.notEqual(api1, api2);
@@ -94,48 +103,53 @@ describe('Global Utils', () => {
94103
);
95104
});
96105

106+
it('should debug log registrations', () => {
107+
const logger = getMockLogger();
108+
api1.diag.setLogger(logger, DiagLogLevel.DEBUG);
109+
110+
const newContextManager = new NoopContextManager();
111+
api1.context.setGlobalContextManager(newContextManager);
112+
113+
sinon.assert.calledWith(logger.debug, sinon.match(/global for context/));
114+
sinon.assert.calledWith(logger.debug, sinon.match(/global for diag/));
115+
sinon.assert.calledTwice(logger.debug);
116+
});
117+
97118
it('should log an error if there is a duplicate registration', () => {
98-
const error = sinon.stub();
99-
api1.diag.setLogger({
100-
verbose: () => {},
101-
debug: () => {},
102-
info: () => {},
103-
warn: () => {},
104-
error,
105-
});
119+
const logger = getMockLogger();
120+
api1.diag.setLogger(logger);
106121

107122
api1.context.setGlobalContextManager(new NoopContextManager());
108123
api1.context.setGlobalContextManager(new NoopContextManager());
109124

110-
sinon.assert.calledOnce(error);
111-
assert.strictEqual(error.firstCall.args.length, 1);
125+
sinon.assert.calledOnce(logger.error);
126+
assert.strictEqual(logger.error.firstCall.args.length, 1);
112127
assert.ok(
113-
error.firstCall.args[0].startsWith(
128+
logger.error.firstCall.args[0].startsWith(
114129
'Error: @opentelemetry/api: Attempted duplicate registration of API: context'
115130
)
116131
);
117132
});
118133

119134
it('should allow duplicate registration of the diag logger', () => {
120-
const error1 = sinon.stub();
121-
const error2 = sinon.stub();
122-
api1.diag.setLogger({
123-
verbose: () => {},
124-
debug: () => {},
125-
info: () => {},
126-
warn: () => {},
127-
error: error1,
128-
});
129-
130-
api1.diag.setLogger({
131-
verbose: () => {},
132-
debug: () => {},
133-
info: () => {},
134-
warn: () => {},
135-
error: error2,
136-
});
137-
138-
sinon.assert.notCalled(error1);
139-
sinon.assert.notCalled(error2);
135+
const logger1 = getMockLogger();
136+
const logger2 = getMockLogger();
137+
138+
api1.diag.setLogger(logger1);
139+
api1.diag.setLogger(logger2);
140+
141+
const MSG = '__log message__';
142+
api1.diag.info(MSG);
143+
144+
sinon.assert.notCalled(logger1.error);
145+
sinon.assert.notCalled(logger1.info);
146+
sinon.assert.calledOnce(logger1.warn);
147+
sinon.assert.calledWith(logger1.warn, sinon.match(/will be overwritten/i));
148+
149+
sinon.assert.notCalled(logger2.error);
150+
sinon.assert.calledOnce(logger2.warn);
151+
sinon.assert.calledWith(logger2.warn, sinon.match(/will overwrite/i));
152+
sinon.assert.calledOnce(logger2.info);
153+
sinon.assert.calledWith(logger2.info, MSG);
140154
});
141155
});

0 commit comments

Comments
 (0)