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/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/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts
index 6a6c68deebf..ece52760e84 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,24 @@ describe('Top level API', () => {
arrAttr: [1, 2, 3]
});
});
+
+ it('should use explicit app version when provided', () => {
+ const telemetry = new TelemetryService(
+ fakeTelemetry.app,
+ fakeTelemetry.loggerProvider
+ );
+ telemetry.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..c5848ac65cb 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 {
+ private _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;
+ }
}
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"