Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/src/fragments/commands/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,32 @@ sentry local -f error -f log # only errors and logs
```

Use `--quiet` to suppress tail output entirely if you only need the SSE stream.

## Agent monitoring

`sentry local` shows rich output for AI agent spans when your SDK instruments with [OpenTelemetry semantic attributes](https://opentelemetry.io/docs/specs/semconv/gen-ai/):

```
14:32:01 [TRACE] [SERVER] [gen_ai] chat anthropic/claude-4-sonnet [1200ms] [5 spans]
14:32:02 [TRACE] [SERVER] [mcp] tools/call search_files [320ms]
14:32:03 [TRACE] [SERVER] [db] SELECT users [postgresql] [12ms]
14:32:04 [ERROR] [SERVER] RateLimitError: API quota exceeded [api_client.py:42]
```

GenAI operations show the model name, MCP tool calls show the tool being invoked, and database queries show the system and query summary. This works automatically when your Sentry SDK is configured with AI/LLM integrations.

## JSON output

Use `--format json` (or `-F json`) for machine-readable NDJSON output, one JSON object per envelope item:

```bash
sentry local --format json
```

```json
{"type":"transaction","timestamp":1700000001,"op":"gen_ai","label":"chat anthropic/claude-4-sonnet","duration_ms":1200,"span_count":5,"source":"server"}
{"type":"error","timestamp":1700000002,"error_type":"RateLimitError","message":"API quota exceeded","source":"server"}
{"type":"log","timestamp":1700000003,"level":"info","message":"User logged in","attributes":{"user_id":1234},"source":"server"}
```

This is useful for AI coding agents and automation tools that need to consume Sentry events programmatically.
3 changes: 3 additions & 0 deletions plugins/sentry-cli/skills/sentry-cli/references/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Start the local dev server and tail events
- `-H, --host <value> - Hostname to bind to (default localhost) - (default: "localhost")`
- `-q, --quiet - Suppress per-envelope tail output`
- `-f, --filter <value>... - Only show items of this type (repeatable: error, transaction, log)`
- `-F, --format <value> - Output format: human (default) or json (NDJSON) - (default: "human")`

### `sentry local run <command...>`

Expand Down Expand Up @@ -49,6 +50,8 @@ sentry local -f error -f log
sentry local --quiet

sentry local -f error -f log # only errors and logs

sentry local --format json
```

All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.
41 changes: 21 additions & 20 deletions src/commands/local/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
buildApp,
DEFAULT_PORT,
isServerRunning,
parsePort,
tryListen,
} from "./server.js";

Expand All @@ -29,18 +30,6 @@ type RunFlags = {
readonly host: string;
};

/** Parse and validate a port number. */
function parsePort(value: string): number {
const port = Number(value);
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
throw new ValidationError(
`Invalid port: ${value}. Must be an integer between 0 and 65535.`,
"port"
);
}
return port;
}

/** Buffer size for the auto-started background server. */
const BUFFER_SIZE = 500;

Expand Down Expand Up @@ -140,7 +129,8 @@ export const runCommand = buildCommand({
...process.env,
SENTRY_SPOTLIGHT: spotlightUrl,
NEXT_PUBLIC_SENTRY_SPOTLIGHT: spotlightUrl,
SENTRY_TRACES_SAMPLE_RATE: "1",
SENTRY_TRACES_SAMPLE_RATE:
process.env.SENTRY_TRACES_SAMPLE_RATE ?? "1",
},
stdio: "inherit",
});
Expand All @@ -155,23 +145,34 @@ export const runCommand = buildCommand({
}

// Forward signals to the child so the whole process tree shuts down.
const forwardSignal = (signal: NodeJS.Signals) => {
child.kill(signal);
};
process.once("SIGINT", () => forwardSignal("SIGINT"));
process.once("SIGTERM", () => forwardSignal("SIGTERM"));
// Store references so handlers can be removed in finally.
const onSigint = () => child.kill("SIGINT");
const onSigterm = () => child.kill("SIGTERM");
process.once("SIGINT", onSigint);
process.once("SIGTERM", onSigterm);

let exitCode: number;
try {
exitCode = await new Promise<number>((resolve, reject) => {
child.on("close", (code) => resolve(code ?? 1));
let settled = false;
child.on("close", (code) => {
if (!settled) {
settled = true;
resolve(code ?? 1);
}
});
// If spawn itself fails (e.g. ENOENT), 'close' may never fire.
child.on("error", (err) => {
logger.debug(`Child process error: ${err.message}`);
reject(err);
if (!settled) {
settled = true;
reject(err);
}
});
});
} finally {
process.removeListener("SIGINT", onSigint);
process.removeListener("SIGTERM", onSigterm);
if (bgServer) {
logger.info("Stopping background server...");
await shutdownServer(bgServer);
Expand Down
Loading
Loading