Skip to content
Back to skills

Debug Logging

ASecurity

Pluggable, category-toggleable debug logging for TanStack AI activities. Toggle with `debug: true | false | DebugConfig` on chat(), summarize(), generateImage(), generateSpeech(), generateTranscription(), generateVideo(). Categories: request, provider, output, middleware, tools, agentLoop, config, errors. Pipe into pino/winston/etc via `debug: { logger }`. Errors log by default even when `debug` is omitted; silence with `debug: false`.

  • 3,166 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added May 26, 2026
toolstypescriptgo

Works with

  • cli

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add TanStack/ai --skill debug-logging --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Debug Logging?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Debug Logging
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/tanstack-debug-logging/badge)](https://www.skillsdirectory.com/skills/tanstack-debug-logging)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ai-core/debug-logging
description: >
  Pluggable, category-toggleable debug logging for TanStack AI activities.
  Toggle with `debug: true | false | DebugConfig` on chat(), summarize(),
  generateImage(), generateSpeech(), generateTranscription(), generateVideo().
  Categories: request, provider, output, middleware, tools, agentLoop,
  config, errors. Pipe into pino/winston/etc via `debug: { logger }`. Errors
  log by default even when `debug` is omitted; silence with `debug: false`.
type: sub-skill
library: tanstack-ai
library_version: '0.42.0'
sources:
  - 'TanStack/ai:docs/advanced/debug-logging.md'
---

# Debug Logging

> **Dependency note:** This skill builds on ai-core. Read it first for critical rules.

Use this skill when you need to turn debug logging on or off, narrow what's
printed, or pipe logs into a custom logger (pino, winston, etc.). The same
`debug` option works on every activity β€” `chat()`, `summarize()`,
`generateImage()`, `generateSpeech()`, `generateTranscription()`,
`generateVideo()`.

## Turn it on

```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const messages = [{ role: 'user' as const, content: 'Hello' }]

const stream = chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  debug: true, // all categories on, prints to console
})
```

Each log line is prefixed with an emoji and `[tanstack-ai:<category>]`:

```
πŸ“€ [tanstack-ai:request] πŸ“€ activity=chat provider=openai model=gpt-5.2 messages=1 tools=0 stream=true
πŸ” [tanstack-ai:agentLoop] πŸ” run started
πŸ“₯ [tanstack-ai:provider] πŸ“₯ provider=openai type=response.output_text.delta
πŸ“¨ [tanstack-ai:output] πŸ“¨ type=TEXT_MESSAGE_CONTENT
```

## Turn it off

```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const messages = [{ role: 'user' as const, content: 'Hello' }]

chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  debug: false, // silence everything, including errors
})
```

Omitting `debug` is **not** the same as `debug: false`. When omitted, the
`errors` category is still on (errors are cheap and important). Use
`debug: false` or `debug: { errors: false }` for true silence.

## `DebugOption` β€” the accepted shapes

```typescript
import type { Logger } from '@tanstack/ai'

// As exported by '@tanstack/ai'
type DebugOption = boolean | DebugConfig

interface DebugConfig {
  // Per-category flags. Any flag omitted from a DebugConfig defaults to true.
  request?: boolean
  provider?: boolean
  output?: boolean
  middleware?: boolean
  tools?: boolean
  agentLoop?: boolean
  config?: boolean
  errors?: boolean
  // Optional custom logger. Defaults to ConsoleLogger.
  logger?: Logger
}
```

Resolution rules for the `debug?: DebugOption` field on every activity:

| `debug` value         | Effect                                                                       |
| --------------------- | ---------------------------------------------------------------------------- |
| omitted (`undefined`) | Only `errors` is active; default `ConsoleLogger`.                            |
| `true`                | All categories on; default `ConsoleLogger`.                                  |
| `false`               | All categories off (including `errors`); default `ConsoleLogger`.            |
| `DebugConfig` object  | Each unspecified flag defaults to `true`; `logger` replaces `ConsoleLogger`. |

## Narrow what's printed

Pass a `DebugConfig` object. Unspecified categories default to `true`, so it's
easiest to toggle by setting specific flags to `false`:

```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const messages = [{ role: 'user' as const, content: 'Hello' }]

chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  debug: { middleware: false }, // everything except middleware
})
```

To print only a specific set, set the rest to `false` explicitly:

```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const messages = [{ role: 'user' as const, content: 'Hello' }]

chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  debug: {
    provider: true,
    output: true,
    middleware: false,
    tools: false,
    agentLoop: false,
    config: false,
    errors: true, // keep errors on β€” they're cheap and important
    request: false,
  },
})
```

## Pipe into your own logger

```typescript
import { chat, type Logger } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import pino from 'pino'

const pinoLogger = pino()
const logger: Logger = {
  debug: (msg, meta) => pinoLogger.debug(meta, msg),
  info: (msg, meta) => pinoLogger.info(meta, msg),
  warn: (msg, meta) => pinoLogger.warn(meta, msg),
  error: (msg, meta) => pinoLogger.error(meta, msg),
}

const messages = [{ role: 'user' as const, content: 'Hello' }]

chat({
  adapter: openaiText('gpt-5.5'),
  messages,
  debug: { logger }, // all categories on, piped to pino
})
```

The default console logger is exported as `ConsoleLogger` if you want to wrap
it:

```typescript
import { ConsoleLogger } from '@tanstack/ai'
```

## Categories

| Category     | Logs                                                           | Applies to                            |
| ------------ | -------------------------------------------------------------- | ------------------------------------- |
| `request`    | Outgoing call to a provider (model, message count, tool count) | All activities                        |
| `provider`   | Every raw chunk/frame received from a provider SDK             | Streaming activities (chat, realtime) |
| `output`     | Every chunk or result yielded to the caller                    | All activities                        |
| `middleware` | Inputs and outputs around every middleware hook                | `chat()` only                         |
| `tools`      | Before/after tool call execution                               | `chat()` only                         |
| `agentLoop`  | Agent-loop iterations and phase transitions                    | `chat()` only                         |
| `config`     | Config transforms returned by middleware `onConfig` hooks      | `chat()` only                         |
| `errors`     | Every caught error anywhere in the pipeline                    | All activities                        |

Chat-only categories simply never fire for non-chat activities β€” those
concepts don't exist in their pipelines.

## Non-chat activities

Same `debug` option everywhere:

```typescript
import {
  summarize,
  generateImage,
  generateSpeech,
  generateTranscription,
  generateVideo,
} from '@tanstack/ai'
import {
  openaiSummarize,
  openaiImage,
  openaiSpeech,
  openaiTranscription,
  openaiVideo,
} from '@tanstack/ai-openai'
import { logger } from './logger'
import { audio } from './recording'

summarize({
  adapter: openaiSummarize('gpt-5.5'),
  text: 'Long article…',
  debug: true,
})
generateImage({
  adapter: openaiImage('gpt-image-2'),
  prompt: 'a cat',
  debug: { logger },
})
generateSpeech({
  adapter: openaiSpeech('tts-1-hd'),
  text: 'Hello',
  debug: { request: true },
})
generateTranscription({
  adapter: openaiTranscription('gpt-4o-transcribe'),
  audio,
  debug: false,
})
generateVideo({
  adapter: openaiVideo('sora-2'),
  prompt: 'a wave',
  debug: { output: true },
})
```

Realtime session adapters in provider packages (e.g. `openaiRealtime`,
`elevenlabsRealtime`) accept the same `debug?: DebugOption` on their session
options. They emit `request`, `provider`, and `errors` lines; the chat-only
categories don't apply.

## Common Mistakes

### a. HIGH: Treating omitted `debug` as silent

```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const adapter = openaiText('gpt-5.5')
const messages = [{ role: 'user' as const, content: 'Hello' }]

// WRONG β€” expecting this to be completely silent
chat({ adapter, messages })
// Errors still print via [tanstack-ai:errors] ... on failure.

// CORRECT β€” explicit silence
chat({ adapter, messages, debug: false })
chat({ adapter, messages, debug: { errors: false } })
```

`debug` undefined means "only errors"; `debug: false` means "nothing at all".

Source: docs/advanced/debug-logging.md

### b. MEDIUM: Reaching for middleware when `debug` would do

```typescript
import { chat, type ChatMiddleware } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

const adapter = openaiText('gpt-5.5')
const messages = [{ role: 'user' as const, content: 'Hello' }]

// WRONG β€” writing logging middleware to see chunks flow
const chunkLogger: ChatMiddleware = {
  name: 'chunk-logger',
  onChunk: (ctx, chunk) => {
    console.log(chunk.type, chunk)
  },
}
chat({ adapter, messages, middleware: [chunkLogger] })

// CORRECT β€” just turn on the relevant categories
chat({
  adapter,
  messages,
  debug: { provider: true, output: true },
})
```

For observing the built-in pipeline, the `debug` option is strictly faster
than writing logging middleware. Reach for middleware when you need to
_transform_ chunks, not just see them.

Source: docs/advanced/debug-logging.md

### c. LOW: Logger implementation that can throw

A user-supplied `Logger` that throws will have its exception swallowed by the
SDK so it never masks the real error that triggered the log call. Still,
prefer implementations that don't throw β€” silenced exceptions are harder to
debug than loud ones.

```typescript
import type { Logger } from '@tanstack/ai'

// WRONG β€” a logger that can throw on serialization
const fragile: Logger = {
  debug: (msg, meta) => console.debug(msg, JSON.stringify(meta)), // cyclic meta β†’ throws
  info: (msg, meta) => console.info(msg, JSON.stringify(meta)),
  warn: (msg, meta) => console.warn(msg, JSON.stringify(meta)),
  error: (msg, meta) => console.error(msg, JSON.stringify(meta)),
}

// CORRECT β€” guard serialization in the logger itself
const guarded =
  (log: (...args: Array<unknown>) => void): Logger['debug'] =>
  (msg, meta) => {
    try {
      log(msg, JSON.stringify(meta))
    } catch {
      log(msg) // fall back to the bare message rather than throw
    }
  }

const safe: Logger = {
  debug: guarded(console.debug),
  info: guarded(console.info),
  warn: guarded(console.warn),
  error: guarded(console.error),
}
```

Source: packages/ai/src/logger/internal-logger.ts

## Cross-References

- See also: **ai-core/middleware/SKILL.md** β€” if you need to transform
  chunks/config, not just observe them.
- See also: **Observability** (`docs/advanced/observability.md`) β€” the
  programmatic event client for a richer, structured feed beyond log lines.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.