Skip to content

Commit 6bdabd4

Browse files
authored
Add client-aware telemetry tracer getter (#247)
1 parent 1ba0ae8 commit 6bdabd4

3 files changed

Lines changed: 179 additions & 2 deletions

File tree

src/extra/observability/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { getRegisteredTracerProvider, registerTracerProvider } from "./provider.
44
export {
55
configureTelemetry,
66
configureTelemetryForHook,
7+
getTelemetryTracer,
78
MISTRAL_OTLP_TRACES_ENDPOINT_ENV,
89
MISTRAL_SDK_TELEMETRY_ENV,
910
MISTRAL_TELEMETRY_BASE_URL,
@@ -17,3 +18,5 @@ export {
1718
type TelemetryProviderMode,
1819
type TelemetrySetting,
1920
} from "./telemetry.js";
21+
22+
export type { Tracer, TracerOptions, TracerProvider } from "@opentelemetry/api";

src/extra/observability/telemetry.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { TracerProvider } from "@opentelemetry/api";
1+
import {
2+
trace,
3+
type Tracer,
4+
type TracerOptions,
5+
type TracerProvider,
6+
} from "@opentelemetry/api";
27

38
import type { SDKOptions } from "../../lib/config.js";
49
import type { SecurityState } from "../../lib/security.js";
@@ -110,6 +115,20 @@ export async function setTracerProvider(
110115
return configureTelemetry(client, provider);
111116
}
112117

118+
export function getTelemetryTracer(
119+
client: ClientWithHooks,
120+
name: string,
121+
version?: string,
122+
options?: TracerOptions,
123+
): Tracer {
124+
const hook = getTracingHook(client);
125+
const providerMode = resolveMistralTelemetryEnv();
126+
return getClientTracerProvider(
127+
hook,
128+
providerMode === TELEMETRY_PROVIDER_GLOBAL,
129+
).getTracer(name, version, options);
130+
}
131+
113132
export async function configureTelemetryForHook(
114133
hook: TelemetryCapableTracingHook,
115134
context: TelemetryContext,
@@ -297,6 +316,24 @@ function isTelemetryCapableTracingHook(
297316
);
298317
}
299318

319+
function getClientTracerProvider(
320+
hook: TelemetryCapableTracingHook,
321+
usesGlobalProvider: boolean,
322+
): TracerProvider {
323+
if (hook.tracerProvider !== undefined) {
324+
return hook.tracerProvider;
325+
}
326+
327+
if (!usesGlobalProvider && !hook._telemetryUseGlobalProvider) {
328+
const registeredProvider = getRegisteredTracerProvider();
329+
if (registeredProvider !== undefined) {
330+
return registeredProvider;
331+
}
332+
}
333+
334+
return trace.getTracerProvider();
335+
}
336+
300337
function resolveTelemetryMode(value: boolean | string): TelemetryProviderMode | null {
301338
if (typeof value === "boolean") {
302339
return value ? TELEMETRY_PROVIDER_DEDICATED : null;

tests/extra/observability/telemetry.test.ts

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
import { trace, type Span, type TracerProvider } from "@opentelemetry/api";
1+
import {
2+
trace,
3+
type Span,
4+
type Tracer,
5+
type TracerOptions,
6+
type TracerProvider,
7+
} from "@opentelemetry/api";
28

39
import { Mistral } from "../../../src/index.js";
10+
import {
11+
getTelemetryTracer,
12+
registerTracerProvider,
13+
} from "../../../src/extra/observability/index.js";
414
import {
515
configureTelemetry,
616
configureTelemetryForHook,
@@ -19,6 +29,8 @@ type FakeProvider = TracerProvider & {
1929
shutdown: () => void;
2030
};
2131

32+
type NamedTracer = Tracer & { label: string };
33+
2234
function createSpan(): Span {
2335
const span = {
2436
spanContext: () => ({ traceId: "", spanId: "", traceFlags: 0 }),
@@ -50,6 +62,28 @@ function createProvider(): FakeProvider {
5062
return provider;
5163
}
5264

65+
function createNamedTracer(label: string): NamedTracer {
66+
return {
67+
label,
68+
startSpan: () => createSpan(),
69+
startActiveSpan: () => undefined as never,
70+
} as NamedTracer;
71+
}
72+
73+
function createNamedProvider(tracer: Tracer): TracerProvider {
74+
return {
75+
getTracer: vi.fn((
76+
_name: string,
77+
_version?: string,
78+
_options?: TracerOptions,
79+
) => tracer),
80+
} as TracerProvider;
81+
}
82+
83+
function expectTracerLabel(tracer: Tracer, label: string): void {
84+
expect((tracer as NamedTracer).label).toBe(label);
85+
}
86+
5387
function createTelemetryModuleLoader(exporterInstances: Array<{ config: unknown }>) {
5488
return vi.fn(async (specifier: string) => {
5589
if (specifier === "@opentelemetry/sdk-trace-base") {
@@ -140,6 +174,11 @@ async function withEnv<T>(
140174
}
141175
}
142176

177+
afterEach(() => {
178+
registerTracerProvider(undefined);
179+
vi.restoreAllMocks();
180+
});
181+
143182
describe("configureTelemetryForHook", () => {
144183
test("defaults to disabled when Mistral telemetry env is absent", async () => {
145184
await withEnv({ [MISTRAL_SDK_TELEMETRY_ENV]: undefined }, async () => {
@@ -318,6 +357,104 @@ describe("configureTelemetry", () => {
318357

319358
expect(hook.tracerProvider).toBe(provider);
320359
});
360+
361+
});
362+
363+
describe("getTelemetryTracer", () => {
364+
test("uses the provider configured on the client", async () => {
365+
const client = createClient();
366+
const clientTracer = createNamedTracer("client");
367+
const registeredTracer = createNamedTracer("registered");
368+
const provider = createNamedProvider(clientTracer);
369+
const registeredProvider = createNamedProvider(registeredTracer);
370+
const globalProviderSpy = vi.spyOn(trace, "getTracerProvider");
371+
registerTracerProvider(registeredProvider);
372+
373+
await configureTelemetry(client, provider);
374+
375+
const tracer = getTelemetryTracer(client, "my-agent");
376+
377+
expectTracerLabel(tracer, "client");
378+
expect(provider.getTracer).toHaveBeenCalledWith("my-agent", undefined, undefined);
379+
expect(registeredProvider.getTracer).not.toHaveBeenCalled();
380+
expect(globalProviderSpy).not.toHaveBeenCalled();
381+
});
382+
383+
test("explicit global provider mode bypasses the registered provider", async () => {
384+
const client = createClient();
385+
const registeredTracer = createNamedTracer("registered");
386+
const globalTracer = createNamedTracer("global");
387+
const registeredProvider = createNamedProvider(registeredTracer);
388+
const globalProvider = createNamedProvider(globalTracer);
389+
registerTracerProvider(registeredProvider);
390+
vi.spyOn(trace, "getTracerProvider").mockReturnValue(globalProvider);
391+
392+
await configureTelemetry(client, "global");
393+
394+
const tracer = getTelemetryTracer(client, "my-agent");
395+
396+
expectTracerLabel(tracer, "global");
397+
expect(registeredProvider.getTracer).not.toHaveBeenCalled();
398+
expect(globalProvider.getTracer).toHaveBeenCalledWith("my-agent", undefined, undefined);
399+
});
400+
401+
test("env global mode bypasses the registered provider before configuration", async () => {
402+
await withEnv({ [MISTRAL_SDK_TELEMETRY_ENV]: "global" }, async () => {
403+
const client = createClient();
404+
const registeredTracer = createNamedTracer("registered");
405+
const globalTracer = createNamedTracer("global");
406+
const registeredProvider = createNamedProvider(registeredTracer);
407+
const globalProvider = createNamedProvider(globalTracer);
408+
registerTracerProvider(registeredProvider);
409+
vi.spyOn(trace, "getTracerProvider").mockReturnValue(globalProvider);
410+
411+
const tracer = getTelemetryTracer(client, "my-agent");
412+
413+
expectTracerLabel(tracer, "global");
414+
expect(registeredProvider.getTracer).not.toHaveBeenCalled();
415+
expect(globalProvider.getTracer).toHaveBeenCalledWith("my-agent", undefined, undefined);
416+
});
417+
});
418+
419+
test("falls back to the registered provider when no client provider is configured", () => {
420+
const client = createClient();
421+
const registeredTracer = createNamedTracer("registered");
422+
const registeredProvider = createNamedProvider(registeredTracer);
423+
const globalProviderSpy = vi.spyOn(trace, "getTracerProvider");
424+
registerTracerProvider(registeredProvider);
425+
426+
const tracer = getTelemetryTracer(client, "my-agent");
427+
428+
expectTracerLabel(tracer, "registered");
429+
expect(registeredProvider.getTracer).toHaveBeenCalledWith("my-agent", undefined, undefined);
430+
expect(globalProviderSpy).not.toHaveBeenCalled();
431+
});
432+
433+
test("falls back to the global provider when no client or registered provider is configured", () => {
434+
const client = createClient();
435+
const globalTracer = createNamedTracer("global");
436+
const globalProvider = createNamedProvider(globalTracer);
437+
vi.spyOn(trace, "getTracerProvider").mockReturnValue(globalProvider);
438+
439+
const tracer = getTelemetryTracer(client, "my-agent");
440+
441+
expectTracerLabel(tracer, "global");
442+
expect(globalProvider.getTracer).toHaveBeenCalledWith("my-agent", undefined, undefined);
443+
});
444+
445+
test("passes custom tracer name, version, and options to the selected provider", async () => {
446+
const client = createClient();
447+
const clientTracer = createNamedTracer("client");
448+
const provider = createNamedProvider(clientTracer);
449+
const options = { schemaUrl: "https://schema.test/v1" };
450+
451+
await configureTelemetry(client, provider);
452+
453+
const tracer = getTelemetryTracer(client, "my-agent", "1.2.3", options);
454+
455+
expectTracerLabel(tracer, "client");
456+
expect(provider.getTracer).toHaveBeenCalledWith("my-agent", "1.2.3", options);
457+
});
321458
});
322459

323460
describe("_createTelemetryTracerProvider", () => {

0 commit comments

Comments
 (0)