diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 8856f96e408..b386b98ae0b 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "@firebase/component": "0.7.0", + "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/exporter-logs-otlp-http": "0.203.0", "@opentelemetry/resources": "2.0.1", @@ -54,7 +55,8 @@ }, "license": "Apache-2.0", "devDependencies": { - "@firebase/app": "0.14.1", + "@firebase/app": "0.14.2", + "@opentelemetry/sdk-trace-web": "2.1.0", "@rollup/plugin-json": "6.1.0", "rollup": "2.79.2", "rollup-plugin-replace": "2.2.0", diff --git a/packages/telemetry/src/api.test.ts b/packages/telemetry/src/api.test.ts index 206c0f693d8..92e76c12429 100644 --- a/packages/telemetry/src/api.test.ts +++ b/packages/telemetry/src/api.test.ts @@ -18,9 +18,18 @@ import { expect } from 'chai'; import { LoggerProvider } from '@opentelemetry/sdk-logs'; import { Telemetry } from './public-types'; +import { trace } from '@opentelemetry/api'; import { Logger, LogRecord, SeverityNumber } from '@opentelemetry/api-logs'; +import { + InMemorySpanExporter, + SimpleSpanProcessor, + WebTracerProvider +} from '@opentelemetry/sdk-trace-web'; import { captureError, flush } from './api'; +const PROJECT_ID = 'my-project'; +const APP_ID = 'my-appid'; + const emittedLogs: LogRecord[] = []; const fakeLoggerProvider = { @@ -43,8 +52,8 @@ const fakeTelemetry: Telemetry = { name: 'DEFAULT', automaticDataCollectionEnabled: true, options: { - projectId: 'my-project', - appId: 'my-appid' + projectId: PROJECT_ID, + appId: APP_ID } }, loggerProvider: fakeLoggerProvider @@ -97,7 +106,7 @@ 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.be.undefined; + expect(log.attributes).to.deep.equal({}); }); it('should capture an unknown error type correctly', () => { @@ -107,7 +116,35 @@ 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.be.undefined; + expect(log.attributes).to.deep.equal({}); + }); + + it('should propagate trace context', async () => { + const provider = new WebTracerProvider({ + spanProcessors: [new SimpleSpanProcessor(new InMemorySpanExporter())] + }); + provider.register(); + + trace.getTracer('test-tracer').startActiveSpan('test-span', span => { + const error = new Error('This is a test error'); + error.stack = '...stack trace...'; + error.name = 'TestError'; + + span.spanContext().traceId = 'my-trace'; + span.spanContext().spanId = 'my-span'; + + captureError(fakeTelemetry, error); + span.end(); + }); + + await provider.shutdown(); + + expect(emittedLogs[0].attributes).to.deep.equal({ + 'error.type': 'TestError', + 'error.stack': '...stack trace...', + 'logging.googleapis.com/trace': `projects/${PROJECT_ID}/traces/my-trace`, + 'logging.googleapis.com/spanId': `my-span` + }); }); }); diff --git a/packages/telemetry/src/api.ts b/packages/telemetry/src/api.ts index 3f771e2a4b9..9110310e1e5 100644 --- a/packages/telemetry/src/api.ts +++ b/packages/telemetry/src/api.ts @@ -19,7 +19,8 @@ import { _getProvider, FirebaseApp, getApp } from '@firebase/app'; import { TELEMETRY_TYPE } from './constants'; import { Telemetry } from './public-types'; import { Provider } from '@firebase/component'; -import { SeverityNumber } from '@opentelemetry/api-logs'; +import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs'; +import { trace } from '@opentelemetry/api'; import { TelemetryService } from './service'; declare module '@firebase/component' { @@ -63,24 +64,44 @@ export function getTelemetry(app: FirebaseApp = getApp()): Telemetry { */ export function captureError(telemetry: Telemetry, error: unknown): void { const logger = telemetry.loggerProvider.getLogger('error-logger'); + + const activeSpanContext = trace.getActiveSpan()?.spanContext(); + const traceAttributes = {} as AnyValueMap; + if (telemetry.app.options.projectId && activeSpanContext?.traceId) { + traceAttributes[ + 'logging.googleapis.com/trace' + ] = `projects/${telemetry.app.options.projectId}/traces/${activeSpanContext.traceId}`; + if (activeSpanContext?.spanId) { + traceAttributes['logging.googleapis.com/spanId'] = + activeSpanContext.spanId; + } + } + if (error instanceof Error) { logger.emit({ severityNumber: SeverityNumber.ERROR, body: error.message, attributes: { 'error.type': error.name || 'Error', - 'error.stack': error.stack || 'No stack trace available' + 'error.stack': error.stack || 'No stack trace available', + ...traceAttributes } }); } else if (typeof error === 'string') { logger.emit({ severityNumber: SeverityNumber.ERROR, - body: error + body: error, + attributes: { + ...traceAttributes + } }); } else { logger.emit({ severityNumber: SeverityNumber.ERROR, - body: `Unknown error type: ${typeof error}` + body: `Unknown error type: ${typeof error}`, + attributes: { + ...traceAttributes + } }); } } diff --git a/yarn.lock b/yarn.lock index 49b8eed4ed5..38577288673 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2515,7 +2515,7 @@ dependencies: "@opentelemetry/api" "^1.3.0" -"@opentelemetry/api@^1.3.0", "@opentelemetry/api@~1.9.0": +"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@~1.9.0": version "1.9.0" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== @@ -2527,6 +2527,13 @@ dependencies: "@opentelemetry/semantic-conventions" "^1.29.0" +"@opentelemetry/core@2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz#5539f04eb9e5245e000b0c3f77bdfaa07557e3a7" + integrity sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" + "@opentelemetry/exporter-logs-otlp-http@0.203.0": version "0.203.0" resolved "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.203.0.tgz#cdecb5c5b39561aa8520c8bb78347c6e11c91a81" @@ -2567,6 +2574,14 @@ "@opentelemetry/core" "2.0.1" "@opentelemetry/semantic-conventions" "^1.29.0" +"@opentelemetry/resources@2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.1.0.tgz#11772e732af4f27953cf55567a6630d8b4d8282d" + integrity sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw== + dependencies: + "@opentelemetry/core" "2.1.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + "@opentelemetry/sdk-logs@0.203.0": version "0.203.0" resolved "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.203.0.tgz#01bc7c0549929d2864af2ab0ba23fd5ce02b5b0a" @@ -2593,6 +2608,23 @@ "@opentelemetry/resources" "2.0.1" "@opentelemetry/semantic-conventions" "^1.29.0" +"@opentelemetry/sdk-trace-base@2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.1.0.tgz#9d31474824e9ed215f94bf71260d5321f64d402a" + integrity sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ== + dependencies: + "@opentelemetry/core" "2.1.0" + "@opentelemetry/resources" "2.1.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace-web@2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.1.0.tgz#5765729ad975a8611eb863d40778d644eda4ae54" + integrity sha512-2F6ZuZFmJg4CdhRPP8+60DkvEwGLCiU3ffAkgnnqe/ALGEBqGa0HrZaNWFGprXWVivrYHpXhr7AEfasgLZD71g== + dependencies: + "@opentelemetry/core" "2.1.0" + "@opentelemetry/sdk-trace-base" "2.1.0" + "@opentelemetry/semantic-conventions@1.36.0", "@opentelemetry/semantic-conventions@^1.29.0": version "1.36.0" resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.36.0.tgz#149449bd4df4d0464220915ad4164121e0d75d4d"