From cc759fc930d4619478776e8cb88d8ee29cca6a2b Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Tue, 15 Sep 2026 13:47:46 -0600 Subject: [PATCH 1/4] wip: api --- js/src/exports.ts | 7 ++- js/src/instrumentation/config.ts | 73 ++++++++++++++++++++++++++++++++ js/src/instrumentation/index.ts | 5 +++ 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/js/src/exports.ts b/js/src/exports.ts index 0d4d20b2f..d4e491dfe 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -373,6 +373,11 @@ export { braintrustFlueObserver, braintrustFlueInstrumentation, } from "./instrumentation"; -export type { InstrumentationConfig } from "./instrumentation"; +export type { + InstrumentationConfig, + InstrumentationContext, + SpanCustomizer, + SpanExportData, +} from "./instrumentation"; export { wrapElevenLabs } from "./wrappers/elevenlabs"; diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index 784ca7b2b..e973e7703 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -1,3 +1,68 @@ +import type { Span } from "../logger"; + +/** + * Borrowed provider objects used by an instrumentation to create a span. + * + * Do not mutate or retain this context or its values after the callback returns. + * Copy any data that needs to outlive the callback. + */ +export interface InstrumentationContext { + /** Stable instrumentation name, such as "openai" or "anthropic". */ + readonly name: string; + + /** + * Get an instrumentation-specific object, or undefined when unavailable. + * Key names and value types are defined by each instrumentation. + */ + get(key: string): unknown; +} + +/** + * One span's outgoing record, with lazy values resolved, before JSON + * serialization. This is an incremental update, not necessarily a complete + * span; a span can produce multiple records, including before it ends. + * + * All fields are available for inspection and mutation. Returned data must + * remain JSON-serializable. Identity and routing fields, including id, + * span_id, root_span_id, and span_parents, must not be changed or removed. + */ +export type SpanExportData = Record; + +/** + * Customize spans created by Braintrust instrumentation. + * + * Callbacks are synchronous and run in registration order. Exceptions are + * swallowed and processing continues without changing provider results or + * preventing span finalization. + * + * @remarks API declaration only; customizer execution is not implemented yet. + */ +export interface SpanCustomizer { + /** + * Called after final output, metrics, or error capture, immediately before + * the instrumented span ends. For streams, this is at termination, not when + * the provider returns an iterator. + * + * Use span.log() or span.setAttributes() to customize the span; do not end it. + * Earlier updates may already be uploaded, so this is not a redaction hook. + */ + onSpanEnding?(span: Span, ctx: InstrumentationContext): void; + + /** + * Inspect and transform a span's outgoing record before JSON serialization + * and upload. This can be used to redact sensitive data. + * + * Mutate the data in place and return it, or return a replacement record. + * Each customizer receives the previous customizer's returned record. + * A record must be returned; dropping records is not supported. + * + * Preserve identity and routing fields and return JSON-serializable data. + * Called once per outgoing record, not per transport retry. No provider + * context is retained for this callback. + */ + onSpanExport?(data: SpanExportData): SpanExportData; +} + export interface InstrumentationIntegrationsConfig { openai?: boolean; anthropic?: boolean; @@ -45,6 +110,14 @@ export interface InstrumentationConfig { * Set to false to disable instrumentation for that SDK. */ integrations?: InstrumentationIntegrationsConfig; + + /** + * Instrumentation-wide customizers, in callback execution order. + * Configure before instrumentation is enabled. + * + * @remarks API declaration only; these callbacks are not invoked yet. + */ + spanCustomizers?: readonly SpanCustomizer[]; } const envIntegrationAliases: Record< diff --git a/js/src/instrumentation/index.ts b/js/src/instrumentation/index.ts index a1dbd990c..1ec0f5fd7 100644 --- a/js/src/instrumentation/index.ts +++ b/js/src/instrumentation/index.ts @@ -45,3 +45,8 @@ export { // Configuration API export { configureInstrumentation } from "./registry"; export type { InstrumentationConfig } from "./registry"; +export type { + InstrumentationContext, + SpanCustomizer, + SpanExportData, +} from "./config"; From 368184d6350bdbbf10edc04df721cd769e68e903 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Tue, 15 Sep 2026 15:15:57 -0600 Subject: [PATCH 2/4] wip: impl --- js/CHANGELOG.md | 6 + js/src/instrumentation/README.md | 42 +++++++ js/src/instrumentation/config.ts | 9 +- js/src/instrumentation/registry.ts | 4 + js/src/logger.ts | 39 ++++--- js/src/span-customizer.test.ts | 176 +++++++++++++++++++++++++++++ js/src/span-customizer.ts | 30 +++++ 7 files changed, 288 insertions(+), 18 deletions(-) create mode 100644 js/src/span-customizer.test.ts create mode 100644 js/src/span-customizer.ts diff --git a/js/CHANGELOG.md b/js/CHANGELOG.md index e6f60a272..8760682c4 100644 --- a/js/CHANGELOG.md +++ b/js/CHANGELOG.md @@ -1,5 +1,11 @@ # braintrust +## Unreleased + +### Minor Changes + +- feat: Run configured `onSpanExport` customizers on incremental instrumentation span records, supporting field mutation, deletion, and replacement before export. + ## 3.32.0 ### Minor Changes diff --git a/js/src/instrumentation/README.md b/js/src/instrumentation/README.md index 26487ec26..48566ad3b 100644 --- a/js/src/instrumentation/README.md +++ b/js/src/instrumentation/README.md @@ -180,6 +180,48 @@ termination, and async context. - Use narrow vendored provider interfaces shared by wrappers and plugins. - Keep enable, disable, subscription, and patching behavior idempotent. +## Export Customizers + +Configure `spanCustomizers` through the standalone instrumentation entrypoint +before importing the main SDK, which enables instrumentation during platform +initialization. Use a bootstrap module before any auto-instrumentation preload +that initializes the SDK. Static imports of the main SDK are hoisted; use a +dynamic import after configuration: + +```ts +import { configureInstrumentation } from "braintrust/instrumentation"; + +configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + data.tags = ["reviewed"]; + if ("output" in data) data.output = "[redacted]"; + delete data.error; + return data; + }, + }, + ], +}); + +const { initLogger } = await import("braintrust"); +initLogger({ projectName: "my-project" }); +// Import and use instrumented provider SDKs here. +``` + +`onSpanExport` receives each incremental record from an instrumentation-created +span after lazy values resolve, before attachment processing, merging, masking, +and JSON serialization. It can run before the span ends; fields may be absent. +Ordinary manually created spans, dataset rows, and feedback are not customized. + +Callbacks run synchronously in registration order. Mutate and return the record, +or return a replacement for the next callback. Preserve identity and routing +fields and return JSON-serializable data. Exceptions are swallowed; remaining +customizers and export continue. Export retries reuse the transformed record +without invoking callbacks again. Configuration is shared across SDK bundles. + +`onSpanEnding` remains a declaration only and is not invoked yet. + ## Testing Test at the narrowest useful layers: diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index e973e7703..5abf5b86b 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -34,8 +34,6 @@ export type SpanExportData = Record; * Callbacks are synchronous and run in registration order. Exceptions are * swallowed and processing continues without changing provider results or * preventing span finalization. - * - * @remarks API declaration only; customizer execution is not implemented yet. */ export interface SpanCustomizer { /** @@ -45,6 +43,8 @@ export interface SpanCustomizer { * * Use span.log() or span.setAttributes() to customize the span; do not end it. * Earlier updates may already be uploaded, so this is not a redaction hook. + * + * @remarks API declaration only; this callback is not invoked yet. */ onSpanEnding?(span: Span, ctx: InstrumentationContext): void; @@ -58,7 +58,8 @@ export interface SpanCustomizer { * * Preserve identity and routing fields and return JSON-serializable data. * Called once per outgoing record, not per transport retry. No provider - * context is retained for this callback. + * context is retained for this callback. Runs before attachment processing, + * merging, and masking. */ onSpanExport?(data: SpanExportData): SpanExportData; } @@ -115,7 +116,7 @@ export interface InstrumentationConfig { * Instrumentation-wide customizers, in callback execution order. * Configure before instrumentation is enabled. * - * @remarks API declaration only; these callbacks are not invoked yet. + * Only onSpanExport is currently invoked; onSpanEnding is not implemented. */ spanCustomizers?: readonly SpanCustomizer[]; } diff --git a/js/src/instrumentation/registry.ts b/js/src/instrumentation/registry.ts index 0ed195ae8..1ed665a0a 100644 --- a/js/src/instrumentation/registry.ts +++ b/js/src/instrumentation/registry.ts @@ -13,6 +13,7 @@ import { type InstrumentationConfig, } from "./config"; import { GLOBAL_INSTRUMENTATION_HOOKS_PROTOCOL_VERSION } from "../global-instrumentation-hooks"; +import { setSpanCustomizers } from "../span-customizer"; export type { InstrumentationConfig } from "./config"; @@ -62,6 +63,9 @@ class PluginRegistry { return; } this.config = { ...this.config, ...config }; + if ("spanCustomizers" in config) { + setSpanCustomizers(config.spanCustomizers); + } } /** diff --git a/js/src/logger.ts b/js/src/logger.ts index 5db55e25d..3b10807fb 100644 --- a/js/src/logger.ts +++ b/js/src/logger.ts @@ -207,6 +207,7 @@ import { mergeSpanOriginContext, type SpanOriginEnvironment, } from "./span-origin"; +import { customizeSpanExport } from "./span-customizer"; // Manual type definition for inline attachments (not in generated_types) const InlineAttachmentReferenceSchema = z.object({ @@ -8140,6 +8141,7 @@ export class SpanImpl implements Span { private isMerge: boolean; private loggedEndTime: number | undefined; + private readonly isInstrumented: boolean; private propagatedEvent: StartSpanEventArgs | undefined; // For internal use only. @@ -8180,6 +8182,8 @@ export class SpanImpl implements Span { const instrumentationName = getSpanInstrumentationName(args) ?? INSTRUMENTATION_NAMES.BRAINTRUST_JS_LOGGER; + this.isInstrumented = + instrumentationName !== INSTRUMENTATION_NAMES.BRAINTRUST_JS_LOGGER; const spanAttributes = args.spanAttributes ?? {}; const rawEvent = args.event ?? {}; @@ -8347,21 +8351,28 @@ export class SpanImpl implements Span { ); } - const computeRecord = async () => ({ - ...partialRecord, - ...Object.fromEntries( - await Promise.all( - Object.entries(lazyInternalData).map(async ([key, value]) => [ - key, - await value.get(), - ]), + const computeRecord = async () => { + const record = { + ...partialRecord, + ...Object.fromEntries( + await Promise.all( + Object.entries(lazyInternalData).map(async ([key, value]) => [ + key, + await value.get(), + ]), + ), ), - ), - ...new SpanComponentsV3({ - object_type: this.parentObjectType, - object_id: await this.parentObjectId.get(), - }).objectIdFields(), - }); + ...new SpanComponentsV3({ + object_type: this.parentObjectType, + object_id: await this.parentObjectId.get(), + }).objectIdFields(), + }; + // Customize inside the memoized lazy value, before attachment processing, + // merging, and masking. Retries reuse the already-customized record. + return this.isInstrumented + ? (customizeSpanExport(record) as BackgroundLogEvent) + : record; + }; this._state.bgLogger().log([new LazyValue(computeRecord)]); } diff --git a/js/src/span-customizer.test.ts b/js/src/span-customizer.test.ts new file mode 100644 index 000000000..ad13e93a7 --- /dev/null +++ b/js/src/span-customizer.test.ts @@ -0,0 +1,176 @@ +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { + _exportsForTestingOnly, + initLogger, + type TestBackgroundLogger, +} from "./logger"; +import { configureInstrumentation, registry } from "./instrumentation/registry"; +import { configureNode } from "./node/config"; +import { + INSTRUMENTATION_NAMES, + withSpanInstrumentationName, +} from "./span-origin"; + +configureNode(); + +describe("onSpanExport", () => { + let memoryLogger: TestBackgroundLogger; + + beforeEach(async () => { + registry.disable(); + await _exportsForTestingOnly.simulateLoginForTests(); + memoryLogger = _exportsForTestingOnly.useTestBackgroundLogger(); + }); + + afterEach(() => { + configureInstrumentation({ spanCustomizers: [] }); + _exportsForTestingOnly.clearTestBackgroundLogger(); + }); + + function startInstrumentedSpan() { + return initLogger({ + projectName: "customizer-project", + projectId: "customizer-project", + }).startSpan( + withSpanInstrumentationName( + { name: "provider.call" }, + INSTRUMENTATION_NAMES.OPENAI, + ), + ); + } + + test("adds a field to outgoing span records", async () => { + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + data.custom_field = "added"; + return data; + }, + }, + ], + }); + + const span = startInstrumentedSpan(); + span.log({ output: "result" }); + span.end(); + + const events = await memoryLogger.drain(); + expect(events).toEqual([ + expect.objectContaining({ + id: span.id, + project_id: "customizer-project", + output: "result", + custom_field: "added", + }), + ]); + }); + + test("alters an existing field in outgoing span records", async () => { + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + if ("output" in data) data.output = "[redacted]"; + return data; + }, + }, + ], + }); + + const span = startInstrumentedSpan(); + span.log({ output: "sensitive response" }); + span.end(); + + expect(await memoryLogger.drain()).toEqual([ + expect.objectContaining({ id: span.id, output: "[redacted]" }), + ]); + }); + + test("deletes a field from outgoing span records", async () => { + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + delete data.error; + return data; + }, + }, + ], + }); + + const span = startInstrumentedSpan(); + span.log({ error: "sensitive error", output: "safe response" }); + span.end(); + + const events = await memoryLogger.drain(); + expect(events).toEqual([ + expect.objectContaining({ id: span.id, output: "safe response" }), + ]); + expect(events[0]).not.toHaveProperty("error"); + }); + + test("passes replacement records through later customizers despite errors", async () => { + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + return "output" in data ? { ...data, output: "replacement" } : data; + }, + }, + { + onSpanExport() { + throw new Error("customizer failed"); + }, + }, + { + onSpanExport(data) { + if (typeof data.output === "string") { + data.output = data.output.toUpperCase(); + } + return data; + }, + }, + ], + }); + + const span = startInstrumentedSpan(); + span.log({ output: "original" }); + span.end(); + + expect(await memoryLogger.drain()).toEqual([ + expect.objectContaining({ + id: span.id, + output: "REPLACEMENT", + metrics: expect.objectContaining({ end: expect.any(Number) }), + }), + ]); + }); + + test("does not customize manually created spans", async () => { + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(data) { + data.tags = ["customized"]; + return data; + }, + }, + ], + }); + + const instrumented = startInstrumentedSpan(); + const manual = instrumented.startSpan({ name: "manual child" }); + manual.log({ output: "manual result" }); + manual.end(); + instrumented.end(); + + const events = await memoryLogger.drain(); + expect(events.find((event) => event.id === instrumented.id)).toMatchObject({ + tags: ["customized"], + }); + const manualEvent = events.find((event) => event.id === manual.id); + expect(manualEvent).toMatchObject({ output: "manual result" }); + expect(manualEvent).not.toHaveProperty("tags"); + }); +}); diff --git a/js/src/span-customizer.ts b/js/src/span-customizer.ts new file mode 100644 index 000000000..81925c9f7 --- /dev/null +++ b/js/src/span-customizer.ts @@ -0,0 +1,30 @@ +import type { SpanCustomizer, SpanExportData } from "./instrumentation/config"; + +// Configuration can precede platform initialization and must be shared across +// SDK bundles without importing the provider plugin registry into the logger. +const SPAN_CUSTOMIZERS_KEY = Symbol.for("braintrust.spanCustomizers"); +const shared: typeof globalThis & { + [SPAN_CUSTOMIZERS_KEY]?: readonly SpanCustomizer[]; +} = globalThis; + +export function setSpanCustomizers( + customizers: readonly SpanCustomizer[] | undefined, +): void { + shared[SPAN_CUSTOMIZERS_KEY] = customizers; +} + +export function customizeSpanExport(data: SpanExportData): SpanExportData { + const customizers = shared[SPAN_CUSTOMIZERS_KEY]; + if (!customizers) return data; + + for (const customizer of customizers) { + try { + if (customizer.onSpanExport) { + data = customizer.onSpanExport(data); + } + } catch { + // Customization must not prevent export or later customizers from running. + } + } + return data; +} From 295b449e35036defa63e0d284cc58ee7ef6d1f8e Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Tue, 15 Sep 2026 17:47:00 -0600 Subject: [PATCH 3/4] wip --- js/src/instrumentation/config.ts | 51 ++++++++++---------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index 5abf5b86b..ae31f5012 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -1,40 +1,26 @@ import type { Span } from "../logger"; -/** - * Borrowed provider objects used by an instrumentation to create a span. - * - * Do not mutate or retain this context or its values after the callback returns. - * Copy any data that needs to outlive the callback. - */ +/** information used by braintrust instrumentation to create a span */ export interface InstrumentationContext { - /** Stable instrumentation name, such as "openai" or "anthropic". */ + /** + * name of the instrumentation that produced a span. e.g. `langchain`, `openai`, etc + * + * this is the same value as `span_origin.instrumentation.name` on the exported span + */ readonly name: string; /** - * Get an instrumentation-specific object, or undefined when unavailable. - * Key names and value types are defined by each instrumentation. + * get a specific object by name used to gather instrumentation data + * + * for example: get("request") could return `OpenAIRequest request` + * + * NOTE: the exact key names and their values are specific to the instrumentation */ get(key: string): unknown; } -/** - * One span's outgoing record, with lazy values resolved, before JSON - * serialization. This is an incremental update, not necessarily a complete - * span; a span can produce multiple records, including before it ends. - * - * All fields are available for inspection and mutation. Returned data must - * remain JSON-serializable. Identity and routing fields, including id, - * span_id, root_span_id, and span_parents, must not be changed or removed. - */ export type SpanExportData = Record; -/** - * Customize spans created by Braintrust instrumentation. - * - * Callbacks are synchronous and run in registration order. Exceptions are - * swallowed and processing continues without changing provider results or - * preventing span finalization. - */ export interface SpanCustomizer { /** * Called after final output, metrics, or error capture, immediately before @@ -42,24 +28,17 @@ export interface SpanCustomizer { * the provider returns an iterator. * * Use span.log() or span.setAttributes() to customize the span; do not end it. - * Earlier updates may already be uploaded, so this is not a redaction hook. * * @remarks API declaration only; this callback is not invoked yet. */ - onSpanEnding?(span: Span, ctx: InstrumentationContext): void; + onBraintrustSpanEnding?(span: Span, ctx: InstrumentationContext): void; /** - * Inspect and transform a span's outgoing record before JSON serialization - * and upload. This can be used to redact sensitive data. + * Hook a span's outgoing record, with lazy values resolved, before JSON serialization * - * Mutate the data in place and return it, or return a replacement record. - * Each customizer receives the previous customizer's returned record. - * A record must be returned; dropping records is not supported. + * You may add/remove/delete most fields on the span. * - * Preserve identity and routing fields and return JSON-serializable data. - * Called once per outgoing record, not per transport retry. No provider - * context is retained for this callback. Runs before attachment processing, - * merging, and masking. + * The follow fields may NOT be altered: trace id, span id, parent id */ onSpanExport?(data: SpanExportData): SpanExportData; } From db9a1edad72778ceed7e88fd0110043c8118c287 Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Wed, 16 Sep 2026 21:44:55 -0600 Subject: [PATCH 4/4] wip --- js/src/exports.ts | 1 - js/src/instrumentation/README.md | 3 ++- js/src/instrumentation/config.ts | 43 ++++---------------------------- js/src/instrumentation/index.ts | 6 +---- js/src/span-customizer.test.ts | 16 +++++++++++- 5 files changed, 23 insertions(+), 46 deletions(-) diff --git a/js/src/exports.ts b/js/src/exports.ts index d4e491dfe..b122a7e56 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -375,7 +375,6 @@ export { } from "./instrumentation"; export type { InstrumentationConfig, - InstrumentationContext, SpanCustomizer, SpanExportData, } from "./instrumentation"; diff --git a/js/src/instrumentation/README.md b/js/src/instrumentation/README.md index 48566ad3b..b07fcaaf1 100644 --- a/js/src/instrumentation/README.md +++ b/js/src/instrumentation/README.md @@ -220,7 +220,8 @@ fields and return JSON-serializable data. Exceptions are swallowed; remaining customizers and export continue. Export retries reuse the transformed record without invoking callbacks again. Configuration is shared across SDK bundles. -`onSpanEnding` remains a declaration only and is not invoked yet. +Customizers receive only the outgoing record, not a live span or provider +instrumentation context. ## Testing diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index ae31f5012..8236a50bd 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -1,44 +1,13 @@ -import type { Span } from "../logger"; - -/** information used by braintrust instrumentation to create a span */ -export interface InstrumentationContext { - /** - * name of the instrumentation that produced a span. e.g. `langchain`, `openai`, etc - * - * this is the same value as `span_origin.instrumentation.name` on the exported span - */ - readonly name: string; - - /** - * get a specific object by name used to gather instrumentation data - * - * for example: get("request") could return `OpenAIRequest request` - * - * NOTE: the exact key names and their values are specific to the instrumentation - */ - get(key: string): unknown; -} - export type SpanExportData = Record; export interface SpanCustomizer { /** - * Called after final output, metrics, or error capture, immediately before - * the instrumented span ends. For streams, this is at termination, not when - * the provider returns an iterator. - * - * Use span.log() or span.setAttributes() to customize the span; do not end it. + * Customize an outgoing span record after lazy values resolve, before JSON + * serialization. Records are incremental and may not contain every span field. * - * @remarks API declaration only; this callback is not invoked yet. - */ - onBraintrustSpanEnding?(span: Span, ctx: InstrumentationContext): void; - - /** - * Hook a span's outgoing record, with lazy values resolved, before JSON serialization - * - * You may add/remove/delete most fields on the span. - * - * The follow fields may NOT be altered: trace id, span id, parent id + * Add, change, or delete fields, then return the record or a replacement. + * Preserve identity and routing fields, including id, span_id, root_span_id, + * and span_parents. */ onSpanExport?(data: SpanExportData): SpanExportData; } @@ -94,8 +63,6 @@ export interface InstrumentationConfig { /** * Instrumentation-wide customizers, in callback execution order. * Configure before instrumentation is enabled. - * - * Only onSpanExport is currently invoked; onSpanEnding is not implemented. */ spanCustomizers?: readonly SpanCustomizer[]; } diff --git a/js/src/instrumentation/index.ts b/js/src/instrumentation/index.ts index 1ec0f5fd7..833786cc5 100644 --- a/js/src/instrumentation/index.ts +++ b/js/src/instrumentation/index.ts @@ -45,8 +45,4 @@ export { // Configuration API export { configureInstrumentation } from "./registry"; export type { InstrumentationConfig } from "./registry"; -export type { - InstrumentationContext, - SpanCustomizer, - SpanExportData, -} from "./config"; +export type { SpanCustomizer, SpanExportData } from "./config"; diff --git a/js/src/span-customizer.test.ts b/js/src/span-customizer.test.ts index ad13e93a7..21df7c70c 100644 --- a/js/src/span-customizer.test.ts +++ b/js/src/span-customizer.test.ts @@ -1,4 +1,11 @@ -import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { + afterEach, + beforeEach, + describe, + expect, + expectTypeOf, + test, +} from "vitest"; import { _exportsForTestingOnly, initLogger, @@ -10,9 +17,16 @@ import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, } from "./span-origin"; +import type { SpanCustomizer, SpanExportData } from "./exports"; configureNode(); +test("customizers expose only the outgoing-record export hook", () => { + expectTypeOf().toEqualTypeOf<{ + onSpanExport?(data: SpanExportData): SpanExportData; + }>(); +}); + describe("onSpanExport", () => { let memoryLogger: TestBackgroundLogger;