Skip to content

Commit 9d6ac47

Browse files
committed
Normalize getTelemetry initialization
1 parent 0e81ec4 commit 9d6ac47

File tree

7 files changed

+168
-26
lines changed

7 files changed

+168
-26
lines changed

packages/telemetry/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
},
7171
"dependencies": {
7272
"@firebase/component": "0.7.0",
73-
"@firebase/util": "1.13.0",
7473
"@opentelemetry/api": "1.9.0",
7574
"@opentelemetry/api-logs": "0.203.0",
7675
"@opentelemetry/exporter-logs-otlp-http": "0.203.0",

packages/telemetry/src/api.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ import {
2525
SimpleSpanProcessor,
2626
WebTracerProvider
2727
} from '@opentelemetry/sdk-trace-web';
28-
import { captureError, flush } from './api';
28+
import {
29+
FirebaseApp,
30+
initializeApp,
31+
_registerComponent,
32+
_addOrOverwriteComponent
33+
} from '@firebase/app';
34+
import { Component, ComponentType } from '@firebase/component';
35+
import { FirebaseAppCheckInternal } from '@firebase/app-check-interop-types';
36+
import { captureError, flush, getTelemetry } from './api';
37+
import { TelemetryService } from './service';
38+
import { registerTelemetry } from './register';
2939

3040
const PROJECT_ID = 'my-project';
3141
const APP_ID = 'my-appid';
@@ -65,6 +75,33 @@ describe('Top level API', () => {
6575
emittedLogs.length = 0;
6676
});
6777

78+
describe('getTelemetry()', () => {
79+
it('works without options', () => {
80+
expect(getTelemetry(getFakeApp())).to.be.instanceOf(TelemetryService);
81+
// Two instances are the same
82+
expect(getTelemetry(getFakeApp())).to.equal(getTelemetry(getFakeApp()));
83+
});
84+
85+
it('works with options: no endpointUrl', () => {
86+
expect(getTelemetry(getFakeApp(), {})).to.equal(
87+
getTelemetry(getFakeApp())
88+
);
89+
});
90+
91+
it('works with options: endpointUrl set', () => {
92+
const app = getFakeApp();
93+
expect(getTelemetry(app, { endpointUrl: 'http://endpoint1' })).to.equal(
94+
getTelemetry(app, { endpointUrl: 'http://endpoint1' })
95+
);
96+
expect(
97+
getTelemetry(app, { endpointUrl: 'http://endpoint1' })
98+
).not.to.equal(getTelemetry(app, { endpointUrl: 'http://endpoint2' }));
99+
expect(
100+
getTelemetry(app, { endpointUrl: 'http://endpoint1' })
101+
).not.to.equal(getTelemetry(app, {}));
102+
});
103+
});
104+
68105
describe('captureError()', () => {
69106
it('should capture an Error object correctly', () => {
70107
const error = new Error('This is a test error');
@@ -189,3 +226,32 @@ describe('Top level API', () => {
189226
});
190227
});
191228
});
229+
230+
function getFakeApp(): FirebaseApp {
231+
registerTelemetry();
232+
_registerComponent(
233+
new Component(
234+
'app-check-internal',
235+
() => {
236+
return {} as FirebaseAppCheckInternal;
237+
},
238+
ComponentType.PUBLIC
239+
)
240+
);
241+
const app = initializeApp({});
242+
_addOrOverwriteComponent(
243+
app,
244+
//@ts-ignore
245+
new Component(
246+
'heartbeat',
247+
// @ts-ignore
248+
() => {
249+
return {
250+
triggerHeartbeat: () => {}
251+
};
252+
},
253+
ComponentType.PUBLIC
254+
)
255+
);
256+
return app;
257+
}

packages/telemetry/src/api.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
*/
1717

1818
import { _getProvider, FirebaseApp, getApp } from '@firebase/app';
19-
import { deepEqual } from '@firebase/util';
2019
import { TELEMETRY_TYPE } from './constants';
2120
import { Telemetry, TelemetryOptions } from './public-types';
2221
import { Provider } from '@firebase/component';
2322
import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs';
2423
import { trace } from '@opentelemetry/api';
2524
import { TelemetryService } from './service';
25+
import { encodeInstanceIdentifier } from './helpers';
2626

2727
declare module '@firebase/component' {
2828
interface NameServiceMapping {
@@ -50,25 +50,13 @@ export function getTelemetry(
5050
app: FirebaseApp = getApp(),
5151
options?: TelemetryOptions
5252
): Telemetry {
53-
// Dependencies
5453
const telemetryProvider: Provider<'telemetry'> = _getProvider(
5554
app,
5655
TELEMETRY_TYPE
5756
);
58-
59-
if (telemetryProvider.isInitialized()) {
60-
const initialOptions = telemetryProvider.getOptions();
61-
if (
62-
(!initialOptions && !options) ||
63-
(initialOptions && options && deepEqual(initialOptions, options))
64-
) {
65-
return telemetryProvider.getImmediate();
66-
}
67-
throw new Error('Firebase Telemetry is already initialized');
68-
}
69-
70-
telemetryProvider.initialize({ options });
71-
return telemetryProvider.getImmediate();
57+
const finalOptions: TelemetryOptions = options || {};
58+
const identifier = encodeInstanceIdentifier(finalOptions);
59+
return telemetryProvider.getImmediate({ identifier });
7260
}
7361

7462
/**
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
import { encodeInstanceIdentifier, decodeInstanceIdentifier } from './helpers';
20+
import { TelemetryOptions } from './public-types';
21+
22+
describe('Identifier encoding/decoding', () => {
23+
it('encodes/decodes empty options', () => {
24+
const encoded = encodeInstanceIdentifier({});
25+
const decoded = decodeInstanceIdentifier(encoded);
26+
expect(decoded).to.deep.equal({});
27+
});
28+
29+
it('encodes/decodes full options', () => {
30+
const options: TelemetryOptions = {
31+
endpointUrl: 'http://myendpoint'
32+
};
33+
const encoded = encodeInstanceIdentifier(options);
34+
const decoded = decodeInstanceIdentifier(encoded);
35+
expect(decoded).to.deep.equal(options);
36+
});
37+
});

packages/telemetry/src/helpers.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { TelemetryOptions } from './public-types';
19+
20+
/**
21+
* Encodes {@link TelemetryOptions} into a string that will be used to uniquely identify
22+
* {@link Telemetry} instances by endpoint URL.
23+
*
24+
* @internal
25+
*/
26+
export function encodeInstanceIdentifier(options: TelemetryOptions): string {
27+
return options.endpointUrl || '';
28+
}
29+
30+
/**
31+
* Decodes an instance identifier string into {@link TelemetryOptions}.
32+
*
33+
* @internal
34+
*/
35+
export function decodeInstanceIdentifier(identifier: string): TelemetryOptions {
36+
if (identifier) {
37+
return {
38+
endpointUrl: identifier
39+
};
40+
}
41+
return {};
42+
}

packages/telemetry/src/register.node.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ import { TELEMETRY_TYPE } from './constants';
2121
import { name, version } from '../package.json';
2222
import { TelemetryService } from './service';
2323
import { createLoggerProvider } from './logging/logger-provider';
24-
import { TelemetryOptions } from './public-types';
24+
import { decodeInstanceIdentifier } from './helpers';
2525

2626
export function registerTelemetry(): void {
2727
_registerComponent(
2828
new Component(
2929
TELEMETRY_TYPE,
30-
(container, { options }: { options?: TelemetryOptions }) => {
30+
(container, { instanceIdentifier }) => {
31+
if (instanceIdentifier === undefined) {
32+
throw new Error('TelemetryService instance identifier is undefined');
33+
}
34+
35+
const options = decodeInstanceIdentifier(instanceIdentifier);
3136
// TODO: change to default endpoint once it exists
32-
const endpointUrl = options?.endpointUrl || 'http://localhost';
37+
const endpointUrl = options.endpointUrl || 'http://localhost';
3338

3439
// getImmediate for FirebaseApp will always succeed
3540
const app = container.getProvider('app').getImmediate();
@@ -39,7 +44,7 @@ export function registerTelemetry(): void {
3944
return new TelemetryService(app, loggerProvider, appCheckProvider);
4045
},
4146
ComponentType.PUBLIC
42-
)
47+
).setMultipleInstances(true)
4348
);
4449

4550
registerVersion(name, version, 'node');

packages/telemetry/src/register.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,20 @@ import { TELEMETRY_TYPE } from './constants';
2121
import { name, version } from '../package.json';
2222
import { TelemetryService } from './service';
2323
import { createLoggerProvider } from './logging/logger-provider';
24-
import { TelemetryOptions } from './public-types';
24+
import { decodeInstanceIdentifier } from './helpers';
2525

2626
export function registerTelemetry(): void {
2727
_registerComponent(
2828
new Component(
2929
TELEMETRY_TYPE,
30-
(container, { options }: { options?: TelemetryOptions }) => {
30+
(container, { instanceIdentifier }) => {
31+
if (instanceIdentifier === undefined) {
32+
throw new Error('TelemetryService instance identifier is undefined');
33+
}
34+
35+
const options = decodeInstanceIdentifier(instanceIdentifier);
3136
// TODO: change to default endpoint once it exists
32-
const endpointUrl = options?.endpointUrl || 'http://localhost';
37+
const endpointUrl = options.endpointUrl || 'http://localhost';
3338

3439
// getImmediate for FirebaseApp will always succeed
3540
const app = container.getProvider('app').getImmediate();
@@ -39,7 +44,7 @@ export function registerTelemetry(): void {
3944
return new TelemetryService(app, loggerProvider, appCheckProvider);
4045
},
4146
ComponentType.PUBLIC
42-
)
47+
).setMultipleInstances(true)
4348
);
4449

4550
registerVersion(name, version);

0 commit comments

Comments
 (0)