From d2aae747d6e547385ed002914bd8c45bdbe06ff4 Mon Sep 17 00:00:00 2001 From: abrook Date: Mon, 17 Nov 2025 13:23:18 -0500 Subject: [PATCH 1/3] Add option to explicitly set app version for telemetry --- common/api-review/telemetry-angular.api.md | 1 + common/api-review/telemetry-react.api.md | 1 + common/api-review/telemetry.api.md | 1 + docs-devsite/telemetry_.telemetryoptions.md | 11 ++++++ .../telemetry_angular.telemetryoptions.md | 11 ++++++ .../telemetry_react.telemetryoptions.md | 11 ++++++ packages/telemetry/src/api.test.ts | 33 +++++++++++++++--- packages/telemetry/src/api.ts | 34 +++++++++++-------- packages/telemetry/src/public-types.ts | 7 ++++ packages/telemetry/src/service.ts | 12 ++++++- 10 files changed, 103 insertions(+), 19 deletions(-) diff --git a/common/api-review/telemetry-angular.api.md b/common/api-review/telemetry-angular.api.md index b0078e25fc1..05ddf91ccdc 100644 --- a/common/api-review/telemetry-angular.api.md +++ b/common/api-review/telemetry-angular.api.md @@ -23,6 +23,7 @@ export interface Telemetry { // @public export interface TelemetryOptions { + appVersion?: string; endpointUrl?: string; } diff --git a/common/api-review/telemetry-react.api.md b/common/api-review/telemetry-react.api.md index 53e344cc1ab..dd5a5fe7a16 100644 --- a/common/api-review/telemetry-react.api.md +++ b/common/api-review/telemetry-react.api.md @@ -22,6 +22,7 @@ export interface Telemetry { // @public export interface TelemetryOptions { + appVersion?: string; endpointUrl?: string; } diff --git a/common/api-review/telemetry.api.md b/common/api-review/telemetry.api.md index 6a23ce03b80..38d4e62560f 100644 --- a/common/api-review/telemetry.api.md +++ b/common/api-review/telemetry.api.md @@ -31,6 +31,7 @@ export interface Telemetry { // @public export interface TelemetryOptions { + appVersion?: string; endpointUrl?: string; } diff --git a/docs-devsite/telemetry_.telemetryoptions.md b/docs-devsite/telemetry_.telemetryoptions.md index 27634878dab..5b660f597b1 100644 --- a/docs-devsite/telemetry_.telemetryoptions.md +++ b/docs-devsite/telemetry_.telemetryoptions.md @@ -22,8 +22,19 @@ export interface TelemetryOptions | Property | Type | Description | | --- | --- | --- | +| [appVersion](./telemetry_.telemetryoptions.md#telemetryoptionsappversion) | string | The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. | | [endpointUrl](./telemetry_.telemetryoptions.md#telemetryoptionsendpointurl) | string | The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. | +## TelemetryOptions.appVersion + +The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. + +Signature: + +```typescript +appVersion?: string; +``` + ## TelemetryOptions.endpointUrl The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. diff --git a/docs-devsite/telemetry_angular.telemetryoptions.md b/docs-devsite/telemetry_angular.telemetryoptions.md index 5a9075f49fa..5174ced70b2 100644 --- a/docs-devsite/telemetry_angular.telemetryoptions.md +++ b/docs-devsite/telemetry_angular.telemetryoptions.md @@ -22,8 +22,19 @@ export interface TelemetryOptions | Property | Type | Description | | --- | --- | --- | +| [appVersion](./telemetry_angular.telemetryoptions.md#telemetryoptionsappversion) | string | The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. | | [endpointUrl](./telemetry_angular.telemetryoptions.md#telemetryoptionsendpointurl) | string | The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. | +## TelemetryOptions.appVersion + +The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. + +Signature: + +```typescript +appVersion?: string; +``` + ## TelemetryOptions.endpointUrl The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. diff --git a/docs-devsite/telemetry_react.telemetryoptions.md b/docs-devsite/telemetry_react.telemetryoptions.md index fa3263d9160..3ec016896e2 100644 --- a/docs-devsite/telemetry_react.telemetryoptions.md +++ b/docs-devsite/telemetry_react.telemetryoptions.md @@ -22,8 +22,19 @@ export interface TelemetryOptions | Property | Type | Description | | --- | --- | --- | +| [appVersion](./telemetry_react.telemetryoptions.md#telemetryoptionsappversion) | string | The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. | | [endpointUrl](./telemetry_react.telemetryoptions.md#telemetryoptionsendpointurl) | string | The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. | +## TelemetryOptions.appVersion + +The version of the application. This should be a unique string that identifies the snapshot of code to be deployed, such as "1.0.2". If not specified, other default locations will be checked for an identifier. Setting a value here takes precedent over any other values. + +Signature: + +```typescript +appVersion?: string; +``` + ## TelemetryOptions.endpointUrl The URL for the endpoint to which telemetry data should be sent, in the Open Telemetry format. By default, data will be sent to Firebase. diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 6a6c68deebf..312bd05efaf 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -124,7 +124,8 @@ describe('Top level API', () => { expect(log.body).to.equal('This is a test error'); expect(log.attributes).to.deep.equal({ 'error.type': 'TestError', - 'error.stack': '...stack trace...' + 'error.stack': '...stack trace...', + 'app.version': 'unset' }); }); @@ -140,7 +141,8 @@ describe('Top level API', () => { expect(log.body).to.equal('error with no stack'); expect(log.attributes).to.deep.equal({ 'error.type': 'Error', - 'error.stack': 'No stack trace available' + 'error.stack': 'No stack trace available', + 'app.version': 'unset' }); }); @@ -151,7 +153,9 @@ describe('Top level API', () => { const log = emittedLogs[0]; expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('a string error'); - expect(log.attributes).to.deep.equal({}); + expect(log.attributes).to.deep.equal({ + 'app.version': 'unset' + }); }); it('should capture an unknown error type correctly', () => { @@ -161,7 +165,9 @@ describe('Top level API', () => { const log = emittedLogs[0]; expect(log.severityNumber).to.equal(SeverityNumber.ERROR); expect(log.body).to.equal('Unknown error type: number'); - expect(log.attributes).to.deep.equal({}); + expect(log.attributes).to.deep.equal({ + 'app.version': 'unset' + }); }); it('should propagate trace context', async () => { @@ -187,6 +193,7 @@ describe('Top level API', () => { expect(emittedLogs[0].attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', + 'app.version': 'unset', 'logging.googleapis.com/trace': `projects/${PROJECT_ID}/traces/my-trace`, 'logging.googleapis.com/spanId': `my-span` }); @@ -211,6 +218,7 @@ describe('Top level API', () => { expect(log.attributes).to.deep.equal({ 'error.type': 'TestError', 'error.stack': '...stack trace...', + 'app.version': 'unset', strAttr: 'string attribute', mapAttr: { boolAttr: true, @@ -219,6 +227,23 @@ describe('Top level API', () => { arrAttr: [1, 2, 3] }); }); + + it('should use explicit app version when provided', () => { + const telemetry = { + ...fakeTelemetry, + options: { + appVersion: '1.0.0' + } + }; + + captureError(telemetry, 'a string error'); + + expect(emittedLogs.length).to.equal(1); + const log = emittedLogs[0]; + expect(log.attributes).to.deep.equal({ + 'app.version': '1.0.0' + }); + }); }); describe('flush()', () => { diff --git a/packages/telemetry/src/api.ts b/packages/telemetry/src/api.ts index 0b93fe555fe..72b6c123e23 100644 --- a/packages/telemetry/src/api.ts +++ b/packages/telemetry/src/api.ts @@ -54,7 +54,13 @@ export function getTelemetry( TELEMETRY_TYPE ); const identifier = options?.endpointUrl || ''; - return telemetryProvider.getImmediate({ identifier }); + const telemetry: TelemetryService = telemetryProvider.getImmediate({ + identifier + }); + if (options) { + telemetry.options = options; + } + return telemetry; } /** @@ -72,20 +78,27 @@ export function captureError( attributes?: AnyValueMap ): void { const logger = telemetry.loggerProvider.getLogger('error-logger'); + const customAttributes = attributes || {}; + // Add trace metadata const activeSpanContext = trace.getActiveSpan()?.spanContext(); - const traceAttributes = {} as AnyValueMap; if (telemetry.app.options.projectId && activeSpanContext?.traceId) { - traceAttributes[ + customAttributes[ 'logging.googleapis.com/trace' ] = `projects/${telemetry.app.options.projectId}/traces/${activeSpanContext.traceId}`; if (activeSpanContext?.spanId) { - traceAttributes['logging.googleapis.com/spanId'] = + customAttributes['logging.googleapis.com/spanId'] = activeSpanContext.spanId; } } - const customAttributes = attributes || {}; + // Add app version metadata + let appVersion = 'unset'; + // TODO: implement app version fallback logic + if ((telemetry as TelemetryService).options?.appVersion) { + appVersion = (telemetry as TelemetryService).options!.appVersion!; + } + customAttributes['app.version'] = appVersion; if (error instanceof Error) { logger.emit({ @@ -94,7 +107,6 @@ export function captureError( attributes: { 'error.type': error.name || 'Error', 'error.stack': error.stack || 'No stack trace available', - ...traceAttributes, ...customAttributes } }); @@ -102,19 +114,13 @@ export function captureError( logger.emit({ severityNumber: SeverityNumber.ERROR, body: error, - attributes: { - ...traceAttributes, - ...customAttributes - } + attributes: customAttributes }); } else { logger.emit({ severityNumber: SeverityNumber.ERROR, body: `Unknown error type: ${typeof error}`, - attributes: { - ...traceAttributes, - ...customAttributes - } + attributes: customAttributes }); } } diff --git a/packages/telemetry/src/public-types.ts b/packages/telemetry/src/public-types.ts index 852c72221fd..0d807e84086 100644 --- a/packages/telemetry/src/public-types.ts +++ b/packages/telemetry/src/public-types.ts @@ -46,4 +46,11 @@ export interface TelemetryOptions { * By default, data will be sent to Firebase. */ endpointUrl?: string; + + /** + * The version of the application. This should be a unique string that identifies the snapshot of + * code to be deployed, such as "1.0.2". If not specified, other default locations will be checked + * for an identifier. Setting a value here takes precedent over any other values. + */ + appVersion?: string; } diff --git a/packages/telemetry/src/service.ts b/packages/telemetry/src/service.ts index e09e861d3dc..29a634d0c40 100644 --- a/packages/telemetry/src/service.ts +++ b/packages/telemetry/src/service.ts @@ -16,13 +16,23 @@ */ import { _FirebaseService, FirebaseApp } from '@firebase/app'; -import { Telemetry } from './public-types'; +import { Telemetry, TelemetryOptions } from './public-types'; import { LoggerProvider } from '@opentelemetry/sdk-logs'; export class TelemetryService implements Telemetry, _FirebaseService { + _options?: TelemetryOptions; + constructor(public app: FirebaseApp, public loggerProvider: LoggerProvider) {} _delete(): Promise { return Promise.resolve(); } + + set options(optionsToSet: TelemetryOptions) { + this._options = optionsToSet; + } + + get options(): TelemetryOptions | undefined { + return this._options; + } } From 248e648e872b690b4dc3ffc5cd7e22a787ceee02 Mon Sep 17 00:00:00 2001 From: abrook Date: Mon, 17 Nov 2025 14:05:42 -0500 Subject: [PATCH 2/3] update deps --- packages/telemetry/package.json | 2 +- yarn.lock | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 0393651a3a4..e18a2c8d2e6 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -107,7 +107,7 @@ "@angular/core": "19.2.15", "@angular/platform-browser": "19.2.15", "@angular/router": "19.2.15", - "@firebase/app": "0.14.4", + "@firebase/app": "0.14.6", "@opentelemetry/sdk-trace-web": "2.1.0", "@rollup/plugin-json": "6.1.0", "@testing-library/dom": "10.4.1", diff --git a/yarn.lock b/yarn.lock index c6f08a95361..a5e54f121ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1360,17 +1360,6 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@firebase/app@0.14.4": - version "0.14.4" - resolved "https://registry.npmjs.org/@firebase/app/-/app-0.14.4.tgz#1d2ce74c09752dec9664e2f981b20335c4efbec1" - integrity sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw== - dependencies: - "@firebase/component" "0.7.0" - "@firebase/logger" "0.5.0" - "@firebase/util" "1.13.0" - idb "7.1.1" - tslib "^2.1.0" - "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" From 07158a14dc127ca4468022f9470dafcfb51f27c3 Mon Sep 17 00:00:00 2001 From: abrook Date: Mon, 17 Nov 2025 14:09:46 -0500 Subject: [PATCH 3/3] misc cleanup --- packages/telemetry/src/api.test.ts | 11 ++++++----- packages/telemetry/src/service.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 312bd05efaf..ece52760e84 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -229,11 +229,12 @@ describe('Top level API', () => { }); it('should use explicit app version when provided', () => { - const telemetry = { - ...fakeTelemetry, - options: { - appVersion: '1.0.0' - } + const telemetry = new TelemetryService( + fakeTelemetry.app, + fakeTelemetry.loggerProvider + ); + telemetry.options = { + appVersion: '1.0.0' }; captureError(telemetry, 'a string error'); diff --git a/packages/telemetry/src/service.ts b/packages/telemetry/src/service.ts index 29a634d0c40..c5848ac65cb 100644 --- a/packages/telemetry/src/service.ts +++ b/packages/telemetry/src/service.ts @@ -20,7 +20,7 @@ import { Telemetry, TelemetryOptions } from './public-types'; import { LoggerProvider } from '@opentelemetry/sdk-logs'; export class TelemetryService implements Telemetry, _FirebaseService { - _options?: TelemetryOptions; + private _options?: TelemetryOptions; constructor(public app: FirebaseApp, public loggerProvider: LoggerProvider) {}