Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion common/api-review/crashlytics-angular.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export interface Crashlytics {
// @public
export interface CrashlyticsOptions {
appVersion?: string;
endpointUrl?: string;
loggingUrl?: string;
tracingUrl?: string;
}

// @public
Expand Down
3 changes: 2 additions & 1 deletion common/api-review/crashlytics-react-router.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export interface Crashlytics {
// @public
export interface CrashlyticsOptions {
appVersion?: string;
endpointUrl?: string;
loggingUrl?: string;
tracingUrl?: string;
}

// @public
Expand Down
3 changes: 2 additions & 1 deletion common/api-review/crashlytics-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export interface Crashlytics {
// @public
export interface CrashlyticsOptions {
appVersion?: string;
endpointUrl?: string;
loggingUrl?: string;
tracingUrl?: string;
}

// @public
Expand Down
3 changes: 2 additions & 1 deletion common/api-review/crashlytics.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export interface Crashlytics {
// @public
export interface CrashlyticsOptions {
appVersion?: string;
endpointUrl?: string;
loggingUrl?: string;
tracingUrl?: string;
}

// @public
Expand Down
6 changes: 5 additions & 1 deletion packages/crashlytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,15 @@
"@opentelemetry/api-logs": "0.203.0",
"@opentelemetry/core": "2.2.0",
"@opentelemetry/exporter-logs-otlp-http": "0.203.0",
"@opentelemetry/instrumentation": "0.55.0",
"@opentelemetry/instrumentation-document-load": "0.46.0",
"@opentelemetry/instrumentation-fetch": "0.55.0",
"@opentelemetry/instrumentation-user-interaction": "0.45.0",
"@opentelemetry/otlp-exporter-base": "0.205.0",
"@opentelemetry/otlp-transformer": "0.205.0",
"@opentelemetry/resources": "2.0.1",
"@opentelemetry/sdk-logs": "0.203.0",
"@opentelemetry/sdk-trace-web": "2.1.0",
"@opentelemetry/semantic-conventions": "1.36.0",
"tslib": "^2.1.0"
},
Expand All @@ -131,7 +136,6 @@
"@angular/router": "19.2.15",
"@firebase/app": "0.14.8",
"@firebase/util": "1.13.0",
"@opentelemetry/sdk-trace-web": "2.1.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
"@testing-library/dom": "10.4.1",
Expand Down
8 changes: 7 additions & 1 deletion packages/crashlytics/src/public-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ export interface CrashlyticsOptions {
* The URL for the endpoint to which Crashlytics data should be sent, in the OpenTelemetry format.
* By default, data will be sent to Firebase.
*/
endpointUrl?: string;
loggingUrl?: string;

/**
* The URL for the endpoint to which Crashlytics traces should be sent, in the OpenTelemetry format.
* By default, data will be sent to Firebase.
*/
tracingUrl?: string;

/**
* The version of the application. This should be a unique string that identifies the snapshot of
Expand Down
9 changes: 6 additions & 3 deletions packages/crashlytics/src/register.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { CRASHLYTICS_TYPE } from './constants';
import { name, version } from '../package.json';
import { CrashlyticsService } from './service';
import { createLoggerProvider } from './logging/logger-provider';
import { createTracingProvider } from './tracing/tracing-provider';

export function registerCrashlytics(): void {
_registerComponent(
Expand All @@ -34,13 +35,15 @@ export function registerCrashlytics(): void {
}

// TODO: change to default endpoint once it exists
const endpointUrl = instanceIdentifier || 'http://localhost';
const loggingUrl = instanceIdentifier || 'http://localhost';
const tracingUrl = instanceIdentifier || 'http://localhost';

// getImmediate for FirebaseApp will always succeed
const app = container.getProvider('app').getImmediate();
const loggerProvider = createLoggerProvider(app, endpointUrl);
const loggerProvider = createLoggerProvider(app, loggingUrl);
const tracingProvider = createTracingProvider(app, tracingUrl);

return new CrashlyticsService(app, loggerProvider);
return new CrashlyticsService(app, loggerProvider, tracingProvider);
},
ComponentType.PUBLIC
).setMultipleInstances(true)
Expand Down
18 changes: 15 additions & 3 deletions packages/crashlytics/src/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Component, ComponentType } from '@firebase/component';
import { name, version } from '../package.json';
import { CrashlyticsService } from './service';
import { createLoggerProvider } from './logging/logger-provider';
import { createTracingProvider } from './tracing/tracing-provider';
import { AppCheckProvider } from './logging/appcheck-provider';
import { InstallationIdProvider } from './logging/installation-id-provider';
import { CRASHLYTICS_TYPE } from './constants';
Expand All @@ -41,7 +42,8 @@ export function registerCrashlytics(): void {
}

// TODO: change to default endpoint once it exists
const endpointUrl = instanceIdentifier || 'http://localhost';
const loggingUrl = instanceIdentifier || 'http://localhost';
const tracingUrl = instanceIdentifier || 'http://localhost';

// getImmediate for FirebaseApp will always succeed
const app = container.getProvider('app').getImmediate();
Expand All @@ -55,12 +57,22 @@ export function registerCrashlytics(): void {
];
const loggerProvider = createLoggerProvider(
app,
endpointUrl,
loggingUrl,
dynamicHeaderProviders,
dynamicLogAttributeProviders
);

const crashlyticsService = new CrashlyticsService(app, loggerProvider);
const tracingProvider = createTracingProvider(
app,
tracingUrl,
[]
);

const crashlyticsService = new CrashlyticsService(
app,
loggerProvider,
tracingProvider
);

// Immediately track this as a new client session (if one doesn't exist yet)
if (!getSessionId()) {
Expand Down
7 changes: 6 additions & 1 deletion packages/crashlytics/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
import { _FirebaseService, FirebaseApp } from '@firebase/app';
import { Crashlytics, CrashlyticsOptions } from './public-types';
import { LoggerProvider } from '@opentelemetry/sdk-logs';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';

export class CrashlyticsService implements Crashlytics, _FirebaseService {
private _options?: CrashlyticsOptions;
private _frameworkAttributesProvider?: () => Record<string, string>;

constructor(public app: FirebaseApp, public loggerProvider: LoggerProvider) {}
constructor(
public app: FirebaseApp,
public loggerProvider: LoggerProvider,
public tracingProvider: WebTracerProvider
) { }

_delete(): Promise<void> {
return Promise.resolve();
Expand Down
140 changes: 140 additions & 0 deletions packages/crashlytics/src/tracing/tracing-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
WebTracerProvider,
BatchSpanProcessor,
SimpleSpanProcessor,
ConsoleSpanExporter,
ReadableSpan,
SpanExporter
} from '@opentelemetry/sdk-trace-web';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer';
import type { OTLPExporterConfigBase } from '@opentelemetry/otlp-exporter-base';
import {
OTLPExporterBase,
createOtlpNetworkExportDelegate
} from '@opentelemetry/otlp-exporter-base';
import {
CompositePropagator,
W3CTraceContextPropagator,
ExportResult
} from '@opentelemetry/core';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

import { FetchTransport } from '../logging/fetch-transport';
import { DynamicHeaderProvider } from '../types';
import { FirebaseApp } from '@firebase/app';

/**
* Create a tracing provider for the current execution environment.
*
* @internal
*/
export function createTracingProvider(
app: FirebaseApp,
endpointUrl: string,
dynamicHeaderProviders: DynamicHeaderProvider[] = []
): WebTracerProvider {
const { projectId, appId, apiKey } = app.options;

const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'firebase_telemetry_service',
'gcp.project_id': projectId,
'cloud.provider': 'gcp'
});

if (endpointUrl.endsWith('/')) {
endpointUrl = endpointUrl.slice(0, -1);
}

const otlpEndpoint = `${endpointUrl}/v1/projects/${projectId}/apps/${appId}/traces`;

const traceExporter = new OTLPTraceExporter(
{
url: otlpEndpoint,
headers: {
'Content-Type': 'application/json',
'X-Goog-User-Project': projectId || '',
...(apiKey ? { 'X-Goog-Api-Key': apiKey } : {})
}
},
dynamicHeaderProviders
);

const provider = new WebTracerProvider({
resource,
spanProcessors: [
new SimpleSpanProcessor(new ConsoleSpanExporter()),
new BatchSpanProcessor(traceExporter)
]
});

provider.register({
propagator: new CompositePropagator({
propagators: [new W3CTraceContextPropagator()]
})
});

registerInstrumentations({
instrumentations: [
new DocumentLoadInstrumentation(),
new FetchInstrumentation(),
new UserInteractionInstrumentation()
]
});

return provider;
}

/** OTLP exporter that uses custom FetchTransport. */
class OTLPTraceExporter
extends OTLPExporterBase<ReadableSpan[]>
implements SpanExporter {
constructor(
config: OTLPExporterConfigBase = {},
dynamicHeaderProviders: DynamicHeaderProvider[] = []
) {
super(
createOtlpNetworkExportDelegate(
{
timeoutMillis: 10000,
concurrencyLimit: 5,
compression: 'none'
},
JsonTraceSerializer,
new FetchTransport({
url: config.url!,
headers: new Headers(config.headers),
dynamicHeaderProviders
})
)
);
}

override export(
spans: ReadableSpan[],
resultCallback: (result: ExportResult) => void
): void {
super.export(spans, resultCallback);
}
}
Loading