You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a --output-format=json|logfmt|raw flag (matching the existing --input-format=auto|json|logfmt enum) so hl can re-emit parsed records as structured output instead of (or in addition to) the human-readable default. The default behavior stays unchanged; the flag is opt-in.
Concretely: after parsing + filtering + sorting, emit one record per line in the chosen machine-readable format. For --output-format=json this means NDJSON (one JSON object per line, matching how jq expects input). For --output-format=logfmt it means the standard key=value key="quoted value" shape.
Motivation and Use Case
hl already does all the work to parse structured logs — JSON, logfmt, even mixed/prefixed inputs. Today the parsed records are formatted only for the human eye, which means there's no way to:
Convert formats mid-pipeline.hl input.logfmt --output-format=json | jq '.level == "error"' — re-emit logfmt as JSON, then filter with jq. Today: not possible without a separate converter (logfmt2json, etc.).
Sort + filter + re-emit as JSON.hl -s -l error -P app.log --output-format=json | jq -r '.trace_id' — sort by timestamp, filter to errors only, then extract a field across all matching lines. Today: impossible without leaving hl, since hl's output is human-formatted text that jq can't parse back.
Pipe into dashboards / log shippers that want JSON over stdout.hl -F app.log --output-format=json | vector --config ... — follow a log file and emit structured records to a log shipper. Today: hl emits ANSI-colored human text; the shipper has to reverse-parse it.
The maintainer's existing InputFormat enum (Auto | Json | Logfmt, in src/cli.rs) already covers the input-side taxonomy. This feature just mirrors that taxonomy on the output side, turning hl from "human reader only" into "structured-log preprocessor".
Example Usage
# Convert logfmt → NDJSON, filter to errors, extract trace_id
hl --input-format=logfmt --output-format=json -l error app.logfmt | jq -r '.trace_id'# Convert JSON → logfmt (a frequently-requested but missing direction)
hl --input-format=json --output-format=logfmt -P app.json
# Sort JSON logs by timestamp, then pipe to a structured sink
hl -s --output-format=json --paging=never app.jsonl
# Follow a log file and emit NDJSON as lines arrive
hl -F --output-format=json --output-delimiter=newline app.log
Expected output of --output-format=json (NDJSON, one record per line):
--raw already exists — but --raw dumps the original source bytes; if input was a 2KB pretty-printed JSON object, --raw gives you 2KB of pretty JSON, not the parsed-and-rewritten-as-compact-JSON record. Different feature.
Customizable output templates (issue Customizable Output Formatting and Custom Fields #611) — much larger scope: needs the internal model refactor the maintainer is mid-way through (see maintainer comment on Customizable Output Formatting and Custom Fields #611, 2025-11-25: "I don't expect it to land for at least a couple of months"). A structured-output flag is the inverse direction: instead of a user-defined human template, it's a fixed machine schema, and only requires a small re-emit pass at the existing output boundary.
External converter (logfmt2json, jq, etc.) — adds dependencies and breaks hl's value proposition as a single-binary pipeline tool. Users would have to know their input format, pick a converter, and chain it manually.
Just emit --raw and let jq parse it — only works if input was already JSON; doesn't help the logfmt-input case, and doesn't preserve hl's parsed/sorted/filtered state (e.g., timestamp reordering from -s is lost).
Additional Context
The maintainer has shipped every input-format flag (--input-format=auto|json|logfmt) and the InputFormat enum in src/cli.rs. Mirroring this on output is symmetric and fits the existing clap ValueEnum pattern.
Suggested implementation scope: a new OutputFormat enum (Pretty | Json | Logfmt, with Pretty as the default preserving current behavior) in src/cli.rs, a new src/output/json.rs and src/output/logfmt.rs formatter module paralleling src/output.rs, and a dispatch site in the render pipeline. ~150-300 lines, no internal model refactor required.
Happy to send a PR if the maintainer agrees this is a useful direction. Filing from fuleinist, a recurring GitHub-issue contributor.
Feature Description
Add a
--output-format=json|logfmt|rawflag (matching the existing--input-format=auto|json|logfmtenum) sohlcan re-emit parsed records as structured output instead of (or in addition to) the human-readable default. The default behavior stays unchanged; the flag is opt-in.Concretely: after parsing + filtering + sorting, emit one record per line in the chosen machine-readable format. For
--output-format=jsonthis means NDJSON (one JSON object per line, matching howjqexpects input). For--output-format=logfmtit means the standardkey=value key="quoted value"shape.Motivation and Use Case
hlalready does all the work to parse structured logs — JSON, logfmt, even mixed/prefixed inputs. Today the parsed records are formatted only for the human eye, which means there's no way to:hl input.logfmt --output-format=json | jq '.level == "error"'— re-emit logfmt as JSON, then filter withjq. Today: not possible without a separate converter (logfmt2json, etc.).hl -s -l error -P app.log --output-format=json | jq -r '.trace_id'— sort by timestamp, filter to errors only, then extract a field across all matching lines. Today: impossible without leaving hl, since hl's output is human-formatted text thatjqcan't parse back.hl -F app.log --output-format=json | vector --config ...— follow a log file and emit structured records to a log shipper. Today: hl emits ANSI-colored human text; the shipper has to reverse-parse it.The maintainer's existing
InputFormatenum (Auto | Json | Logfmt, insrc/cli.rs) already covers the input-side taxonomy. This feature just mirrors that taxonomy on the output side, turning hl from "human reader only" into "structured-log preprocessor".Example Usage
Expected output of
--output-format=json(NDJSON, one record per line):{"timestamp":"2026-07-06T10:00:00Z","level":"INFO","message":"Request received","request_id":"12345","duration_ms":150} {"timestamp":"2026-07-06T10:00:01Z","level":"ERROR","message":"DB timeout","request_id":"12346","duration_ms":3001}Expected output of
--output-format=logfmt:Alternatives Considered
--rawalready exists — but--rawdumps the original source bytes; if input was a 2KB pretty-printed JSON object,--rawgives you 2KB of pretty JSON, not the parsed-and-rewritten-as-compact-JSON record. Different feature.logfmt2json,jq, etc.) — adds dependencies and breaks hl's value proposition as a single-binary pipeline tool. Users would have to know their input format, pick a converter, and chain it manually.--rawand letjqparse it — only works if input was already JSON; doesn't help the logfmt-input case, and doesn't preserve hl's parsed/sorted/filtered state (e.g., timestamp reordering from-sis lost).Additional Context
--input-format=auto|json|logfmt) and theInputFormatenum insrc/cli.rs. Mirroring this on output is symmetric and fits the existing clapValueEnumpattern.OutputFormatenum (Pretty | Json | Logfmt, withPrettyas the default preserving current behavior) insrc/cli.rs, a newsrc/output/json.rsandsrc/output/logfmt.rsformatter module parallelingsrc/output.rs, and a dispatch site in the render pipeline. ~150-300 lines, no internal model refactor required.