Skip to content

Commit d27867d

Browse files
committed
some fixes
1 parent c0fff1b commit d27867d

4 files changed

Lines changed: 102 additions & 87 deletions

File tree

dev-packages/e2e-tests/test-applications/node-firebase/package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"test": "playwright test",
1212
"clean": "npx rimraf node_modules **/node_modules pnpm-lock.yaml **/dist *-debug.log test-results",
1313
"test:build": "pnpm install && pnpm build",
14-
"test:assert": "pnpm firebase emulators:exec --project demo-functions 'pnpm test' && E2E_ORCHESTRION=true pnpm firebase emulators:exec --project demo-functions 'pnpm test tests/transactions.test.ts'"
14+
"test:assert": "pnpm firebase emulators:exec --project demo-functions 'pnpm test'",
15+
"test:assert:orchestrion": "E2E_ORCHESTRION=true pnpm test:assert"
1516
},
1617
"dependencies": {
1718
"@types/node": "^22.13.14",
@@ -26,5 +27,13 @@
2627
},
2728
"volta": {
2829
"extends": "../../package.json"
30+
},
31+
"sentryTest": {
32+
"variants": [
33+
{
34+
"assert-command": "pnpm test:assert:orchestrion",
35+
"label": "node-firebase (Orchestrion)"
36+
}
37+
]
2938
}
3039
}

packages/server-utils/src/integrations/tracing-channel/firebase/functions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FAAS_NAME, FAAS_TRIGGER } from '@sentry/conventions/attributes';
12
import type { SpanAttributes } from '@sentry/core';
23
import {
34
captureException,
@@ -53,8 +54,8 @@ function wrapHandler(handler: Handler, triggerType: string): Handler {
5354

5455
const attributes: SpanAttributes = {
5556
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: FUNCTIONS_ORIGIN,
56-
'faas.name': functionName,
57-
'faas.trigger': triggerType,
57+
[FAAS_NAME]: functionName,
58+
[FAAS_TRIGGER]: triggerType,
5859
'faas.provider': 'firebase',
5960
};
6061

packages/server-utils/src/integrations/tracing-channel/firebase/index.ts

Lines changed: 3 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,10 @@
11
import * as diagnosticsChannel from 'node:diagnostics_channel';
22
import type { IntegrationFn } from '@sentry/core';
3-
import { debug, defineIntegration, waitForTracingChannelBinding } from '@sentry/core';
4-
import { DEBUG_BUILD } from '../../../debug-build';
5-
import { CHANNELS } from '../../../orchestrion/channels';
6-
import { bindTracingChannelToSpan } from '../../../tracing-channel';
7-
import type { FirestoreReference } from './firestore-types';
8-
import { startFirestoreSpan } from './firestore';
9-
import { wrapFunctionsRegistration } from './functions';
3+
import { defineIntegration, waitForTracingChannelBinding } from '@sentry/core';
4+
import { instrumentFirebase } from './instrumentation';
105

11-
// NOTE: this uses the same name as the OTel integration by design. When enabled, the OTel 'Firebase'
12-
// integration is omitted from the default set.
136
const INTEGRATION_NAME = 'Firebase' as const;
147

15-
// The context orchestrion's transform attaches to each firestore channel: `arguments` is the live args
16-
// of the wrapped `addDoc`/`getDocs`/`setDoc`/`deleteDoc` call, `arguments[0]` the reference.
17-
interface FirestoreChannelContext {
18-
arguments: unknown[];
19-
self?: unknown;
20-
result?: unknown;
21-
error?: unknown;
22-
}
23-
24-
// The firestore operations, keyed by channel. `useParent` mirrors the OTel integration: `setDoc`/
25-
// `deleteDoc` take a *document* reference but the span is named after its parent *collection*.
26-
const FIRESTORE_OPERATIONS: Array<{ channel: string; spanName: string; useParent: boolean }> = [
27-
{ channel: CHANNELS.FIREBASE_FIRESTORE_ADD_DOC, spanName: 'addDoc', useParent: false },
28-
{ channel: CHANNELS.FIREBASE_FIRESTORE_GET_DOCS, spanName: 'getDocs', useParent: false },
29-
{ channel: CHANNELS.FIREBASE_FIRESTORE_SET_DOC, spanName: 'setDoc', useParent: true },
30-
{ channel: CHANNELS.FIREBASE_FIRESTORE_DELETE_DOC, spanName: 'deleteDoc', useParent: true },
31-
];
32-
33-
// The firebase-functions triggers, keyed by channel. The value is the faas trigger type used for the
34-
// span name (`firebase.function.<trigger>`) and `faas.trigger` attribute.
35-
const FUNCTIONS_TRIGGERS: Array<{ channel: string; triggerType: string }> = [
36-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_HTTP_REQUEST, triggerType: 'http.request' },
37-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_HTTP_CALL, triggerType: 'http.call' },
38-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_CREATED, triggerType: 'firestore.document.created' },
39-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_UPDATED, triggerType: 'firestore.document.updated' },
40-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_DELETED, triggerType: 'firestore.document.deleted' },
41-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_WRITTEN, triggerType: 'firestore.document.written' },
42-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_SCHEDULER, triggerType: 'scheduler.scheduled' },
43-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_FINALIZED, triggerType: 'storage.object.finalized' },
44-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_ARCHIVED, triggerType: 'storage.object.archived' },
45-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_DELETED, triggerType: 'storage.object.deleted' },
46-
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_METADATA_UPDATED, triggerType: 'storage.object.metadataUpdated' },
47-
];
48-
49-
const NOOP = (): void => {};
50-
51-
/**
52-
* Runs a span-building callback so a throw inside it can never break the user's firebase call: these run
53-
* inside the `tracingChannel(...)` machinery wrapping the real function, where an unguarded throw would
54-
* propagate into the traced call.
55-
*/
56-
function safe<T>(fn: () => T): T | undefined {
57-
try {
58-
return fn();
59-
} catch (error) {
60-
DEBUG_BUILD && debug.warn('[orchestrion:firebase] error handling channel event', error);
61-
return undefined;
62-
}
63-
}
64-
658
const _firebaseChannelIntegration = (() => {
669
return {
6710
name: INTEGRATION_NAME,
@@ -72,31 +15,7 @@ const _firebaseChannelIntegration = (() => {
7215
}
7316

7417
waitForTracingChannelBinding(() => {
75-
for (const { channel, spanName, useParent } of FIRESTORE_OPERATIONS) {
76-
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<FirestoreChannelContext>(channel), data =>
77-
safe(() => {
78-
const reference = data.arguments[0] as FirestoreReference | undefined;
79-
if (!reference) {
80-
return undefined;
81-
}
82-
const spanReference = useParent ? reference.parent || reference : reference;
83-
return startFirestoreSpan(spanName, spanReference);
84-
}),
85-
);
86-
}
87-
88-
for (const { channel, triggerType } of FUNCTIONS_TRIGGERS) {
89-
// Functions are wrapped, not span-bound: the handler runs long after this synchronous
90-
// registration call, so we only rewrap the handler argument here (in `start`) and open the
91-
// span inside that wrapper. The other lifecycle events are irrelevant, so no-op them.
92-
diagnosticsChannel.tracingChannel(channel).subscribe({
93-
start: data => void safe(() => wrapFunctionsRegistration(data as { arguments: unknown[] }, triggerType)),
94-
end: NOOP,
95-
asyncStart: NOOP,
96-
asyncEnd: NOOP,
97-
error: NOOP,
98-
});
99-
}
18+
instrumentFirebase();
10019
});
10120
},
10221
};
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as diagnosticsChannel from 'node:diagnostics_channel';
2+
import { debug } from '@sentry/core';
3+
import { DEBUG_BUILD } from '../../../debug-build';
4+
import { CHANNELS } from '../../../orchestrion/channels';
5+
import { bindTracingChannelToSpan } from '../../../tracing-channel';
6+
import type { FirestoreReference } from './firestore-types';
7+
import { startFirestoreSpan } from './firestore';
8+
import { wrapFunctionsRegistration } from './functions';
9+
10+
// The context orchestrion's transform attaches to each firestore channel: `arguments` is the live args
11+
// of the wrapped `addDoc`/`getDocs`/`setDoc`/`deleteDoc` call, `arguments[0]` the reference.
12+
interface FirestoreChannelContext {
13+
arguments: unknown[];
14+
self?: unknown;
15+
result?: unknown;
16+
error?: unknown;
17+
}
18+
19+
// The firestore operations, keyed by channel. `useParent` mirrors the OTel integration: `setDoc`/
20+
// `deleteDoc` take a *document* reference but the span is named after its parent *collection*.
21+
const FIRESTORE_OPERATIONS: Array<{ channel: string; spanName: string; useParent: boolean }> = [
22+
{ channel: CHANNELS.FIREBASE_FIRESTORE_ADD_DOC, spanName: 'addDoc', useParent: false },
23+
{ channel: CHANNELS.FIREBASE_FIRESTORE_GET_DOCS, spanName: 'getDocs', useParent: false },
24+
{ channel: CHANNELS.FIREBASE_FIRESTORE_SET_DOC, spanName: 'setDoc', useParent: true },
25+
{ channel: CHANNELS.FIREBASE_FIRESTORE_DELETE_DOC, spanName: 'deleteDoc', useParent: true },
26+
];
27+
28+
// The firebase-functions triggers, keyed by channel. The value is the faas trigger type used for the
29+
// span name (`firebase.function.<trigger>`) and `faas.trigger` attribute.
30+
const FUNCTIONS_TRIGGERS: Array<{ channel: string; triggerType: string }> = [
31+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_HTTP_REQUEST, triggerType: 'http.request' },
32+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_HTTP_CALL, triggerType: 'http.call' },
33+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_CREATED, triggerType: 'firestore.document.created' },
34+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_UPDATED, triggerType: 'firestore.document.updated' },
35+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_DELETED, triggerType: 'firestore.document.deleted' },
36+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_FIRESTORE_WRITTEN, triggerType: 'firestore.document.written' },
37+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_SCHEDULER, triggerType: 'scheduler.scheduled' },
38+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_FINALIZED, triggerType: 'storage.object.finalized' },
39+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_ARCHIVED, triggerType: 'storage.object.archived' },
40+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_DELETED, triggerType: 'storage.object.deleted' },
41+
{ channel: CHANNELS.FIREBASE_FUNCTIONS_STORAGE_METADATA_UPDATED, triggerType: 'storage.object.metadataUpdated' },
42+
];
43+
44+
const NOOP = (): void => {};
45+
46+
/**
47+
* Runs a span-building callback so a throw inside it can never break the user's firebase call: these run
48+
* inside the `tracingChannel(...)` machinery wrapping the real function, where an unguarded throw would
49+
* propagate into the traced call.
50+
*/
51+
function safe<T>(fn: () => T): T | undefined {
52+
try {
53+
return fn();
54+
} catch (error) {
55+
DEBUG_BUILD && debug.warn('[orchestrion:firebase] error handling channel event', error);
56+
return undefined;
57+
}
58+
}
59+
60+
export function instrumentFirebase() {
61+
for (const { channel, spanName, useParent } of FIRESTORE_OPERATIONS) {
62+
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<FirestoreChannelContext>(channel), data =>
63+
safe(() => {
64+
const reference = data.arguments[0] as FirestoreReference | undefined;
65+
if (!reference) {
66+
return undefined;
67+
}
68+
const spanReference = useParent ? reference.parent || reference : reference;
69+
return startFirestoreSpan(spanName, spanReference);
70+
}),
71+
);
72+
}
73+
74+
for (const { channel, triggerType } of FUNCTIONS_TRIGGERS) {
75+
// Functions are wrapped, not span-bound: the handler runs long after this synchronous
76+
// registration call, so we only rewrap the handler argument here (in `start`) and open the
77+
// span inside that wrapper. The other lifecycle events are irrelevant, so no-op them.
78+
diagnosticsChannel.tracingChannel(channel).subscribe({
79+
start: data => void safe(() => wrapFunctionsRegistration(data as { arguments: unknown[] }, triggerType)),
80+
end: NOOP,
81+
asyncStart: NOOP,
82+
asyncEnd: NOOP,
83+
error: NOOP,
84+
});
85+
}
86+
}

0 commit comments

Comments
 (0)