|
| 1 | +--- |
| 2 | +title: Debug Logging |
| 3 | +id: debug-logging |
| 4 | +order: 3 |
| 5 | +description: "Turn on structured, category-toggleable debug logging to see every chunk, middleware transform, and tool call inside TanStack AI." |
| 6 | +keywords: |
| 7 | + - tanstack ai |
| 8 | + - debug |
| 9 | + - logging |
| 10 | + - logger |
| 11 | + - pino |
| 12 | + - troubleshooting |
| 13 | + - chunks |
| 14 | + - middleware debugging |
| 15 | +--- |
| 16 | + |
| 17 | +# Debug Logging |
| 18 | + |
| 19 | +You have a `chat()` that isn't behaving as expected — a missing chunk, a middleware that doesn't seem to fire, a tool call with wrong args. By the end of this guide, you'll have turned on debug logging and will see every chunk, middleware transform, and tool call flowing through your call. |
| 20 | + |
| 21 | +## Turn it on |
| 22 | + |
| 23 | +Add `debug: true` to any activity call: |
| 24 | + |
| 25 | +```typescript |
| 26 | +import { chat } from "@tanstack/ai"; |
| 27 | +import { openaiText } from "@tanstack/ai-openai"; |
| 28 | + |
| 29 | +const stream = chat({ |
| 30 | + adapter: openaiText("gpt-4o"), |
| 31 | + messages: [{ role: "user", content: "Hello" }], |
| 32 | + debug: true, |
| 33 | +}); |
| 34 | +``` |
| 35 | + |
| 36 | +Every internal event now prints to the console with a `[tanstack-ai:<category>]` prefix: |
| 37 | + |
| 38 | +``` |
| 39 | +[tanstack-ai:request] activity=chat provider=openai model=gpt-4o messages=1 tools=0 stream=true |
| 40 | +[tanstack-ai:agentLoop] run started |
| 41 | +[tanstack-ai:provider] provider=openai type=response.output_text.delta |
| 42 | +[tanstack-ai:output] type=TEXT_MESSAGE_CONTENT |
| 43 | +... |
| 44 | +``` |
| 45 | + |
| 46 | +## Narrow what's printed |
| 47 | + |
| 48 | +Pass a `DebugConfig` object instead of `true`. Every unspecified category defaults to `true`, so toggle by setting specific flags to `false`: |
| 49 | + |
| 50 | +```typescript |
| 51 | +chat({ |
| 52 | + adapter: openaiText("gpt-4o"), |
| 53 | + messages, |
| 54 | + debug: { middleware: false }, // everything except middleware |
| 55 | +}); |
| 56 | +``` |
| 57 | + |
| 58 | +If you want to see ONLY a specific set of categories, set the rest to `false` explicitly. Errors default to `true` — keep them on unless you really want total silence: |
| 59 | + |
| 60 | +```typescript |
| 61 | +chat({ |
| 62 | + adapter: openaiText("gpt-4o"), |
| 63 | + messages, |
| 64 | + debug: { |
| 65 | + provider: true, |
| 66 | + output: true, |
| 67 | + middleware: false, |
| 68 | + tools: false, |
| 69 | + agentLoop: false, |
| 70 | + config: false, |
| 71 | + errors: true, // keep errors on — they're cheap and important |
| 72 | + request: false, |
| 73 | + }, |
| 74 | +}); |
| 75 | +``` |
| 76 | + |
| 77 | +## Pipe into your own logger |
| 78 | + |
| 79 | +Pass a `Logger` implementation and all debug output flows through it instead of `console`: |
| 80 | + |
| 81 | +```typescript |
| 82 | +import type { Logger } from "@tanstack/ai"; |
| 83 | +import pino from "pino"; |
| 84 | + |
| 85 | +const pinoLogger = pino(); |
| 86 | +const logger: Logger = { |
| 87 | + debug: (msg, meta) => pinoLogger.debug(meta, msg), |
| 88 | + info: (msg, meta) => pinoLogger.info(meta, msg), |
| 89 | + warn: (msg, meta) => pinoLogger.warn(meta, msg), |
| 90 | + error: (msg, meta) => pinoLogger.error(meta, msg), |
| 91 | +}; |
| 92 | + |
| 93 | +chat({ |
| 94 | + adapter: openaiText("gpt-4o"), |
| 95 | + messages, |
| 96 | + debug: { logger }, // all categories on, piped to pino |
| 97 | +}); |
| 98 | +``` |
| 99 | + |
| 100 | +The default logger is exported as `ConsoleLogger` if you want to wrap it: |
| 101 | + |
| 102 | +```typescript |
| 103 | +import { ConsoleLogger } from "@tanstack/ai"; |
| 104 | +``` |
| 105 | + |
| 106 | +### Your `Logger` is wrapped in a try/catch |
| 107 | + |
| 108 | +If your `Logger` implementation throws — a cyclic-meta `JSON.stringify`, a transport that rejects synchronously, a typo in a bound `this` — the exception is swallowed so it never masks the real error that triggered the log call (for example, a provider SDK failure inside the chat stream). You won't see the log line, but the pipeline error still surfaces through thrown exceptions and `RUN_ERROR` chunks. |
| 109 | + |
| 110 | +If you need to know when your own logger is failing, guard inside your implementation: |
| 111 | + |
| 112 | +```typescript |
| 113 | +const logger: Logger = { |
| 114 | + debug: (msg, meta) => { |
| 115 | + try { |
| 116 | + pinoLogger.debug(meta, msg); |
| 117 | + } catch (err) { |
| 118 | + // surface to wherever you track infra errors |
| 119 | + process.stderr.write(`logger failed: ${String(err)}\n`); |
| 120 | + } |
| 121 | + }, |
| 122 | + // ... info, warn, error |
| 123 | +}; |
| 124 | +``` |
| 125 | + |
| 126 | +## Categories reference |
| 127 | + |
| 128 | +| Category | Logs | Applies to | |
| 129 | +|----------|------|------------| |
| 130 | +| `request` | Outgoing call to a provider (model, message count, tool count) | All activities | |
| 131 | +| `provider` | Every raw chunk/frame received from a provider SDK | Streaming activities (chat, realtime) | |
| 132 | +| `output` | Every chunk or result yielded to the caller | All activities | |
| 133 | +| `middleware` | Inputs and outputs around every middleware hook | `chat()` only | |
| 134 | +| `tools` | Before/after tool call execution | `chat()` only | |
| 135 | +| `agentLoop` | Agent-loop iterations and phase transitions | `chat()` only | |
| 136 | +| `config` | Config transforms returned by middleware `onConfig` hooks | `chat()` only | |
| 137 | +| `errors` | Every caught error anywhere in the pipeline | All activities | |
| 138 | + |
| 139 | +## Errors are always logged |
| 140 | + |
| 141 | +Errors flow through the logger unconditionally — even when you omit `debug`: |
| 142 | + |
| 143 | +```typescript |
| 144 | +chat({ adapter, messages }); // still prints [tanstack-ai:errors] ... on failure |
| 145 | +``` |
| 146 | + |
| 147 | +To fully silence (including errors), set `debug: false` or `debug: { errors: false }`. Errors also always reach the caller via thrown exceptions or `RUN_ERROR` stream chunks — the logger is additive, not the only surface. |
| 148 | + |
| 149 | +## Non-chat activities |
| 150 | + |
| 151 | +The same `debug` option works on every activity: |
| 152 | + |
| 153 | +```typescript |
| 154 | +summarize({ adapter, text, debug: true }); |
| 155 | +generateImage({ adapter, prompt: "a cat", debug: { logger } }); |
| 156 | +generateSpeech({ adapter, text, debug: { request: true } }); |
| 157 | +``` |
| 158 | + |
| 159 | +The chat-only categories (`middleware`, `tools`, `agentLoop`, `config`) simply never fire for these activities because those concepts don't exist in their pipelines. |
| 160 | + |
| 161 | +## Related |
| 162 | + |
| 163 | +If you're building middleware and want to see chunks flow through it, `debug: { middleware: true }` is faster than writing a logging middleware. See [Middleware](./middleware) for writing your own middleware, or [Observability](./observability) for the programmatic event client. |
0 commit comments