Skill: JSON log parsing

## When to use

7 stars

Best use case

Skill: JSON log parsing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

## When to use

Teams using Skill: JSON log parsing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/json-logs/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/developer-tools/log-tailer/skills/json-logs/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/json-logs/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Skill: JSON log parsing Compares

Feature / AgentSkill: JSON log parsingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

## When to use

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Skill: JSON log parsing

## When to use

Use this skill when implementing the log parser, handling structured log output from Node.js services using bunyan or pino, mapping numeric log levels to named levels, extracting fields from JSON log lines, or falling back to plain-text parsing when JSON is absent.

## Log format auto-detection

The parser applies the following algorithm to each incoming line:

1. Attempt `JSON.parse(line)`
2. If the parse succeeds, treat as structured JSON log
3. Extract `level`, `time`, `msg` (or `message`) fields from the parsed object
4. If the parse fails, treat the entire line as a plain-text message with level `info` and timestamp from wall clock

This matches the behavior of bunyan, pino, and Winston JSON transports.

## JSON log structure

A minimal valid structured log line:

```json
{"level":30,"time":1742040276003,"msg":"server started","pid":1,"hostname":"api"}
```

Field mapping:

| JSON field | Type | Description |
|---|---|---|
| `level` | number or string | Log level. Numbers follow bunyan convention. |
| `time` | number | Unix millisecond timestamp |
| `msg` or `message` | string | Human-readable message |
| All other fields | any | Stored as structured fields, shown in expanded view |

## Bunyan numeric level mapping

| Number | Named level | Color |
|---|---|---|
| 10 | trace | gray |
| 20 | debug | gray |
| 30 | info | white |
| 40 | warn | yellow |
| 50 | error | red |
| 60 | fatal | red (bright) |

Numbers outside these values are rounded down to the nearest tier. Numbers below 10 are treated as trace. Numbers above 60 are treated as fatal.

## Pino level strings

Pino can be configured to emit string levels. The parser accepts both numeric and string level values:

```json
{"level":"warn","time":1742040276003,"msg":"high memory usage"}
```

String levels accepted (case-insensitive): `trace`, `debug`, `info`, `warn`, `warning`, `error`, `fatal`, `critical`.

`warning` maps to `warn`. `critical` maps to `fatal`.

## Implementation reference

### LogLine interface

```typescript
interface LogLine {
  id:        string;         // UUID, assigned by parser
  source_id: string;         // source UUID
  ts:        number;         // Unix ms
  level:     LogLevel;       // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
  message:   string;
  fields:    Record<string, unknown> | null;
  raw:       string;         // original line text
}
```

### Parser function signature

```typescript
function parseLine(raw: string, sourceId: string): LogLine
```

### Level detection

```typescript
function detectLevel(parsed: Record<string, unknown>): LogLevel {
  const v = parsed['level'];
  if (typeof v === 'number') return numericToLevel(v);
  if (typeof v === 'string') return stringToLevel(v);
  return 'info';
}

function numericToLevel(n: number): LogLevel {
  if (n >= 60) return 'fatal';
  if (n >= 50) return 'error';
  if (n >= 40) return 'warn';
  if (n >= 30) return 'info';
  if (n >= 20) return 'debug';
  return 'trace';
}
```

### Timestamp extraction

```typescript
function extractTimestamp(parsed: Record<string, unknown>): number {
  const t = parsed['time'];
  if (typeof t === 'number' && t > 0) return t;
  // pino uses epoch ms; bunyan uses epoch ms
  // fall back to wall clock
  return Date.now();
}
```

### Field extraction

After extracting `level`, `time`, and `msg`/`message`, the remaining keys in the parsed object are stored as `fields`. Reserved keys to exclude from fields:

- `level`
- `time`
- `msg`
- `message`
- `v` (bunyan schema version)
- `pid`
- `hostname`
- `name`

All remaining top-level keys are stored in the `fields` map.

### Plain text fallback

When JSON parse fails, construct a LogLine with:

- `level`: `info`
- `ts`: `Date.now()`
- `message`: the full raw line with ANSI escape codes stripped
- `fields`: `null`
- `raw`: the original unmodified line

Strip ANSI codes using: `/\x1b\[[0-9;]*m/g`

## Filter engine

The filter engine operates on a `FilterState`:

```typescript
interface FilterState {
  minLevel:  LogLevel;           // inclusive lower bound
  sources:   string[] | null;    // null = all sources
  search:    string | null;      // regex pattern or null
}
```

A `LogLine` passes the filter when:

1. `levelOrder[line.level] >= levelOrder[filter.minLevel]`
2. `filter.sources === null || filter.sources.includes(line.source_id)`
3. `filter.search === null || new RegExp(filter.search, 'i').test(line.message)`

Level order:

```typescript
const levelOrder: Record<LogLevel, number> = {
  trace: 0, debug: 1, info: 2, warn: 3, error: 4, fatal: 5
};
```

Precompile the `RegExp` once per filter state change, not per line.

## SSE stream format

Log lines are pushed to connected clients over Server-Sent Events. Each event is a JSON-encoded `LogLine`:

```
data: {"id":"...","source_id":"...","ts":1742040276003,"level":"error","message":"...","fields":{...},"raw":"..."}\n\n
```

The SSE endpoint is `GET /api/stream`. Clients reconnect automatically using the browser's built-in `EventSource` reconnect behavior. The server sends a `data: ping\n\n` heartbeat every 15 seconds to keep the connection alive through proxies.

On initial connect, the server replays the ring buffer for each active source filtered by the request's `?level=` and `?source=` query parameters. Replay events are sent as type `replay` before switching to live `data` events:

```
event: replay
data: {...}\n\n
```

## Common pitfalls

### Treating the level field as always a number

Pino allows string levels. The parser must handle both `30` and `"info"` for the same level. Always check `typeof v`.

### Crashing on malformed JSON

Not every line from a process is valid JSON. A syntax error in the log line must not crash the parser. Wrap `JSON.parse` in a try/catch and fall back to plain text.

### Using `eval` or `Function` constructor for parsing

Use only `JSON.parse`. Never eval user-supplied log content.

### Storing `raw` only as parsed

Always store the original `raw` string unmodified. The dashboard may need to display the original line for plain-text sources or for debugging the parser.

### Off-by-one in level thresholds

The `minLevel` filter is inclusive. A filter of `warn` should show `warn`, `error`, and `fatal` lines. Verify with: `levelOrder[line.level] >= levelOrder[filter.minLevel]`.

Related Skills

json-differ

7
from heldernoid/agentic-build-templates

Compare two JSON blobs and view structural and value differences. Use when you need to compare API responses, config files, database records, or any two JSON objects. Triggers include "compare JSON", "JSON diff", "what changed", "JSON differences", "diff two objects", or any task involving comparing JSON data.

json-pipe

7
from heldernoid/agentic-build-templates

Pipe JSON between shell commands using jq-lite for transformation steps. Use when building shell pipelines that process JSON data through multiple transformation stages. Triggers include "pipe JSON", "shell pipeline JSON", "curl | jql", "transform JSON in pipeline", "chain JSON operations".

jsonl-format

7
from heldernoid/agentic-build-templates

JSONL format guide for LLM fine-tuning. Covers OpenAI, Anthropic, and Llama formats, format validation rules, conversion between formats, and quality checklist.

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview