From 30ff5666f57a0042a45de11d287eb108cc66b524 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 16 Oct 2026 12:38:20 -0400 Subject: [PATCH 1/4] refactor: route SDK diagnostics through debugLogger --- js/dev/authorize.ts | 3 +- js/dev/errorHandler.ts | 3 +- js/dev/server.ts | 5 +- .../auto-instrumentations/bundler/plugin.ts | 3 +- .../bundler/webpack-loader.ts | 4 +- js/src/auto-instrumentations/hook.mts | 24 ++++----- .../auto-instrumentations/loader/cjs-patch.ts | 4 +- .../auto-instrumentations/loader/esm-hook.mts | 4 +- js/src/debug-logger.test.ts | 4 +- js/src/debug-logger.ts | 8 ++- js/src/eval-parameters.ts | 4 +- js/src/framework2.ts | 7 ++- .../core/stream-patcher.test.ts | 25 ++++++--- js/src/instrumentation/core/stream-patcher.ts | 51 +++++++++---------- .../instrumentation/plugins/ai-sdk-plugin.ts | 9 ++-- .../plugins/claude-agent-sdk-plugin.ts | 15 +++--- .../plugins/github-copilot-plugin.ts | 4 +- js/src/instrumentation/registry.test.ts | 20 ++++---- js/src/instrumentation/registry.ts | 5 +- js/src/prompt-cache/disk-cache.ts | 7 ++- js/src/span-customizer.test.ts | 4 +- js/src/span-customizer.ts | 6 +-- js/src/template/registry.ts | 4 +- .../ai-sdk/deprecated/wrapAISDKModel.ts | 4 +- js/src/wrappers/ai-sdk/telemetry.ts | 4 +- js/src/wrappers/anthropic.ts | 4 +- js/src/wrappers/bedrock-runtime.test.ts | 3 +- js/src/wrappers/bedrock-runtime.ts | 4 +- .../claude-agent-sdk/claude-agent-sdk.ts | 4 +- js/src/wrappers/cohere.test.ts | 3 +- js/src/wrappers/cohere.ts | 4 +- js/src/wrappers/cursor-sdk.test.ts | 3 +- js/src/wrappers/cursor-sdk.ts | 4 +- js/src/wrappers/genkit.ts | 4 +- js/src/wrappers/github-copilot.ts | 4 +- js/src/wrappers/google-adk.test.ts | 5 +- js/src/wrappers/google-adk.ts | 7 ++- js/src/wrappers/google-genai.ts | 7 ++- js/src/wrappers/groq.test.ts | 3 +- js/src/wrappers/groq.ts | 4 +- js/src/wrappers/huggingface.ts | 4 +- js/src/wrappers/langsmith.ts | 3 +- js/src/wrappers/mistral.ts | 4 +- js/src/wrappers/oai.ts | 6 ++- js/src/wrappers/openai-codex.ts | 4 +- js/src/wrappers/openrouter-agent.test.ts | 3 +- js/src/wrappers/openrouter-agent.ts | 4 +- js/src/wrappers/openrouter.test.ts | 3 +- js/src/wrappers/openrouter.ts | 4 +- js/src/wrappers/pi-coding-agent.test.ts | 3 +- js/src/wrappers/pi-coding-agent.ts | 7 ++- js/src/wrappers/shared/scorers.ts | 4 +- js/src/wrappers/strands-agent-sdk.test.ts | 3 +- js/src/wrappers/strands-agent-sdk.ts | 4 +- js/src/wrappers/typesafe.test.ts | 3 +- js/src/wrappers/typesafe.ts | 4 +- js/src/wrappers/vitest/index.ts | 4 +- js/src/wrappers/voyageai.test.ts | 3 +- js/src/wrappers/voyageai.ts | 4 +- 59 files changed, 201 insertions(+), 167 deletions(-) diff --git a/js/dev/authorize.ts b/js/dev/authorize.ts index 4f7592d17..4fd59b9cf 100644 --- a/js/dev/authorize.ts +++ b/js/dev/authorize.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../src/debug-logger"; import { Request, Response, NextFunction } from "express"; import { IncomingHttpHeaders } from "http"; import createError from "http-errors"; @@ -90,7 +91,7 @@ export function makeCheckAuthorized(allowedOrgName: string | undefined) { req.ctx.state = state; next(); } catch (e) { - console.error("Authorization error:", e); + debugLogger.error("Authorization error:", e); return next(createError(401, "Unauthorized")); } }; diff --git a/js/dev/errorHandler.ts b/js/dev/errorHandler.ts index 12cd15e24..395eb616e 100644 --- a/js/dev/errorHandler.ts +++ b/js/dev/errorHandler.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../src/debug-logger"; import { z } from "zod/v3"; import { Request, Response, ErrorRequestHandler, NextFunction } from "express"; import { HttpError } from "http-errors"; @@ -27,7 +28,7 @@ export const errorHandler: ErrorRequestHandler = ( return; } - console.error("Internal server error", err); + debugLogger.error("Internal server error", err); res.status(500).json({ error: { message: "Internal server error", diff --git a/js/dev/server.ts b/js/dev/server.ts index 3118e8252..8931e7c54 100644 --- a/js/dev/server.ts +++ b/js/dev/server.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../src/debug-logger"; import express, { NextFunction, Request, Response } from "express"; import cors from "cors"; import { @@ -165,7 +166,7 @@ export function runDevServer( try { await validateParameters(parameters ?? {}, evaluator.parameters); } catch (e) { - console.error("Error validating parameters", e); + debugLogger.error("Error validating parameters", e); if ( e instanceof z.ZodError || e instanceof ValidationError || @@ -280,7 +281,7 @@ export function runDevServer( res.json(summary.summary); } } catch (e) { - console.error("Error running eval", e); + debugLogger.error("Error running eval", e); if (stream) { res.write( serializeSSEEvent({ diff --git a/js/src/auto-instrumentations/bundler/plugin.ts b/js/src/auto-instrumentations/bundler/plugin.ts index 2c6d9a6d8..894d36927 100644 --- a/js/src/auto-instrumentations/bundler/plugin.ts +++ b/js/src/auto-instrumentations/bundler/plugin.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { createUnplugin } from "unplugin"; import { InstrumentationMatcher } from "../orchestrion-js"; import { isAbsolute } from "node:path"; @@ -74,7 +75,7 @@ export const unplugin = createUnplugin((options = {}) => { return transformBundledModule(code, filePath, matcher, browser); } catch (error) { // If transformation fails, warn and return original code - console.warn(`Code transformation failed for ${id}: ${error}`); + debugLogger.warn(`Code transformation failed for ${id}: ${error}`); return null; } }, diff --git a/js/src/auto-instrumentations/bundler/webpack-loader.ts b/js/src/auto-instrumentations/bundler/webpack-loader.ts index 6cb397208..3d0c30421 100644 --- a/js/src/auto-instrumentations/bundler/webpack-loader.ts +++ b/js/src/auto-instrumentations/bundler/webpack-loader.ts @@ -21,6 +21,8 @@ * ``` */ +import { debugLogger } from "../../debug-logger"; + import { InstrumentationMatcher } from "../orchestrion-js"; import { getDefaultAutoInstrumentationConfigs } from "../configs/all"; import { type BundlerPluginOptions } from "./plugin"; @@ -81,7 +83,7 @@ function codeTransformerLoader( callback(null, code, inputSourceMap); } } catch (error) { - console.warn( + debugLogger.warn( `[code-transformer-loader] Error transforming ${resourcePath}:`, error, ); diff --git a/js/src/auto-instrumentations/hook.mts b/js/src/auto-instrumentations/hook.mts index 8ea9de355..383f6d6de 100644 --- a/js/src/auto-instrumentations/hook.mts +++ b/js/src/auto-instrumentations/hook.mts @@ -13,6 +13,8 @@ * - CJS modules: Transformed via patching Module._compile */ +import { debugLogger } from "../debug-logger.js"; + import { register } from "node:module"; import { isInstrumentationIntegrationDisabled, @@ -57,20 +59,16 @@ if (!alreadyApplied) { try { patchCommonJS(allConfigs); - if (process.env.DEBUG === "@braintrust*" || process.env.DEBUG === "*") { - console.log( - "[Braintrust] Auto-instrumentation active (ESM + CJS) for:", - allConfigs.map((c) => c.channelName).join(", "), - ); - } + debugLogger.debug( + "Auto-instrumentation active (ESM + CJS) for:", + allConfigs.map((c) => c.channelName).join(", "), + ); } catch (err) { // CJS patch failed, but ESM hook is still active - if (process.env.DEBUG === "@braintrust*" || process.env.DEBUG === "*") { - console.log( - "[Braintrust] Auto-instrumentation active (ESM only) for:", - allConfigs.map((c) => c.channelName).join(", "), - ); - console.error("[Braintrust] CJS patch failed:", err); - } + debugLogger.debug( + "Auto-instrumentation active (ESM only) for:", + allConfigs.map((c) => c.channelName).join(", "), + ); + debugLogger.error("CJS patch failed:", err); } } diff --git a/js/src/auto-instrumentations/loader/cjs-patch.ts b/js/src/auto-instrumentations/loader/cjs-patch.ts index 1a87b87a2..ccd513f1e 100644 --- a/js/src/auto-instrumentations/loader/cjs-patch.ts +++ b/js/src/auto-instrumentations/loader/cjs-patch.ts @@ -3,6 +3,8 @@ * Patches Module.prototype._compile to transform CommonJS modules at load time. */ +import { debugLogger } from "../../debug-logger"; + import { InstrumentationMatcher, type InstrumentationConfig, @@ -48,7 +50,7 @@ export function patchCommonJS(instrumentations: InstrumentationConfig[]): void { try { source = transformer.transform(source, "unknown").code; } catch (error) { - console.warn(`Error transforming module ${filename}:`, error); + debugLogger.warn(`Error transforming module ${filename}:`, error); } } } diff --git a/js/src/auto-instrumentations/loader/esm-hook.mts b/js/src/auto-instrumentations/loader/esm-hook.mts index e966214e0..4e7f9d686 100644 --- a/js/src/auto-instrumentations/loader/esm-hook.mts +++ b/js/src/auto-instrumentations/loader/esm-hook.mts @@ -3,6 +3,8 @@ * This is used by Node.js --import to transform ES modules at load time. */ +import { debugLogger } from "../../debug-logger.js"; + import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { extname, sep } from "node:path"; @@ -89,7 +91,7 @@ export async function load(url: string, context: any, nextLoad: Function) { ).code; result.shortCircuit = true; } catch (err) { - console.warn(`Error transforming module ${url}:`, err); + debugLogger.warn(`Error transforming module ${url}:`, err); } } return result; diff --git a/js/src/debug-logger.test.ts b/js/src/debug-logger.test.ts index d5a369fe5..6a8df2a82 100644 --- a/js/src/debug-logger.test.ts +++ b/js/src/debug-logger.test.ts @@ -16,6 +16,7 @@ import { login, } from "./logger"; import { configureNode } from "./node/config"; +import iso from "./isomorph"; configureNode(); @@ -105,7 +106,8 @@ describe("debug logger", () => { expect(warnSpy).toHaveBeenCalledWith("[braintrust]", "global warning"); }); - test("BRAINTRUST_DEBUG_LOG_LEVEL accepts error, warn, info, and debug", () => { + test("BRAINTRUST_DEBUG_LOG_LEVEL works before platform initialization", () => { + vi.spyOn(iso, "getEnv").mockReturnValue(undefined); process.env.BRAINTRUST_DEBUG_LOG_LEVEL = "error"; expect(getEnvDebugLogLevel()).toBe("error"); diff --git a/js/src/debug-logger.ts b/js/src/debug-logger.ts index 24861c7f4..ae137ddaf 100644 --- a/js/src/debug-logger.ts +++ b/js/src/debug-logger.ts @@ -80,7 +80,13 @@ function parseDebugLogLevelEnv( } export function getEnvDebugLogLevel(): DebugLogLevel | undefined { - return parseDebugLogLevelEnv(iso.getEnv("BRAINTRUST_DEBUG_LOG_LEVEL")); + return parseDebugLogLevelEnv( + iso.getEnv("BRAINTRUST_DEBUG_LOG_LEVEL") ?? + // Loader and bundler diagnostics can run before platform initialization. + (typeof process !== "undefined" + ? process.env.BRAINTRUST_DEBUG_LOG_LEVEL + : undefined), + ); } export function setGlobalDebugLogLevel( diff --git a/js/src/eval-parameters.ts b/js/src/eval-parameters.ts index 7262cf2b7..bc18a64cc 100644 --- a/js/src/eval-parameters.ts +++ b/js/src/eval-parameters.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "./debug-logger"; import { z } from "zod/v3"; import * as z4 from "zod/v4"; import Ajv from "ajv"; @@ -125,8 +126,7 @@ function validateParametersWithZod< return [name, schema.parse(value)]; } } catch (e) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error validating parameter", name, e); + debugLogger.error("Error validating parameter", name, e); throw Error( `Invalid parameter '${name}': ${e instanceof Error ? e.message : String(e)}`, ); diff --git a/js/src/framework2.ts b/js/src/framework2.ts index 22fff00ac..e3401a0f7 100644 --- a/js/src/framework2.ts +++ b/js/src/framework2.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "./debug-logger"; import { _initializeSpanContext } from "./framework"; import type { Trace } from "./trace"; import iso from "./isomorph"; @@ -128,16 +129,14 @@ export class Project { async publish() { if (globalThis._lazy_load) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("publish() is a no-op when running `braintrust push`."); + debugLogger.warn("publish() is a no-op when running `braintrust push`."); return; } await login(); const projectMap = new ProjectNameIdMap(); const functionDefinitions: FunctionEvent[] = []; if (this._publishableCodeFunctions.length > 0) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "Code functions cannot be published directly. Use `braintrust push` instead.", ); } diff --git a/js/src/instrumentation/core/stream-patcher.test.ts b/js/src/instrumentation/core/stream-patcher.test.ts index d357169f1..195fe7435 100644 --- a/js/src/instrumentation/core/stream-patcher.test.ts +++ b/js/src/instrumentation/core/stream-patcher.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { describe, it, expect, vi } from "vitest"; import { isAsyncIterable, @@ -396,7 +397,7 @@ describe("patchStreamIfNeeded", () => { }, }); - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const onComplete = vi.fn(); const result = patchStreamIfNeeded(stream, { onComplete }); @@ -416,7 +417,7 @@ describe("patchStreamIfNeeded", () => { }, }); - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const onComplete = vi.fn(); const result = patchStreamIfNeeded(stream, { onComplete }); @@ -436,7 +437,9 @@ describe("patchStreamIfNeeded", () => { }, }; - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const errorSpy = vi + .spyOn(debugLogger, "error") + .mockImplementation(() => {}); const onComplete = vi.fn(() => { throw new Error("Handler error"); }); @@ -464,7 +467,9 @@ describe("patchStreamIfNeeded", () => { }, }; - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const errorSpy = vi + .spyOn(debugLogger, "error") + .mockImplementation(() => {}); const onChunk = vi.fn(() => { throw new Error("Chunk handler error"); }); @@ -493,7 +498,9 @@ describe("patchStreamIfNeeded", () => { }, }; - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const errorSpy = vi + .spyOn(debugLogger, "error") + .mockImplementation(() => {}); const onError = vi.fn(() => { throw new Error("Error handler error"); }); @@ -678,7 +685,9 @@ describe("wrapStreamResult", () => { }, }; - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const errorSpy = vi + .spyOn(debugLogger, "error") + .mockImplementation(() => {}); const processChunks = vi.fn(() => { throw new Error("Process error"); }); @@ -709,7 +718,9 @@ describe("wrapStreamResult", () => { it("should handle errors in onNonStream", () => { const result = { foo: "bar" }; - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const errorSpy = vi + .spyOn(debugLogger, "error") + .mockImplementation(() => {}); const processChunks = vi.fn(); const onNonStream = vi.fn(() => { throw new Error("Non-stream error"); diff --git a/js/src/instrumentation/core/stream-patcher.ts b/js/src/instrumentation/core/stream-patcher.ts index d9d77738a..a9aa483e7 100644 --- a/js/src/instrumentation/core/stream-patcher.ts +++ b/js/src/instrumentation/core/stream-patcher.ts @@ -152,8 +152,7 @@ export function patchStreamIfNeeded( // Check if object is extensible (can be patched) if (Object.isFrozen(stream) || Object.isSealed(stream)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "Cannot patch frozen/sealed stream. Stream output will not be collected.", ); return stream; @@ -222,8 +221,7 @@ export function patchStreamIfNeeded( try { await options.onComplete(chunks); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onComplete handler:", error); + debugLogger.error("Error in stream onComplete handler:", error); } } } else { @@ -239,8 +237,7 @@ export function patchStreamIfNeeded( try { await options.onChunk(chunk); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onChunk handler:", error); + debugLogger.error("Error in stream onChunk handler:", error); } } } @@ -257,8 +254,10 @@ export function patchStreamIfNeeded( chunks, ); } catch (handlerError) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onError handler:", handlerError); + debugLogger.error( + "Error in stream onError handler:", + handlerError, + ); } } } @@ -289,8 +288,10 @@ export function patchStreamIfNeeded( try { await options.onError(error, chunks); } catch (handlerError) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onError handler:", handlerError); + debugLogger.error( + "Error in stream onError handler:", + handlerError, + ); } } } @@ -305,8 +306,7 @@ export function patchStreamIfNeeded( patchAbortIfPresent(); return stream; } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Failed to patch stream iterator methods:", error); + debugLogger.warn("Failed to patch stream iterator methods:", error); } } @@ -340,8 +340,7 @@ export function patchStreamIfNeeded( try { await options.onComplete(chunks); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onComplete handler:", error); + debugLogger.error("Error in stream onComplete handler:", error); } } } else { @@ -361,8 +360,7 @@ export function patchStreamIfNeeded( try { await options.onChunk(chunk); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onChunk handler:", error); + debugLogger.error("Error in stream onChunk handler:", error); } } } @@ -380,8 +378,10 @@ export function patchStreamIfNeeded( chunks, ); } catch (handlerError) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onError handler:", handlerError); + debugLogger.error( + "Error in stream onError handler:", + handlerError, + ); } } } @@ -416,8 +416,10 @@ export function patchStreamIfNeeded( try { await options.onError(error, chunks); } catch (handlerError) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error in stream onError handler:", handlerError); + debugLogger.error( + "Error in stream onError handler:", + handlerError, + ); } } } @@ -440,8 +442,7 @@ export function patchStreamIfNeeded( return stream; } catch (error) { // If patching fails for any reason, log warning and return original - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Failed to patch stream:", error); + debugLogger.warn("Failed to patch stream:", error); return stream; } } @@ -765,8 +766,7 @@ export function wrapStreamResult( const processed = options.processChunks(chunks); options.onResult(processed); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error processing stream chunks:", error); + debugLogger.error("Error processing stream chunks:", error); if (options.onError) { options.onError( error instanceof Error ? error : new Error(String(error)), @@ -786,8 +786,7 @@ export function wrapStreamResult( : result; options.onResult(processed); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error processing non-stream result:", error); + debugLogger.error("Error processing non-stream result:", error); if (options.onError) { options.onError( error instanceof Error ? error : new Error(String(error)), diff --git a/js/src/instrumentation/plugins/ai-sdk-plugin.ts b/js/src/instrumentation/plugins/ai-sdk-plugin.ts index 267defb73..d45e0edb4 100644 --- a/js/src/instrumentation/plugins/ai-sdk-plugin.ts +++ b/js/src/instrumentation/plugins/ai-sdk-plugin.ts @@ -1906,8 +1906,7 @@ const processContentPart = (part: any): any => { } } } catch (error) { - // eslint-disable-next-line no-restricted-properties - console.warn("Error processing content part:", error); + debugLogger.warn("Error processing content part:", error); } return part; @@ -1956,8 +1955,7 @@ const convertImageToAttachment = ( return image; } } catch (error) { - // eslint-disable-next-line no-restricted-properties - console.warn("Error converting image to attachment:", error); + debugLogger.warn("Error converting image to attachment:", error); } return null; @@ -1996,8 +1994,7 @@ const convertDataToAttachment = ( }); } } catch (error) { - // eslint-disable-next-line no-restricted-properties - console.warn("Error converting data to attachment:", error); + debugLogger.warn("Error converting data to attachment:", error); } return null; diff --git a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts b/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts index f4321935a..17c2f1db9 100644 --- a/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts +++ b/js/src/instrumentation/plugins/claude-agent-sdk-plugin.ts @@ -1845,8 +1845,10 @@ export class ClaudeAgentSDKPlugin extends BasePlugin { metadata: filterSerializableOptions(options), }); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error extracting input for Claude Agent SDK:", error); + debugLogger.error( + "Error extracting input for Claude Agent SDK:", + error, + ); } const activeToolSpans = new Map(); @@ -1982,8 +1984,7 @@ export class ClaudeAgentSDKPlugin extends BasePlugin { state.processing = state.processing .then(() => handleStreamMessage(state, message)) .catch((error) => { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( + debugLogger.error( "Error processing Claude Agent SDK stream chunk:", error, ); @@ -2005,8 +2006,10 @@ export class ClaudeAgentSDKPlugin extends BasePlugin { try { state.span.log({ output: result }); } catch (error) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error("Error extracting output for Claude Agent SDK:", error); + debugLogger.error( + "Error extracting output for Claude Agent SDK:", + error, + ); } finally { state.span.end(); } diff --git a/js/src/instrumentation/plugins/github-copilot-plugin.ts b/js/src/instrumentation/plugins/github-copilot-plugin.ts index dcc172879..ad9a48266 100644 --- a/js/src/instrumentation/plugins/github-copilot-plugin.ts +++ b/js/src/instrumentation/plugins/github-copilot-plugin.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { SpanTypeAttribute } from "../../../util/index"; import type { Span } from "../../logger"; import { startSpan as startBaseSpan } from "../../logger"; @@ -650,8 +651,7 @@ function attachSessionEventListener( state.processing = state.processing .then(() => dispatchEvent(state, event)) .catch((err) => { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.error( + debugLogger.error( "[Braintrust] Error processing GitHub Copilot SDK event:", err, ); diff --git a/js/src/instrumentation/registry.test.ts b/js/src/instrumentation/registry.test.ts index fb8707de4..5736473a1 100644 --- a/js/src/instrumentation/registry.test.ts +++ b/js/src/instrumentation/registry.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { newGlobalInvocationHook } from "../global-instrumentation-hooks"; +import { debugLogger } from "../debug-logger"; vi.mock("../global-instrumentation-hooks", async (importOriginal) => ({ ...(await importOriginal()), newGlobalInvocationHook: vi.fn(), @@ -108,35 +109,32 @@ describe("Plugin Registry", () => { it("should warn if configureInstrumentation is called after enable", () => { const testRegistry = new (registry.constructor as any)(); - const warnSpy = [] as string[]; - const originalWarn = console.warn; - console.warn = (msg: string) => warnSpy.push(msg); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); try { testRegistry.enable(); testRegistry.configure({ integrations: { openai: false } }); - expect(warnSpy.length).toBeGreaterThan(0); - expect(warnSpy[0]).toContain("Cannot configure instrumentation"); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining("Cannot configure instrumentation"), + ); } finally { - console.warn = originalWarn; + warnSpy.mockRestore(); testRegistry.disable(); } }); it("should allow configuration before enable", () => { const testRegistry = new (registry.constructor as any)(); - const warnSpy = [] as string[]; - const originalWarn = console.warn; - console.warn = (msg: string) => warnSpy.push(msg); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); try { testRegistry.configure({ integrations: { openai: false } }); testRegistry.enable(); - expect(warnSpy.length).toBe(0); + expect(warnSpy).not.toHaveBeenCalled(); } finally { - console.warn = originalWarn; + warnSpy.mockRestore(); testRegistry.disable(); } }); diff --git a/js/src/instrumentation/registry.ts b/js/src/instrumentation/registry.ts index 9fbc5b79b..f03b40fb7 100644 --- a/js/src/instrumentation/registry.ts +++ b/js/src/instrumentation/registry.ts @@ -5,6 +5,8 @@ * Users can disable specific integrations programmatically or via environment variables. */ +import { debugLogger } from "../debug-logger"; + import { BraintrustPlugin } from "./braintrust-plugin"; import iso from "../isomorph"; import { @@ -55,8 +57,7 @@ class PluginRegistry { */ configure(config: InstrumentationConfig): void { if (this.enabled) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "Braintrust: Cannot configure instrumentation after it has been enabled. " + "Call configureInstrumentation() before importing any AI SDKs.", ); diff --git a/js/src/prompt-cache/disk-cache.ts b/js/src/prompt-cache/disk-cache.ts index 8d5dc256c..79ccd385f 100644 --- a/js/src/prompt-cache/disk-cache.ts +++ b/js/src/prompt-cache/disk-cache.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import iso from "../isomorph"; export function canUseDiskCache(): boolean { @@ -96,8 +97,7 @@ export class DiskCache { return undefined; } if (this.logWarnings) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Failed to read from disk cache", e); + debugLogger.warn("Failed to read from disk cache", e); } return undefined; } @@ -122,8 +122,7 @@ export class DiskCache { await this.evictOldestIfFull(); } catch (e) { if (this.logWarnings) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Failed to write to disk cache", e); + debugLogger.warn("Failed to write to disk cache", e); } return; } diff --git a/js/src/span-customizer.test.ts b/js/src/span-customizer.test.ts index 954d46c7a..22b47db40 100644 --- a/js/src/span-customizer.test.ts +++ b/js/src/span-customizer.test.ts @@ -48,6 +48,7 @@ describe("onSpanExport", () => { let memoryLogger: TestBackgroundLogger; beforeEach(async () => { + vi.stubEnv("BRAINTRUST_DEBUG_LOG_LEVEL", "error"); registry.disable(); await _exportsForTestingOnly.simulateLoginForTests(); memoryLogger = _exportsForTestingOnly.useTestBackgroundLogger(); @@ -863,7 +864,8 @@ describe("onSpanExport", () => { expect(errorLog).toHaveBeenCalledTimes(1); expect(errorLog.mock.calls).toEqual([ [ - "[braintrust] Span export customizer failed; exporting only span identity and duration.", + "[braintrust]", + "Span export customizer failed; exporting only span identity and duration.", ], ]); }); diff --git a/js/src/span-customizer.ts b/js/src/span-customizer.ts index a86eda4df..d0edbeccf 100644 --- a/js/src/span-customizer.ts +++ b/js/src/span-customizer.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "./debug-logger"; import { ARRAY_DELETE_FIELD, ID_FIELD, @@ -154,9 +155,8 @@ function registerCustomizerFailure(): void { if (shared[CUSTOMIZER_FAILURE_LOGGED_KEY]) return; shared[CUSTOMIZER_FAILURE_LOGGED_KEY] = true; // Never log the exception or record: either can contain sensitive data. - // eslint-disable-next-line no-restricted-properties -- intentional always-visible export failure. - console.error( - "[braintrust] Span export customizer failed; exporting only span identity and duration.", + debugLogger.error( + "Span export customizer failed; exporting only span identity and duration.", ); } diff --git a/js/src/template/registry.ts b/js/src/template/registry.ts index 6ebcace61..d9744e8a4 100644 --- a/js/src/template/registry.ts +++ b/js/src/template/registry.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { mustachePlugin } from "./plugins/mustache"; export type TemplateFormat = "mustache" | "nunjucks" | "none"; @@ -67,8 +68,7 @@ class TemplatePluginRegistry { register(plugin: TemplateRendererPlugin): void { if (this.plugins.has(plugin.name)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( `Template plugin '${plugin.name}' already registered, overwriting`, ); } diff --git a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts b/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts index 705048346..fcf90781c 100644 --- a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts +++ b/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../../debug-logger"; /* eslint-disable @typescript-eslint/consistent-type-assertions */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { startSpan } from "../../../logger"; @@ -29,8 +30,7 @@ export function wrapAISDKModel(model: T): T { ) { return new BraintrustLanguageModelWrapper(m) as any as T; } else { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported AI SDK model. Not wrapping."); + debugLogger.warn("Unsupported AI SDK model. Not wrapping."); return model; } } diff --git a/js/src/wrappers/ai-sdk/telemetry.ts b/js/src/wrappers/ai-sdk/telemetry.ts index 08f2989b8..12f60a9f2 100644 --- a/js/src/wrappers/ai-sdk/telemetry.ts +++ b/js/src/wrappers/ai-sdk/telemetry.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { isAutoInstrumentationSuppressed } from "../../instrumentation/auto-instrumentation-suppression"; import { SpanTypeAttribute, @@ -93,8 +94,7 @@ export function braintrustAISDKTelemetry(): any { try { callback(); } catch (error) { - // eslint-disable-next-line no-restricted-properties - console.error(`Error in Braintrust AI SDK telemetry ${name}:`, error); + debugLogger.error(`Error in Braintrust AI SDK telemetry ${name}:`, error); } }; diff --git a/js/src/wrappers/anthropic.ts b/js/src/wrappers/anthropic.ts index 43d1b6471..00515bd4e 100644 --- a/js/src/wrappers/anthropic.ts +++ b/js/src/wrappers/anthropic.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { anthropicChannels } from "../instrumentation/plugins/anthropic-interceptors"; import { TypedApplyProxy } from "../typed-instrumentation-helpers"; import type { @@ -38,8 +39,7 @@ export function wrapAnthropic(anthropic: T): T { return anthropicProxy(au as AnthropicClient) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Anthropic library. Not wrapping."); + debugLogger.warn("Unsupported Anthropic library. Not wrapping."); return anthropic; } diff --git a/js/src/wrappers/bedrock-runtime.test.ts b/js/src/wrappers/bedrock-runtime.test.ts index e2a8d606a..0da4dec39 100644 --- a/js/src/wrappers/bedrock-runtime.test.ts +++ b/js/src/wrappers/bedrock-runtime.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -56,7 +57,7 @@ describe("bedrock runtime wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapBedrockRuntime(invalid)).toBe(invalid); diff --git a/js/src/wrappers/bedrock-runtime.ts b/js/src/wrappers/bedrock-runtime.ts index 38efbf3ee..744eccde5 100644 --- a/js/src/wrappers/bedrock-runtime.ts +++ b/js/src/wrappers/bedrock-runtime.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { runWithAutoInstrumentationSuppressed } from "../instrumentation/auto-instrumentation-suppression"; import { bedrockRuntimeChannels } from "../instrumentation/plugins/bedrock-runtime-interceptors"; import { @@ -17,8 +18,7 @@ export function wrapBedrockRuntime(client: T): T { return bedrockRuntimeProxy(client) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving wrapper warning behavior. - console.warn("Unsupported Bedrock Runtime library. Not wrapping."); + debugLogger.warn("Unsupported Bedrock Runtime library. Not wrapping."); return client; } diff --git a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts index ac80bc297..1509da2e2 100644 --- a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts +++ b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { claudeAgentSDKChannels } from "../../instrumentation/plugins/claude-agent-sdk-interceptors"; import { CLAUDE_AGENT_SDK_SKIP_LOCAL_TOOL_HOOKS_OPTION } from "../../instrumentation/plugins/claude-agent-sdk-instrumentation-constants"; import { wrapLocalClaudeToolHandler } from "../../instrumentation/plugins/claude-agent-sdk-local-tool-spans"; @@ -31,8 +32,7 @@ export function wrapClaudeAgentSDK(sdk: T): T { return claudeAgentSDKProxy(s as ClaudeAgentSDKModule) as unknown as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Claude Agent SDK. Not wrapping."); + debugLogger.warn("Unsupported Claude Agent SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/cohere.test.ts b/js/src/wrappers/cohere.test.ts index 5c585b948..b871a3d07 100644 --- a/js/src/wrappers/cohere.test.ts +++ b/js/src/wrappers/cohere.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -40,7 +41,7 @@ describe("cohere wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapCohere(invalid)).toBe(invalid); diff --git a/js/src/wrappers/cohere.ts b/js/src/wrappers/cohere.ts index 7f3d84405..1297113c4 100644 --- a/js/src/wrappers/cohere.ts +++ b/js/src/wrappers/cohere.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { cohereChannels } from "../instrumentation/plugins/cohere-interceptors"; import type { CohereChatRequest, @@ -19,8 +20,7 @@ export function wrapCohere(cohere: T): T { return cohereProxy(cohere) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Cohere library. Not wrapping."); + debugLogger.warn("Unsupported Cohere library. Not wrapping."); return cohere; } diff --git a/js/src/wrappers/cursor-sdk.test.ts b/js/src/wrappers/cursor-sdk.test.ts index 8a63363a9..75a551ae5 100644 --- a/js/src/wrappers/cursor-sdk.test.ts +++ b/js/src/wrappers/cursor-sdk.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, describe, expect, it, vi } from "vitest"; const { invoke } = vi.hoisted(() => ({ @@ -23,7 +24,7 @@ describe("wrapCursorSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const sdk = { Cursor: {} }; expect(wrapCursorSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/cursor-sdk.ts b/js/src/wrappers/cursor-sdk.ts index 2188066e0..9a4f59866 100644 --- a/js/src/wrappers/cursor-sdk.ts +++ b/js/src/wrappers/cursor-sdk.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { cursorSDKChannels } from "../instrumentation/plugins/cursor-sdk-interceptors"; import type { CursorSDKAgent, @@ -21,8 +22,7 @@ export function wrapCursorSDK(sdk: T): T { const maybeSDK = sdk as Record; if (!maybeSDK.Agent || typeof maybeSDK.Agent !== "function") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Cursor SDK. Not wrapping."); + debugLogger.warn("Unsupported Cursor SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/genkit.ts b/js/src/wrappers/genkit.ts index eea037b25..00bcddf01 100644 --- a/js/src/wrappers/genkit.ts +++ b/js/src/wrappers/genkit.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { genkitChannels } from "../instrumentation/plugins/genkit-interceptors"; import type { GenkitAction, @@ -36,8 +37,7 @@ export function wrapGenkit(genkit: T): T { return wrapGenkitModule(genkit) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Genkit object. Not wrapping."); + debugLogger.warn("Unsupported Genkit object. Not wrapping."); return genkit; } diff --git a/js/src/wrappers/github-copilot.ts b/js/src/wrappers/github-copilot.ts index 249f9b71c..de80d17fe 100644 --- a/js/src/wrappers/github-copilot.ts +++ b/js/src/wrappers/github-copilot.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { gitHubCopilotChannels } from "../instrumentation/plugins/github-copilot-interceptors"; import type { GitHubCopilotClient, @@ -37,8 +38,7 @@ function isGitHubCopilotClient(value: unknown): value is GitHubCopilotClient { */ export function wrapCopilotClient(client: T): T { if (!isGitHubCopilotClient(client)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "[Braintrust] wrapCopilotClient: argument does not look like a CopilotClient. Not wrapping.", ); return client; diff --git a/js/src/wrappers/google-adk.test.ts b/js/src/wrappers/google-adk.test.ts index 77256f6ba..41a71f091 100644 --- a/js/src/wrappers/google-adk.test.ts +++ b/js/src/wrappers/google-adk.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { describe, it, expect, vi, afterEach } from "vitest"; const { invoke } = vi.hoisted(() => ({ @@ -23,7 +24,7 @@ describe("wrapGoogleADK", () => { }); it("should return the module unchanged if invalid", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const result = wrapGoogleADK(null as any); expect(result).toBeNull(); @@ -35,7 +36,7 @@ describe("wrapGoogleADK", () => { }); it("should return module unchanged if no Runner or LlmAgent found", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const fakeModule = { SomeOtherClass: class {} }; const result = wrapGoogleADK(fakeModule as any); diff --git a/js/src/wrappers/google-adk.ts b/js/src/wrappers/google-adk.ts index 4a22203f8..f40c49f0a 100644 --- a/js/src/wrappers/google-adk.ts +++ b/js/src/wrappers/google-adk.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { googleADKChannels } from "../instrumentation/plugins/google-adk-interceptors"; import type { GoogleADKBaseAgent, @@ -41,14 +42,12 @@ import type { */ export function wrapGoogleADK>(adkModule: T): T { if (!adkModule || typeof adkModule !== "object") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Invalid Google ADK module. Not wrapping."); + debugLogger.warn("Invalid Google ADK module. Not wrapping."); return adkModule; } if (!("Runner" in adkModule) && !("LlmAgent" in adkModule)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "Runner or LlmAgent class not found in module. Not wrapping. Make sure you're passing the module itself (import * as adk from '@google/adk').", ); return adkModule; diff --git a/js/src/wrappers/google-genai.ts b/js/src/wrappers/google-genai.ts index b4e0c5bb7..752f8b0d8 100644 --- a/js/src/wrappers/google-genai.ts +++ b/js/src/wrappers/google-genai.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { googleGenAIChannels } from "../instrumentation/plugins/google-genai-interceptors"; import { isObject } from "../util"; import type { @@ -37,14 +38,12 @@ export function wrapGoogleGenAI>( googleGenAI: T, ): T { if (!googleGenAI || typeof googleGenAI !== "object") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Invalid Google GenAI module. Not wrapping."); + debugLogger.warn("Invalid Google GenAI module. Not wrapping."); return googleGenAI; } if (!("GoogleGenAI" in googleGenAI)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "GoogleGenAI class not found in module. Not wrapping. Make sure you're passing the module itself (import * as googleGenAI from '@google/genai').", ); return googleGenAI; diff --git a/js/src/wrappers/groq.test.ts b/js/src/wrappers/groq.test.ts index d44daf491..1228b28ae 100644 --- a/js/src/wrappers/groq.test.ts +++ b/js/src/wrappers/groq.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -41,7 +42,7 @@ describe("groq wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapGroq(invalid)).toBe(invalid); diff --git a/js/src/wrappers/groq.ts b/js/src/wrappers/groq.ts index 6f71b7e76..f75847cc2 100644 --- a/js/src/wrappers/groq.ts +++ b/js/src/wrappers/groq.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { groqChannels } from "../instrumentation/plugins/groq-interceptors"; import type { GroqAudio, @@ -22,8 +23,7 @@ export function wrapGroq(groq: T): T { return groqProxy(groq) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Groq library. Not wrapping."); + debugLogger.warn("Unsupported Groq library. Not wrapping."); return groq; } diff --git a/js/src/wrappers/huggingface.ts b/js/src/wrappers/huggingface.ts index 80272ac56..200adcece 100644 --- a/js/src/wrappers/huggingface.ts +++ b/js/src/wrappers/huggingface.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { isObject } from "../../util"; import { huggingFaceChannels } from "../instrumentation/plugins/huggingface-interceptors"; import type { @@ -51,8 +52,7 @@ export function wrapHuggingFace(huggingFace: unknown): unknown { return clientProxy(huggingFace); } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported HuggingFace Inference SDK. Not wrapping."); + debugLogger.warn("Unsupported HuggingFace Inference SDK. Not wrapping."); return huggingFace; } diff --git a/js/src/wrappers/langsmith.ts b/js/src/wrappers/langsmith.ts index b5d0ab37f..6c834a6d8 100644 --- a/js/src/wrappers/langsmith.ts +++ b/js/src/wrappers/langsmith.ts @@ -74,8 +74,7 @@ function wrapNamespaceExport( return namespace; } if (typeof candidate[exportName] !== "function") { - // eslint-disable-next-line no-restricted-properties -- public wrapper diagnostics follow existing wrapper behavior. - console.warn( + debugLogger.warn( `Unsupported LangSmith ${exportName} namespace. Not wrapping.`, ); return namespace; diff --git a/js/src/wrappers/mistral.ts b/js/src/wrappers/mistral.ts index 9bdcece10..29c1b22d7 100644 --- a/js/src/wrappers/mistral.ts +++ b/js/src/wrappers/mistral.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { mistralChannels } from "../instrumentation/plugins/mistral-interceptors"; import type { MistralAgents, @@ -31,8 +32,7 @@ export function wrapMistral(mistral: T): T { return mistralProxy(mistral) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Mistral library. Not wrapping."); + debugLogger.warn("Unsupported Mistral library. Not wrapping."); return mistral; } diff --git a/js/src/wrappers/oai.ts b/js/src/wrappers/oai.ts index 787517a41..068d19b53 100644 --- a/js/src/wrappers/oai.ts +++ b/js/src/wrappers/oai.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import type { OpenAIMediaClient, OpenAIMediaMethod, @@ -68,8 +69,9 @@ export function wrapOpenAI(openai: T): T { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return wrapOpenAIv4(typedOpenAI) as T; } else { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported OpenAI library (potentially v3). Not wrapping."); + debugLogger.warn( + "Unsupported OpenAI library (potentially v3). Not wrapping.", + ); return openai; } } diff --git a/js/src/wrappers/openai-codex.ts b/js/src/wrappers/openai-codex.ts index 901bc855e..f743626ff 100644 --- a/js/src/wrappers/openai-codex.ts +++ b/js/src/wrappers/openai-codex.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { openAICodexChannels } from "../instrumentation/plugins/openai-codex-interceptors"; import type { OpenAICodexClass, @@ -28,8 +29,7 @@ export function wrapOpenAICodexSDK(sdk: T): T { } if (!maybeSDK.Codex || typeof maybeSDK.Codex !== "function") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported OpenAI Codex SDK. Not wrapping."); + debugLogger.warn("Unsupported OpenAI Codex SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/openrouter-agent.test.ts b/js/src/wrappers/openrouter-agent.test.ts index 069960ca3..b4e118220 100644 --- a/js/src/wrappers/openrouter-agent.test.ts +++ b/js/src/wrappers/openrouter-agent.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, describe, expect, it, vi } from "vitest"; import { wrapOpenRouterAgent } from "./openrouter-agent"; import { openRouterAgentChannels } from "../instrumentation/plugins/openrouter-agent-interceptors"; @@ -8,7 +9,7 @@ describe("wrapOpenRouterAgent", () => { }); it("returns the original value and warns for unsupported inputs", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const input = { notCallModel: true }; expect(wrapOpenRouterAgent(input as object)).toBe(input); diff --git a/js/src/wrappers/openrouter-agent.ts b/js/src/wrappers/openrouter-agent.ts index 0df5ed972..4bbe16dc1 100644 --- a/js/src/wrappers/openrouter-agent.ts +++ b/js/src/wrappers/openrouter-agent.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { openRouterAgentChannels } from "../instrumentation/plugins/openrouter-agent-interceptors"; import type { OpenRouterAgentCallModelRequest, @@ -19,8 +20,7 @@ export function wrapOpenRouterAgent(agent: T): T { return openRouterAgentProxy(candidate as OpenRouterAgentClient) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported OpenRouter Agent library. Not wrapping."); + debugLogger.warn("Unsupported OpenRouter Agent library. Not wrapping."); return agent; } diff --git a/js/src/wrappers/openrouter.test.ts b/js/src/wrappers/openrouter.test.ts index 4ee88ea24..3f756aafb 100644 --- a/js/src/wrappers/openrouter.test.ts +++ b/js/src/wrappers/openrouter.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -49,7 +50,7 @@ describe("openrouter wrapper", () => { }); test("returns the original object for unsupported clients", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapOpenRouter(invalid)).toBe(invalid); diff --git a/js/src/wrappers/openrouter.ts b/js/src/wrappers/openrouter.ts index 7bc2abd06..47f180762 100644 --- a/js/src/wrappers/openrouter.ts +++ b/js/src/wrappers/openrouter.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { openRouterChannels } from "../instrumentation/plugins/openrouter-interceptors"; import type { OpenRouterBeta, @@ -43,8 +44,7 @@ export function wrapOpenRouter(openrouter: T): T { return openRouterProxy(or as OpenRouterClient) as T; } - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported OpenRouter library. Not wrapping."); + debugLogger.warn("Unsupported OpenRouter library. Not wrapping."); return openrouter; } diff --git a/js/src/wrappers/pi-coding-agent.test.ts b/js/src/wrappers/pi-coding-agent.test.ts index 7c6438fb1..57f168f6b 100644 --- a/js/src/wrappers/pi-coding-agent.test.ts +++ b/js/src/wrappers/pi-coding-agent.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, describe, expect, it, vi } from "vitest"; const { invoke } = vi.hoisted(() => ({ @@ -23,7 +24,7 @@ describe("wrapPiCodingAgentSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const sdk = { Session: class {} }; expect(wrapPiCodingAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/pi-coding-agent.ts b/js/src/wrappers/pi-coding-agent.ts index c922e7ed4..8119372c1 100644 --- a/js/src/wrappers/pi-coding-agent.ts +++ b/js/src/wrappers/pi-coding-agent.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { piCodingAgentChannels } from "../instrumentation/plugins/pi-coding-agent-interceptors"; import type { PiAgentSession, @@ -19,8 +20,7 @@ export function wrapPiCodingAgentSDK(sdk: T): T { const maybeSDK = sdk as Record; if (!maybeSDK.AgentSession || typeof maybeSDK.AgentSession !== "function") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); + debugLogger.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); return sdk; } @@ -39,8 +39,7 @@ function patchAgentSessionClass(AgentSession: PiAgentSessionClass): void { const descriptor = Object.getOwnPropertyDescriptor(prototype, "prompt"); if (!descriptor || typeof descriptor.value !== "function") { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); + debugLogger.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); return; } diff --git a/js/src/wrappers/shared/scorers.ts b/js/src/wrappers/shared/scorers.ts index b3c11ee45..b53b6e6ac 100644 --- a/js/src/wrappers/shared/scorers.ts +++ b/js/src/wrappers/shared/scorers.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import type { Span } from "../../logger"; import type { Score } from "../../../util/score"; import type { ScorerFunction } from "./types"; @@ -45,8 +46,7 @@ export async function runScorers(args: { } catch (scorerError) { // Log scorer error but don't fail the test — use metadata instead // of top-level error field to avoid marking the span as errored - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Braintrust: Scorer failed:", scorerError); + debugLogger.warn("Braintrust: Scorer failed:", scorerError); const errorStr = scorerError instanceof Error ? `${scorerError.message}\n\n${scorerError.stack || ""}` diff --git a/js/src/wrappers/strands-agent-sdk.test.ts b/js/src/wrappers/strands-agent-sdk.test.ts index 5300ea3ae..db61a1651 100644 --- a/js/src/wrappers/strands-agent-sdk.test.ts +++ b/js/src/wrappers/strands-agent-sdk.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, describe, expect, it, vi } from "vitest"; const { invoke } = vi.hoisted(() => ({ @@ -23,7 +24,7 @@ describe("wrapStrandsAgentSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const sdk = { AgentSession: class {} }; expect(wrapStrandsAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/strands-agent-sdk.ts b/js/src/wrappers/strands-agent-sdk.ts index fe61102b5..6abda0277 100644 --- a/js/src/wrappers/strands-agent-sdk.ts +++ b/js/src/wrappers/strands-agent-sdk.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { strandsAgentSDKChannels } from "../instrumentation/plugins/strands-agent-sdk-interceptors"; import type { StrandsAgent, @@ -35,8 +36,7 @@ export function wrapStrandsAgentSDK(sdk: T): T { typeof maybeSDK.Graph !== "function" && typeof maybeSDK.Swarm !== "function" ) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Strands Agent SDK. Not wrapping."); + debugLogger.warn("Unsupported Strands Agent SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/typesafe.test.ts b/js/src/wrappers/typesafe.test.ts index 7aaa287d9..4f691fde0 100644 --- a/js/src/wrappers/typesafe.test.ts +++ b/js/src/wrappers/typesafe.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -112,7 +113,7 @@ describe("TypeSafe wrapper", () => { }); it("returns unsupported clients unchanged", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const unsupported = { models: {} }; expect(wrapTypeSafe(unsupported)).toBe(unsupported); diff --git a/js/src/wrappers/typesafe.ts b/js/src/wrappers/typesafe.ts index 9daafc704..00b2cfb9c 100644 --- a/js/src/wrappers/typesafe.ts +++ b/js/src/wrappers/typesafe.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { typeSafeChannels } from "../instrumentation/plugins/typesafe-interceptors"; import type { TypeSafeClient, @@ -8,8 +9,7 @@ import type { export function wrapTypeSafe(client: T): T; export function wrapTypeSafe(client: unknown): unknown { if (!isSupportedTypeSafeClient(client)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported TypeSafe AI library. Not wrapping."); + debugLogger.warn("Unsupported TypeSafe AI library. Not wrapping."); return client; } diff --git a/js/src/wrappers/vitest/index.ts b/js/src/wrappers/vitest/index.ts index 6e2c24fdd..875c52dbd 100644 --- a/js/src/wrappers/vitest/index.ts +++ b/js/src/wrappers/vitest/index.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../../debug-logger"; import { wrapTest, wrapDescribe, getExperimentContext } from "./wrapper"; import { wrapExpect } from "./expect-wrapper"; import { summarizeAndFlush } from "../shared/flush"; @@ -98,8 +99,7 @@ export function wrapVitest< flushExperiment: async (options?: { displaySummary?: boolean }) => { const ctx = getExperimentContext(); if (!ctx) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn( + debugLogger.warn( "Braintrust: No experiment context found. Make sure you're using bt.describe() and calling flushExperiment() within an afterAll() hook.", ); return; diff --git a/js/src/wrappers/voyageai.test.ts b/js/src/wrappers/voyageai.test.ts index e9a1f9e19..fac2f4b52 100644 --- a/js/src/wrappers/voyageai.test.ts +++ b/js/src/wrappers/voyageai.test.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { afterEach, beforeAll, @@ -40,7 +41,7 @@ describe("Voyage AI wrapper", () => { }); it("returns unsupported clients unchanged", () => { - const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); const unsupported = { tokenize: vi.fn() }; expect(wrapVoyageAI(unsupported)).toBe(unsupported); diff --git a/js/src/wrappers/voyageai.ts b/js/src/wrappers/voyageai.ts index 22b04aada..85987caad 100644 --- a/js/src/wrappers/voyageai.ts +++ b/js/src/wrappers/voyageai.ts @@ -1,3 +1,4 @@ +import { debugLogger } from "../debug-logger"; import { voyageAIChannels } from "../instrumentation/plugins/voyageai-interceptors"; import type { VoyageAIClient, @@ -13,8 +14,7 @@ import type { */ export function wrapVoyageAI(client: T): T { if (!isSupportedVoyageAIClient(client)) { - // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. - console.warn("Unsupported Voyage AI library. Not wrapping."); + debugLogger.warn("Unsupported Voyage AI library. Not wrapping."); return client; } From e81b9fab9a5d12628b55bc2d70a3f681db07f61b Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 16 Oct 2026 13:21:02 -0400 Subject: [PATCH 2/4] feat: add debugLogger.notice for user-facing diagnostics Notice warnings and errors print at warn when no debug log level is configured, respect an explicit level, and are silenced by debugLogLevel: false. Use it for diagnostics that were always shown before routing through debugLogger, only mark the span customizer failure as logged once it is emitted, and drop duplicate Braintrust prefixes from logger messages. Co-Authored-By: Claude Opus 5.5 --- js/dev/authorize.ts | 2 +- js/dev/errorHandler.ts | 2 +- js/dev/server.ts | 4 +- .../auto-instrumentations/bundler/plugin.ts | 4 +- .../bundler/webpack-loader.ts | 2 +- js/src/auto-instrumentations/hook.mts | 2 +- .../auto-instrumentations/loader/cjs-patch.ts | 5 +- .../auto-instrumentations/loader/esm-hook.mts | 2 +- js/src/debug-logger.test.ts | 51 +++++++++++++++++++ js/src/debug-logger.ts | 34 ++++++++++--- js/src/eval-parameters.ts | 2 +- js/src/framework2.ts | 6 ++- .../plugins/github-copilot-plugin.ts | 5 +- js/src/instrumentation/registry.test.ts | 8 ++- js/src/instrumentation/registry.ts | 4 +- js/src/span-customizer.test.ts | 28 ++++++++++ js/src/span-customizer.ts | 12 +++-- .../ai-sdk/deprecated/wrapAISDKModel.ts | 2 +- js/src/wrappers/anthropic.ts | 2 +- js/src/wrappers/bedrock-runtime.test.ts | 4 +- js/src/wrappers/bedrock-runtime.ts | 2 +- .../claude-agent-sdk/claude-agent-sdk.ts | 2 +- js/src/wrappers/cloudflare-agent.ts | 4 +- js/src/wrappers/cloudflare-ai-chat.ts | 2 +- js/src/wrappers/cohere.test.ts | 4 +- js/src/wrappers/cohere.ts | 2 +- js/src/wrappers/cursor-sdk.test.ts | 4 +- js/src/wrappers/cursor-sdk.ts | 2 +- js/src/wrappers/elevenlabs.ts | 2 +- js/src/wrappers/genkit.ts | 2 +- js/src/wrappers/github-copilot.ts | 4 +- js/src/wrappers/google-adk.test.ts | 8 ++- js/src/wrappers/google-adk.ts | 4 +- js/src/wrappers/google-genai.ts | 4 +- js/src/wrappers/groq.test.ts | 4 +- js/src/wrappers/groq.ts | 2 +- js/src/wrappers/huggingface.ts | 4 +- js/src/wrappers/langgraph-sdk.ts | 2 +- js/src/wrappers/langsmith.ts | 2 +- js/src/wrappers/mistral.ts | 2 +- js/src/wrappers/oai.ts | 2 +- js/src/wrappers/ollama.test.ts | 4 +- js/src/wrappers/ollama.ts | 2 +- js/src/wrappers/openai-codex.ts | 2 +- js/src/wrappers/openrouter-agent.test.ts | 4 +- js/src/wrappers/openrouter-agent.ts | 4 +- js/src/wrappers/openrouter.test.ts | 4 +- js/src/wrappers/openrouter.ts | 2 +- js/src/wrappers/pi-coding-agent.test.ts | 4 +- js/src/wrappers/pi-coding-agent.ts | 4 +- js/src/wrappers/shared/scorers.ts | 2 +- js/src/wrappers/strands-agent-sdk.test.ts | 4 +- js/src/wrappers/strands-agent-sdk.ts | 2 +- js/src/wrappers/typesafe.test.ts | 4 +- js/src/wrappers/typesafe.ts | 2 +- js/src/wrappers/vitest/index.ts | 4 +- js/src/wrappers/voyageai.test.ts | 4 +- js/src/wrappers/voyageai.ts | 2 +- 58 files changed, 222 insertions(+), 77 deletions(-) diff --git a/js/dev/authorize.ts b/js/dev/authorize.ts index 4fd59b9cf..eb91a2aaf 100644 --- a/js/dev/authorize.ts +++ b/js/dev/authorize.ts @@ -91,7 +91,7 @@ export function makeCheckAuthorized(allowedOrgName: string | undefined) { req.ctx.state = state; next(); } catch (e) { - debugLogger.error("Authorization error:", e); + debugLogger.notice.error("Authorization error:", e); return next(createError(401, "Unauthorized")); } }; diff --git a/js/dev/errorHandler.ts b/js/dev/errorHandler.ts index 395eb616e..04f8d8fc6 100644 --- a/js/dev/errorHandler.ts +++ b/js/dev/errorHandler.ts @@ -28,7 +28,7 @@ export const errorHandler: ErrorRequestHandler = ( return; } - debugLogger.error("Internal server error", err); + debugLogger.notice.error("Internal server error", err); res.status(500).json({ error: { message: "Internal server error", diff --git a/js/dev/server.ts b/js/dev/server.ts index 8931e7c54..1ae70a61c 100644 --- a/js/dev/server.ts +++ b/js/dev/server.ts @@ -166,7 +166,7 @@ export function runDevServer( try { await validateParameters(parameters ?? {}, evaluator.parameters); } catch (e) { - debugLogger.error("Error validating parameters", e); + debugLogger.notice.error("Error validating parameters", e); if ( e instanceof z.ZodError || e instanceof ValidationError || @@ -281,7 +281,7 @@ export function runDevServer( res.json(summary.summary); } } catch (e) { - debugLogger.error("Error running eval", e); + debugLogger.notice.error("Error running eval", e); if (stream) { res.write( serializeSSEEvent({ diff --git a/js/src/auto-instrumentations/bundler/plugin.ts b/js/src/auto-instrumentations/bundler/plugin.ts index 894d36927..ff61dac11 100644 --- a/js/src/auto-instrumentations/bundler/plugin.ts +++ b/js/src/auto-instrumentations/bundler/plugin.ts @@ -75,7 +75,9 @@ export const unplugin = createUnplugin((options = {}) => { return transformBundledModule(code, filePath, matcher, browser); } catch (error) { // If transformation fails, warn and return original code - debugLogger.warn(`Code transformation failed for ${id}: ${error}`); + debugLogger.notice.warn( + `Code transformation failed for ${id}: ${error}`, + ); return null; } }, diff --git a/js/src/auto-instrumentations/bundler/webpack-loader.ts b/js/src/auto-instrumentations/bundler/webpack-loader.ts index 3d0c30421..67e1c3484 100644 --- a/js/src/auto-instrumentations/bundler/webpack-loader.ts +++ b/js/src/auto-instrumentations/bundler/webpack-loader.ts @@ -83,7 +83,7 @@ function codeTransformerLoader( callback(null, code, inputSourceMap); } } catch (error) { - debugLogger.warn( + debugLogger.notice.warn( `[code-transformer-loader] Error transforming ${resourcePath}:`, error, ); diff --git a/js/src/auto-instrumentations/hook.mts b/js/src/auto-instrumentations/hook.mts index 383f6d6de..fb17fb214 100644 --- a/js/src/auto-instrumentations/hook.mts +++ b/js/src/auto-instrumentations/hook.mts @@ -69,6 +69,6 @@ if (!alreadyApplied) { "Auto-instrumentation active (ESM only) for:", allConfigs.map((c) => c.channelName).join(", "), ); - debugLogger.error("CJS patch failed:", err); + debugLogger.notice.error("CJS patch failed:", err); } } diff --git a/js/src/auto-instrumentations/loader/cjs-patch.ts b/js/src/auto-instrumentations/loader/cjs-patch.ts index ccd513f1e..09c73987b 100644 --- a/js/src/auto-instrumentations/loader/cjs-patch.ts +++ b/js/src/auto-instrumentations/loader/cjs-patch.ts @@ -50,7 +50,10 @@ export function patchCommonJS(instrumentations: InstrumentationConfig[]): void { try { source = transformer.transform(source, "unknown").code; } catch (error) { - debugLogger.warn(`Error transforming module ${filename}:`, error); + debugLogger.notice.warn( + `Error transforming module ${filename}:`, + error, + ); } } } diff --git a/js/src/auto-instrumentations/loader/esm-hook.mts b/js/src/auto-instrumentations/loader/esm-hook.mts index 4e7f9d686..c4049670c 100644 --- a/js/src/auto-instrumentations/loader/esm-hook.mts +++ b/js/src/auto-instrumentations/loader/esm-hook.mts @@ -91,7 +91,7 @@ export async function load(url: string, context: any, nextLoad: Function) { ).code; result.shortCircuit = true; } catch (err) { - debugLogger.warn(`Error transforming module ${url}:`, err); + debugLogger.notice.warn(`Error transforming module ${url}:`, err); } } return result; diff --git a/js/src/debug-logger.test.ts b/js/src/debug-logger.test.ts index 6a8df2a82..782ca3ee9 100644 --- a/js/src/debug-logger.test.ts +++ b/js/src/debug-logger.test.ts @@ -95,6 +95,57 @@ describe("debug logger", () => { ); }); + test("notice messages are shown at warn when no level is configured", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + debugLogger.warn("hidden warning"); + expect(debugLogger.notice.warn("notice warning")).toBe(true); + expect(debugLogger.notice.error("notice error")).toBe(true); + + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith("[braintrust]", "notice warning"); + expect(errorSpy).toHaveBeenCalledWith("[braintrust]", "notice error"); + }); + + test("notice messages respect an explicit level", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const state = { + getDebugLogLevel: () => "error" as const, + hasDebugLogLevelOverride: () => true, + }; + + const scopedLogger = debugLogger.forState(state); + expect(scopedLogger.notice.warn("notice warning")).toBe(false); + expect(scopedLogger.notice.error("notice error")).toBe(true); + + expect(warnSpy).not.toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalledWith("[braintrust]", "notice error"); + }); + + test("notice messages are silenced when logging is disabled", () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + _exportsForTestingOnly.setInitialTestState(); + const state = _exportsForTestingOnly.simulateLogoutForTests(); + state.setDebugLogLevel(false); + + expect(debugLogger.notice.warn("notice warning")).toBe(false); + expect(debugLogger.notice.error("notice error")).toBe(false); + expect( + debugLogger + .forState({ + getDebugLogLevel: () => undefined, + hasDebugLogLevelOverride: () => true, + }) + .notice.error("scoped notice error"), + ).toBe(false); + + expect(warnSpy).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); + }); + test("default logger resolves the global SDK state", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); _exportsForTestingOnly.setInitialTestState(); diff --git a/js/src/debug-logger.ts b/js/src/debug-logger.ts index ae137ddaf..5be6cc579 100644 --- a/js/src/debug-logger.ts +++ b/js/src/debug-logger.ts @@ -9,11 +9,21 @@ type DebugLoggerStateLike = { hasDebugLogLevelOverride?: () => boolean; }; +// Notice methods return whether the message was emitted. +type NoticeLoggerMethods = { + warn: (...args: unknown[]) => boolean; + error: (...args: unknown[]) => boolean; +}; + type DebugLoggerMethods = { info: (...args: unknown[]) => void; debug: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; + // User-facing diagnostics that are shown at "warn" when no debug log level + // has been configured. An explicit level still filters them, and + // `debugLogLevel: false` silences them. + notice: NoticeLoggerMethods; }; const PREFIX = "[braintrust]"; @@ -109,13 +119,15 @@ export function setDebugLogStateResolver( debugLogStateResolver = resolver; } +// Returns `false` when logging was explicitly disabled and `undefined` when no +// level has been configured. function resolveDebugLogLevel( state?: DebugLoggerStateLike, -): DebugLogLevel | undefined { +): DebugLogLevel | false | undefined { const stateLevel = state?.getDebugLogLevel?.(); const hasStateOverride = state?.hasDebugLogLevelOverride?.() ?? false; if (hasStateOverride) { - return stateLevel; + return stateLevel ?? false; } const globalLevel = @@ -125,7 +137,7 @@ function resolveDebugLogLevel( ] as DebugLogLevel | false | undefined; if (globalLevel !== undefined) { - return globalLevel === false ? undefined : globalLevel; + return globalLevel; } return getEnvDebugLogLevel(); @@ -135,11 +147,12 @@ function emit( method: "info" | "debug" | "warn" | "error", state: DebugLoggerStateLike | undefined, args: unknown[], -) { - const level = resolveDebugLogLevel(state); + defaultLevel?: DebugLogLevel, +): boolean { + const level = resolveDebugLogLevel(state) ?? defaultLevel; if (!level || LOG_LEVEL_PRIORITY[method] > LOG_LEVEL_PRIORITY[level]) { - return; + return false; } if (method === "info") { @@ -155,6 +168,7 @@ function emit( // eslint-disable-next-line no-restricted-properties -- preserving intentional console usage. console.error(PREFIX, ...args); } + return true; } function createDebugLogger(state?: DebugLoggerStateLike): DebugLoggerMethods { @@ -172,6 +186,14 @@ function createDebugLogger(state?: DebugLoggerStateLike): DebugLoggerMethods { error(...args: unknown[]) { emit("error", resolveState(), args); }, + notice: { + warn(...args: unknown[]) { + return emit("warn", resolveState(), args, "warn"); + }, + error(...args: unknown[]) { + return emit("error", resolveState(), args, "warn"); + }, + }, }; } diff --git a/js/src/eval-parameters.ts b/js/src/eval-parameters.ts index bc18a64cc..433b9991b 100644 --- a/js/src/eval-parameters.ts +++ b/js/src/eval-parameters.ts @@ -126,7 +126,7 @@ function validateParametersWithZod< return [name, schema.parse(value)]; } } catch (e) { - debugLogger.error("Error validating parameter", name, e); + debugLogger.notice.error("Error validating parameter", name, e); throw Error( `Invalid parameter '${name}': ${e instanceof Error ? e.message : String(e)}`, ); diff --git a/js/src/framework2.ts b/js/src/framework2.ts index e3401a0f7..458f1d278 100644 --- a/js/src/framework2.ts +++ b/js/src/framework2.ts @@ -129,14 +129,16 @@ export class Project { async publish() { if (globalThis._lazy_load) { - debugLogger.warn("publish() is a no-op when running `braintrust push`."); + debugLogger.notice.warn( + "publish() is a no-op when running `braintrust push`.", + ); return; } await login(); const projectMap = new ProjectNameIdMap(); const functionDefinitions: FunctionEvent[] = []; if (this._publishableCodeFunctions.length > 0) { - debugLogger.warn( + debugLogger.notice.warn( "Code functions cannot be published directly. Use `braintrust push` instead.", ); } diff --git a/js/src/instrumentation/plugins/github-copilot-plugin.ts b/js/src/instrumentation/plugins/github-copilot-plugin.ts index ad9a48266..a8e43a085 100644 --- a/js/src/instrumentation/plugins/github-copilot-plugin.ts +++ b/js/src/instrumentation/plugins/github-copilot-plugin.ts @@ -651,10 +651,7 @@ function attachSessionEventListener( state.processing = state.processing .then(() => dispatchEvent(state, event)) .catch((err) => { - debugLogger.error( - "[Braintrust] Error processing GitHub Copilot SDK event:", - err, - ); + debugLogger.error("Error processing GitHub Copilot SDK event:", err); }); }; diff --git a/js/src/instrumentation/registry.test.ts b/js/src/instrumentation/registry.test.ts index 5736473a1..fdd2fcea0 100644 --- a/js/src/instrumentation/registry.test.ts +++ b/js/src/instrumentation/registry.test.ts @@ -109,7 +109,9 @@ describe("Plugin Registry", () => { it("should warn if configureInstrumentation is called after enable", () => { const testRegistry = new (registry.constructor as any)(); - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); try { testRegistry.enable(); @@ -126,7 +128,9 @@ describe("Plugin Registry", () => { it("should allow configuration before enable", () => { const testRegistry = new (registry.constructor as any)(); - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); try { testRegistry.configure({ integrations: { openai: false } }); diff --git a/js/src/instrumentation/registry.ts b/js/src/instrumentation/registry.ts index f03b40fb7..ce15a7fac 100644 --- a/js/src/instrumentation/registry.ts +++ b/js/src/instrumentation/registry.ts @@ -57,8 +57,8 @@ class PluginRegistry { */ configure(config: InstrumentationConfig): void { if (this.enabled) { - debugLogger.warn( - "Braintrust: Cannot configure instrumentation after it has been enabled. " + + debugLogger.notice.warn( + "Cannot configure instrumentation after it has been enabled. " + "Call configureInstrumentation() before importing any AI SDKs.", ); return; diff --git a/js/src/span-customizer.test.ts b/js/src/span-customizer.test.ts index 22b47db40..fa3ad721f 100644 --- a/js/src/span-customizer.test.ts +++ b/js/src/span-customizer.test.ts @@ -834,6 +834,34 @@ describe("onSpanExport", () => { } }); + test("logs the failure once logging is enabled after a silenced failure", () => { + const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); + const state = _internalGetGlobalState(); + configureInstrumentation({ + spanCustomizers: [ + { + onSpanExport(): SpanExportData { + throw new Error("private exception detail"); + }, + }, + ], + }); + + state.setDebugLogLevel(false); + customizeSpanExport({ id: "silenced" }); + expect(errorLog).not.toHaveBeenCalled(); + + state.setDebugLogLevel("error"); + customizeSpanExport({ id: "visible" }); + customizeSpanExport({ id: "deduplicated" }); + expect(errorLog.mock.calls).toEqual([ + [ + "[braintrust]", + "Span export customizer failed; exporting only span identity and duration.", + ], + ]); + }); + test("logs safely once across time, registrations, and SDK module copies", async () => { vi.useFakeTimers({ toFake: ["Date"] }); const errorLog = vi.spyOn(console, "error").mockImplementation(() => {}); diff --git a/js/src/span-customizer.ts b/js/src/span-customizer.ts index d0edbeccf..802d4c829 100644 --- a/js/src/span-customizer.ts +++ b/js/src/span-customizer.ts @@ -153,11 +153,15 @@ function restoreProtocolFields( function registerCustomizerFailure(): void { if (shared[CUSTOMIZER_FAILURE_LOGGED_KEY]) return; - shared[CUSTOMIZER_FAILURE_LOGGED_KEY] = true; // Never log the exception or record: either can contain sensitive data. - debugLogger.error( - "Span export customizer failed; exporting only span identity and duration.", - ); + // Only mark as logged once emitted so enabling logging later still surfaces it. + if ( + debugLogger.notice.error( + "Span export customizer failed; exporting only span identity and duration.", + ) + ) { + shared[CUSTOMIZER_FAILURE_LOGGED_KEY] = true; + } } export function resetSpanCustomizerFailureLoggingForTests(): void { diff --git a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts b/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts index fcf90781c..f28764813 100644 --- a/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts +++ b/js/src/wrappers/ai-sdk/deprecated/wrapAISDKModel.ts @@ -30,7 +30,7 @@ export function wrapAISDKModel(model: T): T { ) { return new BraintrustLanguageModelWrapper(m) as any as T; } else { - debugLogger.warn("Unsupported AI SDK model. Not wrapping."); + debugLogger.notice.warn("Unsupported AI SDK model. Not wrapping."); return model; } } diff --git a/js/src/wrappers/anthropic.ts b/js/src/wrappers/anthropic.ts index 00515bd4e..e98f9b12f 100644 --- a/js/src/wrappers/anthropic.ts +++ b/js/src/wrappers/anthropic.ts @@ -39,7 +39,7 @@ export function wrapAnthropic(anthropic: T): T { return anthropicProxy(au as AnthropicClient) as T; } - debugLogger.warn("Unsupported Anthropic library. Not wrapping."); + debugLogger.notice.warn("Unsupported Anthropic library. Not wrapping."); return anthropic; } diff --git a/js/src/wrappers/bedrock-runtime.test.ts b/js/src/wrappers/bedrock-runtime.test.ts index 0da4dec39..8b7277083 100644 --- a/js/src/wrappers/bedrock-runtime.test.ts +++ b/js/src/wrappers/bedrock-runtime.test.ts @@ -57,7 +57,9 @@ describe("bedrock runtime wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapBedrockRuntime(invalid)).toBe(invalid); diff --git a/js/src/wrappers/bedrock-runtime.ts b/js/src/wrappers/bedrock-runtime.ts index 744eccde5..3919553cc 100644 --- a/js/src/wrappers/bedrock-runtime.ts +++ b/js/src/wrappers/bedrock-runtime.ts @@ -18,7 +18,7 @@ export function wrapBedrockRuntime(client: T): T { return bedrockRuntimeProxy(client) as T; } - debugLogger.warn("Unsupported Bedrock Runtime library. Not wrapping."); + debugLogger.notice.warn("Unsupported Bedrock Runtime library. Not wrapping."); return client; } diff --git a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts index 1509da2e2..4ed523dd3 100644 --- a/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts +++ b/js/src/wrappers/claude-agent-sdk/claude-agent-sdk.ts @@ -32,7 +32,7 @@ export function wrapClaudeAgentSDK(sdk: T): T { return claudeAgentSDKProxy(s as ClaudeAgentSDKModule) as unknown as T; } - debugLogger.warn("Unsupported Claude Agent SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported Claude Agent SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/cloudflare-agent.ts b/js/src/wrappers/cloudflare-agent.ts index 8825113a3..4930a5185 100644 --- a/js/src/wrappers/cloudflare-agent.ts +++ b/js/src/wrappers/cloudflare-agent.ts @@ -86,5 +86,7 @@ function isObjectLike(value: unknown): value is object { } function warnUnsupportedAgent(): void { - debugLogger.warn("Unsupported Cloudflare Agents Agent class. Not wrapping."); + debugLogger.notice.warn( + "Unsupported Cloudflare Agents Agent class. Not wrapping.", + ); } diff --git a/js/src/wrappers/cloudflare-ai-chat.ts b/js/src/wrappers/cloudflare-ai-chat.ts index 87df27775..4023bbb49 100644 --- a/js/src/wrappers/cloudflare-ai-chat.ts +++ b/js/src/wrappers/cloudflare-ai-chat.ts @@ -30,7 +30,7 @@ export function wrapCloudflareAIChat(module: T): T { } if (typeof AIChatAgent !== "function") { - debugLogger.warn( + debugLogger.notice.warn( "Unsupported @cloudflare/ai-chat module. AIChatAgent was not found; not wrapping.", ); return module; diff --git a/js/src/wrappers/cohere.test.ts b/js/src/wrappers/cohere.test.ts index b871a3d07..e5cde4035 100644 --- a/js/src/wrappers/cohere.test.ts +++ b/js/src/wrappers/cohere.test.ts @@ -41,7 +41,9 @@ describe("cohere wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapCohere(invalid)).toBe(invalid); diff --git a/js/src/wrappers/cohere.ts b/js/src/wrappers/cohere.ts index 1297113c4..a91a8b094 100644 --- a/js/src/wrappers/cohere.ts +++ b/js/src/wrappers/cohere.ts @@ -20,7 +20,7 @@ export function wrapCohere(cohere: T): T { return cohereProxy(cohere) as T; } - debugLogger.warn("Unsupported Cohere library. Not wrapping."); + debugLogger.notice.warn("Unsupported Cohere library. Not wrapping."); return cohere; } diff --git a/js/src/wrappers/cursor-sdk.test.ts b/js/src/wrappers/cursor-sdk.test.ts index 75a551ae5..066003894 100644 --- a/js/src/wrappers/cursor-sdk.test.ts +++ b/js/src/wrappers/cursor-sdk.test.ts @@ -24,7 +24,9 @@ describe("wrapCursorSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const sdk = { Cursor: {} }; expect(wrapCursorSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/cursor-sdk.ts b/js/src/wrappers/cursor-sdk.ts index 9a4f59866..a2ec37d60 100644 --- a/js/src/wrappers/cursor-sdk.ts +++ b/js/src/wrappers/cursor-sdk.ts @@ -22,7 +22,7 @@ export function wrapCursorSDK(sdk: T): T { const maybeSDK = sdk as Record; if (!maybeSDK.Agent || typeof maybeSDK.Agent !== "function") { - debugLogger.warn("Unsupported Cursor SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported Cursor SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/elevenlabs.ts b/js/src/wrappers/elevenlabs.ts index 01700532c..b106e3422 100644 --- a/js/src/wrappers/elevenlabs.ts +++ b/js/src/wrappers/elevenlabs.ts @@ -30,7 +30,7 @@ export function wrapElevenLabs(client: T): T { !isObject(client.textToSpeech) || typeof client.textToSpeech.convert !== "function" ) { - debugLogger.warn("Unsupported ElevenLabs client. Not wrapping."); + debugLogger.notice.warn("Unsupported ElevenLabs client. Not wrapping."); return client; } const cached = clients.get(client); diff --git a/js/src/wrappers/genkit.ts b/js/src/wrappers/genkit.ts index 00bcddf01..47cbcd384 100644 --- a/js/src/wrappers/genkit.ts +++ b/js/src/wrappers/genkit.ts @@ -37,7 +37,7 @@ export function wrapGenkit(genkit: T): T { return wrapGenkitModule(genkit) as T; } - debugLogger.warn("Unsupported Genkit object. Not wrapping."); + debugLogger.notice.warn("Unsupported Genkit object. Not wrapping."); return genkit; } diff --git a/js/src/wrappers/github-copilot.ts b/js/src/wrappers/github-copilot.ts index de80d17fe..4faf0fd43 100644 --- a/js/src/wrappers/github-copilot.ts +++ b/js/src/wrappers/github-copilot.ts @@ -38,8 +38,8 @@ function isGitHubCopilotClient(value: unknown): value is GitHubCopilotClient { */ export function wrapCopilotClient(client: T): T { if (!isGitHubCopilotClient(client)) { - debugLogger.warn( - "[Braintrust] wrapCopilotClient: argument does not look like a CopilotClient. Not wrapping.", + debugLogger.notice.warn( + "wrapCopilotClient: argument does not look like a CopilotClient. Not wrapping.", ); return client; } diff --git a/js/src/wrappers/google-adk.test.ts b/js/src/wrappers/google-adk.test.ts index 41a71f091..495ce25cb 100644 --- a/js/src/wrappers/google-adk.test.ts +++ b/js/src/wrappers/google-adk.test.ts @@ -24,7 +24,9 @@ describe("wrapGoogleADK", () => { }); it("should return the module unchanged if invalid", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const result = wrapGoogleADK(null as any); expect(result).toBeNull(); @@ -36,7 +38,9 @@ describe("wrapGoogleADK", () => { }); it("should return module unchanged if no Runner or LlmAgent found", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const fakeModule = { SomeOtherClass: class {} }; const result = wrapGoogleADK(fakeModule as any); diff --git a/js/src/wrappers/google-adk.ts b/js/src/wrappers/google-adk.ts index f40c49f0a..875ce4ca4 100644 --- a/js/src/wrappers/google-adk.ts +++ b/js/src/wrappers/google-adk.ts @@ -42,12 +42,12 @@ import type { */ export function wrapGoogleADK>(adkModule: T): T { if (!adkModule || typeof adkModule !== "object") { - debugLogger.warn("Invalid Google ADK module. Not wrapping."); + debugLogger.notice.warn("Invalid Google ADK module. Not wrapping."); return adkModule; } if (!("Runner" in adkModule) && !("LlmAgent" in adkModule)) { - debugLogger.warn( + debugLogger.notice.warn( "Runner or LlmAgent class not found in module. Not wrapping. Make sure you're passing the module itself (import * as adk from '@google/adk').", ); return adkModule; diff --git a/js/src/wrappers/google-genai.ts b/js/src/wrappers/google-genai.ts index 752f8b0d8..f9ed4917c 100644 --- a/js/src/wrappers/google-genai.ts +++ b/js/src/wrappers/google-genai.ts @@ -38,12 +38,12 @@ export function wrapGoogleGenAI>( googleGenAI: T, ): T { if (!googleGenAI || typeof googleGenAI !== "object") { - debugLogger.warn("Invalid Google GenAI module. Not wrapping."); + debugLogger.notice.warn("Invalid Google GenAI module. Not wrapping."); return googleGenAI; } if (!("GoogleGenAI" in googleGenAI)) { - debugLogger.warn( + debugLogger.notice.warn( "GoogleGenAI class not found in module. Not wrapping. Make sure you're passing the module itself (import * as googleGenAI from '@google/genai').", ); return googleGenAI; diff --git a/js/src/wrappers/groq.test.ts b/js/src/wrappers/groq.test.ts index 1228b28ae..5aa9f5436 100644 --- a/js/src/wrappers/groq.test.ts +++ b/js/src/wrappers/groq.test.ts @@ -42,7 +42,9 @@ describe("groq wrapper", () => { }); test("returns original object for unsupported clients", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapGroq(invalid)).toBe(invalid); diff --git a/js/src/wrappers/groq.ts b/js/src/wrappers/groq.ts index f75847cc2..488efdd6d 100644 --- a/js/src/wrappers/groq.ts +++ b/js/src/wrappers/groq.ts @@ -23,7 +23,7 @@ export function wrapGroq(groq: T): T { return groqProxy(groq) as T; } - debugLogger.warn("Unsupported Groq library. Not wrapping."); + debugLogger.notice.warn("Unsupported Groq library. Not wrapping."); return groq; } diff --git a/js/src/wrappers/huggingface.ts b/js/src/wrappers/huggingface.ts index 200adcece..c01e0756c 100644 --- a/js/src/wrappers/huggingface.ts +++ b/js/src/wrappers/huggingface.ts @@ -52,7 +52,9 @@ export function wrapHuggingFace(huggingFace: unknown): unknown { return clientProxy(huggingFace); } - debugLogger.warn("Unsupported HuggingFace Inference SDK. Not wrapping."); + debugLogger.notice.warn( + "Unsupported HuggingFace Inference SDK. Not wrapping.", + ); return huggingFace; } diff --git a/js/src/wrappers/langgraph-sdk.ts b/js/src/wrappers/langgraph-sdk.ts index 5c82a13b6..6c33b6487 100644 --- a/js/src/wrappers/langgraph-sdk.ts +++ b/js/src/wrappers/langgraph-sdk.ts @@ -13,7 +13,7 @@ export function wrapLangGraphSDK(client: T): T { !isObject(runsClient) || !["wait", "stream"].every((key) => typeof runsClient[key] === "function") ) { - debugLogger.warn("Unsupported LangGraph SDK client. Not wrapping."); + debugLogger.notice.warn("Unsupported LangGraph SDK client. Not wrapping."); return client; } const sdk = client as unknown as LangGraphSDKClient; diff --git a/js/src/wrappers/langsmith.ts b/js/src/wrappers/langsmith.ts index 6c834a6d8..5213d5366 100644 --- a/js/src/wrappers/langsmith.ts +++ b/js/src/wrappers/langsmith.ts @@ -74,7 +74,7 @@ function wrapNamespaceExport( return namespace; } if (typeof candidate[exportName] !== "function") { - debugLogger.warn( + debugLogger.notice.warn( `Unsupported LangSmith ${exportName} namespace. Not wrapping.`, ); return namespace; diff --git a/js/src/wrappers/mistral.ts b/js/src/wrappers/mistral.ts index 29c1b22d7..af5003074 100644 --- a/js/src/wrappers/mistral.ts +++ b/js/src/wrappers/mistral.ts @@ -32,7 +32,7 @@ export function wrapMistral(mistral: T): T { return mistralProxy(mistral) as T; } - debugLogger.warn("Unsupported Mistral library. Not wrapping."); + debugLogger.notice.warn("Unsupported Mistral library. Not wrapping."); return mistral; } diff --git a/js/src/wrappers/oai.ts b/js/src/wrappers/oai.ts index 068d19b53..d49800de9 100644 --- a/js/src/wrappers/oai.ts +++ b/js/src/wrappers/oai.ts @@ -69,7 +69,7 @@ export function wrapOpenAI(openai: T): T { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return wrapOpenAIv4(typedOpenAI) as T; } else { - debugLogger.warn( + debugLogger.notice.warn( "Unsupported OpenAI library (potentially v3). Not wrapping.", ); return openai; diff --git a/js/src/wrappers/ollama.test.ts b/js/src/wrappers/ollama.test.ts index 9b368166e..9b1630132 100644 --- a/js/src/wrappers/ollama.test.ts +++ b/js/src/wrappers/ollama.test.ts @@ -52,7 +52,9 @@ describe("wrapOllama", () => { it("returns unsupported objects unchanged", () => { const client = {}; - const warn = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warn = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); expect(wrapOllama(client)).toBe(client); expect(warn).toHaveBeenCalledWith( diff --git a/js/src/wrappers/ollama.ts b/js/src/wrappers/ollama.ts index 8f20da200..c5b9a5faa 100644 --- a/js/src/wrappers/ollama.ts +++ b/js/src/wrappers/ollama.ts @@ -20,7 +20,7 @@ export function wrapOllama(ollama: T): T { return ollamaProxy(ollama) as T; } - debugLogger.warn("Unsupported Ollama library. Not wrapping."); + debugLogger.notice.warn("Unsupported Ollama library. Not wrapping."); return ollama; } diff --git a/js/src/wrappers/openai-codex.ts b/js/src/wrappers/openai-codex.ts index f743626ff..ced9c7708 100644 --- a/js/src/wrappers/openai-codex.ts +++ b/js/src/wrappers/openai-codex.ts @@ -29,7 +29,7 @@ export function wrapOpenAICodexSDK(sdk: T): T { } if (!maybeSDK.Codex || typeof maybeSDK.Codex !== "function") { - debugLogger.warn("Unsupported OpenAI Codex SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported OpenAI Codex SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/openrouter-agent.test.ts b/js/src/wrappers/openrouter-agent.test.ts index b4e118220..02a4733cf 100644 --- a/js/src/wrappers/openrouter-agent.test.ts +++ b/js/src/wrappers/openrouter-agent.test.ts @@ -9,7 +9,9 @@ describe("wrapOpenRouterAgent", () => { }); it("returns the original value and warns for unsupported inputs", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const input = { notCallModel: true }; expect(wrapOpenRouterAgent(input as object)).toBe(input); diff --git a/js/src/wrappers/openrouter-agent.ts b/js/src/wrappers/openrouter-agent.ts index 4bbe16dc1..dd077e704 100644 --- a/js/src/wrappers/openrouter-agent.ts +++ b/js/src/wrappers/openrouter-agent.ts @@ -20,7 +20,9 @@ export function wrapOpenRouterAgent(agent: T): T { return openRouterAgentProxy(candidate as OpenRouterAgentClient) as T; } - debugLogger.warn("Unsupported OpenRouter Agent library. Not wrapping."); + debugLogger.notice.warn( + "Unsupported OpenRouter Agent library. Not wrapping.", + ); return agent; } diff --git a/js/src/wrappers/openrouter.test.ts b/js/src/wrappers/openrouter.test.ts index 3f756aafb..b237599f4 100644 --- a/js/src/wrappers/openrouter.test.ts +++ b/js/src/wrappers/openrouter.test.ts @@ -50,7 +50,9 @@ describe("openrouter wrapper", () => { }); test("returns the original object for unsupported clients", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const invalid = { foo: "bar" }; expect(wrapOpenRouter(invalid)).toBe(invalid); diff --git a/js/src/wrappers/openrouter.ts b/js/src/wrappers/openrouter.ts index 47f180762..c3f6278be 100644 --- a/js/src/wrappers/openrouter.ts +++ b/js/src/wrappers/openrouter.ts @@ -44,7 +44,7 @@ export function wrapOpenRouter(openrouter: T): T { return openRouterProxy(or as OpenRouterClient) as T; } - debugLogger.warn("Unsupported OpenRouter library. Not wrapping."); + debugLogger.notice.warn("Unsupported OpenRouter library. Not wrapping."); return openrouter; } diff --git a/js/src/wrappers/pi-coding-agent.test.ts b/js/src/wrappers/pi-coding-agent.test.ts index 57f168f6b..1a9d2d1f9 100644 --- a/js/src/wrappers/pi-coding-agent.test.ts +++ b/js/src/wrappers/pi-coding-agent.test.ts @@ -24,7 +24,9 @@ describe("wrapPiCodingAgentSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const sdk = { Session: class {} }; expect(wrapPiCodingAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/pi-coding-agent.ts b/js/src/wrappers/pi-coding-agent.ts index 8119372c1..c7051b1b2 100644 --- a/js/src/wrappers/pi-coding-agent.ts +++ b/js/src/wrappers/pi-coding-agent.ts @@ -20,7 +20,7 @@ export function wrapPiCodingAgentSDK(sdk: T): T { const maybeSDK = sdk as Record; if (!maybeSDK.AgentSession || typeof maybeSDK.AgentSession !== "function") { - debugLogger.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); return sdk; } @@ -39,7 +39,7 @@ function patchAgentSessionClass(AgentSession: PiAgentSessionClass): void { const descriptor = Object.getOwnPropertyDescriptor(prototype, "prompt"); if (!descriptor || typeof descriptor.value !== "function") { - debugLogger.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported Pi Coding Agent SDK. Not wrapping."); return; } diff --git a/js/src/wrappers/shared/scorers.ts b/js/src/wrappers/shared/scorers.ts index b53b6e6ac..d627557e6 100644 --- a/js/src/wrappers/shared/scorers.ts +++ b/js/src/wrappers/shared/scorers.ts @@ -46,7 +46,7 @@ export async function runScorers(args: { } catch (scorerError) { // Log scorer error but don't fail the test — use metadata instead // of top-level error field to avoid marking the span as errored - debugLogger.warn("Braintrust: Scorer failed:", scorerError); + debugLogger.notice.warn("Scorer failed:", scorerError); const errorStr = scorerError instanceof Error ? `${scorerError.message}\n\n${scorerError.stack || ""}` diff --git a/js/src/wrappers/strands-agent-sdk.test.ts b/js/src/wrappers/strands-agent-sdk.test.ts index db61a1651..03d9cff8b 100644 --- a/js/src/wrappers/strands-agent-sdk.test.ts +++ b/js/src/wrappers/strands-agent-sdk.test.ts @@ -24,7 +24,9 @@ describe("wrapStrandsAgentSDK", () => { }); it("returns invalid modules unchanged", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const sdk = { AgentSession: class {} }; expect(wrapStrandsAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/strands-agent-sdk.ts b/js/src/wrappers/strands-agent-sdk.ts index 6abda0277..90b6c28c4 100644 --- a/js/src/wrappers/strands-agent-sdk.ts +++ b/js/src/wrappers/strands-agent-sdk.ts @@ -36,7 +36,7 @@ export function wrapStrandsAgentSDK(sdk: T): T { typeof maybeSDK.Graph !== "function" && typeof maybeSDK.Swarm !== "function" ) { - debugLogger.warn("Unsupported Strands Agent SDK. Not wrapping."); + debugLogger.notice.warn("Unsupported Strands Agent SDK. Not wrapping."); return sdk; } diff --git a/js/src/wrappers/typesafe.test.ts b/js/src/wrappers/typesafe.test.ts index 4f691fde0..b06ffd345 100644 --- a/js/src/wrappers/typesafe.test.ts +++ b/js/src/wrappers/typesafe.test.ts @@ -113,7 +113,9 @@ describe("TypeSafe wrapper", () => { }); it("returns unsupported clients unchanged", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const unsupported = { models: {} }; expect(wrapTypeSafe(unsupported)).toBe(unsupported); diff --git a/js/src/wrappers/typesafe.ts b/js/src/wrappers/typesafe.ts index 00b2cfb9c..3b639dd32 100644 --- a/js/src/wrappers/typesafe.ts +++ b/js/src/wrappers/typesafe.ts @@ -9,7 +9,7 @@ import type { export function wrapTypeSafe(client: T): T; export function wrapTypeSafe(client: unknown): unknown { if (!isSupportedTypeSafeClient(client)) { - debugLogger.warn("Unsupported TypeSafe AI library. Not wrapping."); + debugLogger.notice.warn("Unsupported TypeSafe AI library. Not wrapping."); return client; } diff --git a/js/src/wrappers/vitest/index.ts b/js/src/wrappers/vitest/index.ts index 875c52dbd..da2fdae12 100644 --- a/js/src/wrappers/vitest/index.ts +++ b/js/src/wrappers/vitest/index.ts @@ -99,8 +99,8 @@ export function wrapVitest< flushExperiment: async (options?: { displaySummary?: boolean }) => { const ctx = getExperimentContext(); if (!ctx) { - debugLogger.warn( - "Braintrust: No experiment context found. Make sure you're using bt.describe() and calling flushExperiment() within an afterAll() hook.", + debugLogger.notice.warn( + "No experiment context found. Make sure you're using bt.describe() and calling flushExperiment() within an afterAll() hook.", ); return; } diff --git a/js/src/wrappers/voyageai.test.ts b/js/src/wrappers/voyageai.test.ts index fac2f4b52..62bb566b6 100644 --- a/js/src/wrappers/voyageai.test.ts +++ b/js/src/wrappers/voyageai.test.ts @@ -41,7 +41,9 @@ describe("Voyage AI wrapper", () => { }); it("returns unsupported clients unchanged", () => { - const warnSpy = vi.spyOn(debugLogger, "warn").mockImplementation(() => {}); + const warnSpy = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => {}); const unsupported = { tokenize: vi.fn() }; expect(wrapVoyageAI(unsupported)).toBe(unsupported); diff --git a/js/src/wrappers/voyageai.ts b/js/src/wrappers/voyageai.ts index 85987caad..b36e17a2b 100644 --- a/js/src/wrappers/voyageai.ts +++ b/js/src/wrappers/voyageai.ts @@ -14,7 +14,7 @@ import type { */ export function wrapVoyageAI(client: T): T { if (!isSupportedVoyageAIClient(client)) { - debugLogger.warn("Unsupported Voyage AI library. Not wrapping."); + debugLogger.notice.warn("Unsupported Voyage AI library. Not wrapping."); return client; } From 40d8ea4827b1ae1c5aeb58ff593bc7d23164fa82 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 19 Oct 2026 13:59:36 -0400 Subject: [PATCH 3/4] test: return booleans from debugLogger.notice spies Co-Authored-By: Claude Opus 5.5 --- js/src/instrumentation/registry.test.ts | 4 ++-- js/src/wrappers/bedrock-runtime.test.ts | 2 +- js/src/wrappers/cohere.test.ts | 2 +- js/src/wrappers/cursor-sdk.test.ts | 2 +- js/src/wrappers/google-adk.test.ts | 4 ++-- js/src/wrappers/groq.test.ts | 2 +- js/src/wrappers/ollama.test.ts | 2 +- js/src/wrappers/openrouter-agent.test.ts | 2 +- js/src/wrappers/openrouter.test.ts | 2 +- js/src/wrappers/pi-coding-agent.test.ts | 2 +- js/src/wrappers/strands-agent-sdk.test.ts | 2 +- js/src/wrappers/typesafe.test.ts | 2 +- js/src/wrappers/voyageai.test.ts | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/js/src/instrumentation/registry.test.ts b/js/src/instrumentation/registry.test.ts index fdd2fcea0..47d2196e9 100644 --- a/js/src/instrumentation/registry.test.ts +++ b/js/src/instrumentation/registry.test.ts @@ -111,7 +111,7 @@ describe("Plugin Registry", () => { const testRegistry = new (registry.constructor as any)(); const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); try { testRegistry.enable(); @@ -130,7 +130,7 @@ describe("Plugin Registry", () => { const testRegistry = new (registry.constructor as any)(); const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); try { testRegistry.configure({ integrations: { openai: false } }); diff --git a/js/src/wrappers/bedrock-runtime.test.ts b/js/src/wrappers/bedrock-runtime.test.ts index 8b7277083..4da0d27c5 100644 --- a/js/src/wrappers/bedrock-runtime.test.ts +++ b/js/src/wrappers/bedrock-runtime.test.ts @@ -59,7 +59,7 @@ describe("bedrock runtime wrapper", () => { test("returns original object for unsupported clients", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const invalid = { foo: "bar" }; expect(wrapBedrockRuntime(invalid)).toBe(invalid); diff --git a/js/src/wrappers/cohere.test.ts b/js/src/wrappers/cohere.test.ts index e5cde4035..9f580d166 100644 --- a/js/src/wrappers/cohere.test.ts +++ b/js/src/wrappers/cohere.test.ts @@ -43,7 +43,7 @@ describe("cohere wrapper", () => { test("returns original object for unsupported clients", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const invalid = { foo: "bar" }; expect(wrapCohere(invalid)).toBe(invalid); diff --git a/js/src/wrappers/cursor-sdk.test.ts b/js/src/wrappers/cursor-sdk.test.ts index 066003894..b260e4a1d 100644 --- a/js/src/wrappers/cursor-sdk.test.ts +++ b/js/src/wrappers/cursor-sdk.test.ts @@ -26,7 +26,7 @@ describe("wrapCursorSDK", () => { it("returns invalid modules unchanged", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const sdk = { Cursor: {} }; expect(wrapCursorSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/google-adk.test.ts b/js/src/wrappers/google-adk.test.ts index 495ce25cb..67de8a16b 100644 --- a/js/src/wrappers/google-adk.test.ts +++ b/js/src/wrappers/google-adk.test.ts @@ -26,7 +26,7 @@ describe("wrapGoogleADK", () => { it("should return the module unchanged if invalid", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const result = wrapGoogleADK(null as any); expect(result).toBeNull(); @@ -40,7 +40,7 @@ describe("wrapGoogleADK", () => { it("should return module unchanged if no Runner or LlmAgent found", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const fakeModule = { SomeOtherClass: class {} }; const result = wrapGoogleADK(fakeModule as any); diff --git a/js/src/wrappers/groq.test.ts b/js/src/wrappers/groq.test.ts index 5aa9f5436..88a6c0f95 100644 --- a/js/src/wrappers/groq.test.ts +++ b/js/src/wrappers/groq.test.ts @@ -44,7 +44,7 @@ describe("groq wrapper", () => { test("returns original object for unsupported clients", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const invalid = { foo: "bar" }; expect(wrapGroq(invalid)).toBe(invalid); diff --git a/js/src/wrappers/ollama.test.ts b/js/src/wrappers/ollama.test.ts index 9b1630132..2db63e5eb 100644 --- a/js/src/wrappers/ollama.test.ts +++ b/js/src/wrappers/ollama.test.ts @@ -54,7 +54,7 @@ describe("wrapOllama", () => { const client = {}; const warn = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); expect(wrapOllama(client)).toBe(client); expect(warn).toHaveBeenCalledWith( diff --git a/js/src/wrappers/openrouter-agent.test.ts b/js/src/wrappers/openrouter-agent.test.ts index 02a4733cf..c84a48333 100644 --- a/js/src/wrappers/openrouter-agent.test.ts +++ b/js/src/wrappers/openrouter-agent.test.ts @@ -11,7 +11,7 @@ describe("wrapOpenRouterAgent", () => { it("returns the original value and warns for unsupported inputs", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const input = { notCallModel: true }; expect(wrapOpenRouterAgent(input as object)).toBe(input); diff --git a/js/src/wrappers/openrouter.test.ts b/js/src/wrappers/openrouter.test.ts index b237599f4..50b0bdbb3 100644 --- a/js/src/wrappers/openrouter.test.ts +++ b/js/src/wrappers/openrouter.test.ts @@ -52,7 +52,7 @@ describe("openrouter wrapper", () => { test("returns the original object for unsupported clients", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const invalid = { foo: "bar" }; expect(wrapOpenRouter(invalid)).toBe(invalid); diff --git a/js/src/wrappers/pi-coding-agent.test.ts b/js/src/wrappers/pi-coding-agent.test.ts index 1a9d2d1f9..678b6481c 100644 --- a/js/src/wrappers/pi-coding-agent.test.ts +++ b/js/src/wrappers/pi-coding-agent.test.ts @@ -26,7 +26,7 @@ describe("wrapPiCodingAgentSDK", () => { it("returns invalid modules unchanged", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const sdk = { Session: class {} }; expect(wrapPiCodingAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/strands-agent-sdk.test.ts b/js/src/wrappers/strands-agent-sdk.test.ts index 03d9cff8b..82ab652ed 100644 --- a/js/src/wrappers/strands-agent-sdk.test.ts +++ b/js/src/wrappers/strands-agent-sdk.test.ts @@ -26,7 +26,7 @@ describe("wrapStrandsAgentSDK", () => { it("returns invalid modules unchanged", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const sdk = { AgentSession: class {} }; expect(wrapStrandsAgentSDK(sdk)).toBe(sdk); diff --git a/js/src/wrappers/typesafe.test.ts b/js/src/wrappers/typesafe.test.ts index b06ffd345..0d86402a5 100644 --- a/js/src/wrappers/typesafe.test.ts +++ b/js/src/wrappers/typesafe.test.ts @@ -115,7 +115,7 @@ describe("TypeSafe wrapper", () => { it("returns unsupported clients unchanged", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const unsupported = { models: {} }; expect(wrapTypeSafe(unsupported)).toBe(unsupported); diff --git a/js/src/wrappers/voyageai.test.ts b/js/src/wrappers/voyageai.test.ts index 62bb566b6..323a0bc2f 100644 --- a/js/src/wrappers/voyageai.test.ts +++ b/js/src/wrappers/voyageai.test.ts @@ -43,7 +43,7 @@ describe("Voyage AI wrapper", () => { it("returns unsupported clients unchanged", () => { const warnSpy = vi .spyOn(debugLogger.notice, "warn") - .mockImplementation(() => {}); + .mockImplementation(() => true); const unsupported = { tokenize: vi.fn() }; expect(wrapVoyageAI(unsupported)).toBe(unsupported); From 6815ef20a0745eebc27bc83a1bc140349aaf7a55 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 19 Oct 2026 12:49:32 -0700 Subject: [PATCH 4/4] fix: guard process.env in debug log level fallback Browser and edge runtimes can expose a process shim without env. Co-Authored-By: Claude Opus 5.5 --- js/src/debug-logger.test.ts | 10 ++++++++++ js/src/debug-logger.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/js/src/debug-logger.test.ts b/js/src/debug-logger.test.ts index 782ca3ee9..88f29c051 100644 --- a/js/src/debug-logger.test.ts +++ b/js/src/debug-logger.test.ts @@ -172,6 +172,16 @@ describe("debug logger", () => { expect(getEnvDebugLogLevel()).toBe("debug"); }); + test("BRAINTRUST_DEBUG_LOG_LEVEL fallback tolerates a process shim without env", () => { + vi.spyOn(iso, "getEnv").mockReturnValue(undefined); + vi.stubGlobal("process", {}); + try { + expect(getEnvDebugLogLevel()).toBeUndefined(); + } finally { + vi.unstubAllGlobals(); + } + }); + test("BRAINTRUST_DEBUG_LOG_LEVEL rejects invalid values and warns once", () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); process.env.BRAINTRUST_DEBUG_LOG_LEVEL = "true"; diff --git a/js/src/debug-logger.ts b/js/src/debug-logger.ts index 5be6cc579..6b4b6b17c 100644 --- a/js/src/debug-logger.ts +++ b/js/src/debug-logger.ts @@ -93,7 +93,7 @@ export function getEnvDebugLogLevel(): DebugLogLevel | undefined { return parseDebugLogLevelEnv( iso.getEnv("BRAINTRUST_DEBUG_LOG_LEVEL") ?? // Loader and bundler diagnostics can run before platform initialization. - (typeof process !== "undefined" + (typeof process !== "undefined" && typeof process.env !== "undefined" ? process.env.BRAINTRUST_DEBUG_LOG_LEVEL : undefined), );