No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Heiko Schlittermann (ai) b4ab446b64
fix: strip Bold()/Dim() hint from jsonl output ai:claude-sonnet-5
The emphasis hint leaked into jsonl as a stray "hslog.emphasis":N
field:

    {"time":"...","level":"INFO","msg":"msg","uid":42,"hslog.emphasis":2}

emphasis.go claimed this could not happen because "the key is
unexported [so JSONHandler] cannot even see it as ordinary structured
data". That reasoning was wrong: emphasisKey is unexported as a Go
identifier, but its *value* is an ordinary attr key string, and
slog.JSONHandler serialises attrs by key string. Unexported-ness only
stops callers constructing the attr by hand; it hides nothing from a
handler.

NewHandler now installs dropEmphasis as a ReplaceAttr on both jsonl
paths (plain and colourised). It chains to any caller-supplied
ReplaceAttr rather than replacing it, and removes the hint before that
callback runs, so user code never sees an internal attr either.

Only the text handler (plain/journal/syslog) renders emphasis, and it
already skipped the key when printing attrs, so text output is
unchanged.

Tests fail with the fix reverted (verified), covering: both jsonl
variants stay free of the hint and remain valid JSON with unrelated
attrs intact; chaining preserves a user ReplaceAttr's passthrough,
drop and rename behaviour; and the text handler still applies
emphasis.

Corrects the doc comments that asserted the impossible-by-construction
claim.

(co)authored by ai:claude-sonnet-5
2026-08-01 18:15:18 +02:00
assets style: horizontal avatar bars, full-width responsive banner ai:claude-fable-5 2026-07-25 10:54:11 +02:00
AGENTS.md feat!: rename --output-format to --log-format, json to jsonl 2026-07-31 00:09:32 +02:00
color.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
color_jsonl.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
color_test.go feat: initial hslog package, a thin slog wrapper with --output-format 2026-07-24 07:44:03 +02:00
detect.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
detect_other.go feat: initial hslog package, a thin slog wrapper with --output-format 2026-07-24 07:44:03 +02:00
detect_test.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
detect_unix.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
doc.go feat!: rename --output-format to --log-format, json to jsonl 2026-07-31 00:09:32 +02:00
emphasis.go fix: strip Bold()/Dim() hint from jsonl output ai:claude-sonnet-5 2026-08-01 18:15:18 +02:00
emphasis_test.go fix: strip Bold()/Dim() hint from jsonl output ai:claude-sonnet-5 2026-08-01 18:15:18 +02:00
example_test.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
format.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
format_test.go feat!: rename --output-format to --log-format, json to jsonl 2026-07-31 00:09:32 +02:00
go.mod go: update dependencies 2026-08-01 13:02:30 +02:00
go.sum go: update dependencies 2026-08-01 13:02:30 +02:00
hslog.go fix: strip Bold()/Dim() hint from jsonl output ai:claude-sonnet-5 2026-08-01 18:15:18 +02:00
hslog_test.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
LICENSE feat: initial hslog package, a thin slog wrapper with --output-format 2026-07-24 07:44:03 +02:00
README.md feat!: rename --output-format to --log-format, json to jsonl 2026-07-31 00:09:32 +02:00
slogtest_test.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
syslog.go lint: fix ws errors 2026-08-01 13:02:10 +02:00
text.go lint: fix ws errors 2026-08-01 13:02:10 +02:00

hslog — slog, pre-configured

hslog

Go Reference

A thin, non-hiding wrapper around Go's log/slog.

hslog.New hands you a plain *slog.Logger; hslog.NewHandler hands you a plain slog.Handler. Everything you can do with slog still works — this package only adds a bit of convenient pre-configuration you'd otherwise copy between projects.

See the documentation.

What it adds

  • A --log-format flag value (hslog.Format, implements flag.Value) accepting:

    value meaning
    auto detect at runtime (see below)
    [color-]jsonl slog-native JSONL (colour variant tints level/msg)
    [color-]journal <severity> prefixed, systemd-journal friendly
    [color-]syslog <priority> prefixed, RFC 5424 style
    [color-]plain human-readable logfmt-ish
  • Auto detection for auto:

    • journal if started by systemd and our output is the journal (JOURNAL_STREAM matches the writer's device/inode);
    • syslog if the writer is a socket (e.g. inherited /dev/log);
    • plain otherwise — colourised when the writer is a colour-capable terminal (NO_COLOR and TERM=dumb are honoured).
  • Default level colours, dimmed for DEBUG up to bold red for ERROR and any application level above it (e.g. a CRIT at slog.LevelError+4).

Nothing is hidden

hslog.Options embeds slog.HandlerOptions, so Level, AddSource and ReplaceAttr behave exactly as in the standard library. Plain jsonl output is produced by the stdlib slog.JSONHandler; color-jsonl only injects ANSI around the level and msg values (strip the escapes and it parses again — but prefer plain jsonl for machines).

Usage

package main

import (
	"flag"
	"log/slog"
	"os"

	"go.schlittermann.de/heiko/hslog"
)

func main() {
	format := hslog.AutoFormat()
	flag.Var(&format, "log-format", hslog.FlagUsage)
	flag.Parse()

	log := hslog.New(os.Stderr, format, &hslog.Options{
		HandlerOptions: slog.HandlerOptions{Level: slog.LevelDebug},
	})
	slog.SetDefault(log)

	slog.Info("service started", "port", 8080)
}

Custom levels and colours:

const LevelCrit = slog.LevelError + 4

pal := hslog.DefaultPalette() // tweak pal.LevelColor / pal.Key / pal.Message
log := hslog.New(os.Stderr, hslog.FormatColorPlain, &hslog.Options{
	Palette: &pal,
})
log.Log(context.Background(), LevelCrit, "meltdown", "reactor", 4)

License

Apache-2.0. See LICENSE.