weftslognats

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

README

weft-slognats

Pure-Go slog.Handler fan-out that pipes logs to stderr AND a NATS subject in one shot. The bridge weft-doctor needs to receive the logs it classifies — without it, the diagnosis pipeline is empty.

Why

Every openweft component (weft-agent, weft-microvm-agent, drivers, HA agents, runners) logs structured records via Go's log/slog. Until now they all wrote to stderr only. weft-doctor subscribes to NATS and expects JSON-encoded slog records there. This package is the one-line swap that makes the pipeline complete : same stderr output as before, plus WARN+ERROR records published on a per-component NATS subject.

Wiring (one line in main.go)

import "github.com/openweft/weft-slognats"

natsConn, _ := nats.Connect("nats://nats.weft.svc:4222")

log := slog.New(weftslognats.NewHandler(weftslognats.Options{
    Base:    slog.NewJSONHandler(os.Stderr, nil),
    Conn:    weftslognats.FromConn(natsConn),
    Subject: "weft.agent." + hostUUID + ".log",
}))
slog.SetDefault(log)

That's it. Every slog.Info(...), slog.Warn(...), slog.Error(...) in the codebase keeps working ; WARN+ERROR records additionally land on the NATS subject as JSON-encoded slog records — the exact format weft-doctor/ingest expects.

Subject convention

weft.<component>.<host_or_vm_id>.log

Examples :

Component Subject
weft-agent weft.agent.dc1-r1-h1.log
weft-microvm-agent weft.microvm-agent.<vm_uuid>.log
weft-driver-qemu weft.driver.qemu.dc1-r1-h1.log
weft-ha-postgresql weft.ha.postgres.dc1-r1-h1.log

weft-doctor subscribes via weft.> (everything) or finer wildcards to scope its analysis.

Degraded mode

Conn: nil is supported : the handler degrades to base-only (stderr only), no panic. Useful for :

  • Local dev / tests (no NATS reachable)
  • Initial bring-up when NATS isn't running yet
  • A NATS outage where you still want stderr logging

When Conn != nil but the publish fails at runtime, the error is surfaced via Options.OnPublishError (defaults to no-op) but never bubbles back to the caller — logging must never fail the caller.

Configuration

Option Default Notes
Base Required. nil panics at startup.
Conn nil nil = base-only (graceful degrade).
Subject Required when Conn != nil.
MinLevel slog.LevelWarn Floor for NATS publish (stderr always gets everything Base allows).
OnPublishError no-op Callback on Publish failure.

Payload schema

The NATS payload is byte-for-byte the same as what slog.JSONHandler emits to stderr (minus the trailing newline) :

{"time":"2026-06-08T19:42:01Z","level":"ERROR","msg":"VM start failed","vm_uuid":"abc","retry":3}

This means weft-doctor's ingest.parseSlog has ONE format to handle across all components.

Build matrix

Pure-Go, CGO=0. Tested cross-build : linux + darwin + openbsd + freebsd + netbsd × amd64 + arm64 (10 targets).

License

BSD 3-Clause — see LICENSE.

Documentation

Overview

Package weftslognats provides a slog.Handler that fans logs out to both stderr (or any base handler) and a NATS subject. Errors and warnings published on NATS are what weft-doctor's NATSIngester subscribes to ; the stderr path stays untouched so existing tooling (operator tailing, kubectl-like CLI sniff, journald) keeps working.

Wire it once in main.go :

conn, _ := nats.Connect("nats://nats.weft.svc:4222")
log := slog.New(weftslognats.NewHandler(weftslognats.Options{
    Base:    slog.NewJSONHandler(os.Stderr, nil),
    Conn:    conn,
    Subject: "weft.agent." + hostUUID + ".log",
}))
slog.SetDefault(log)

Everything that already calls slog.{Info,Warn,Error} keeps working unchanged ; WARN+ERROR records additionally flow onto NATS in slog.JSONHandler-compatible format so weft-doctor can parse them.

Index

Constants

View Source
const EnvNATSURL = "WEFT_NATS_URL"

EnvNATSURL is the env var the SetupFromEnv helper reads. Unset → the handler degrades to base-only (stderr) cleanly.

Variables

This section is empty.

Functions

func PanicReporter added in v0.3.0

func PanicReporter(name ...string)

PanicReporter recovers a panic, slog.Errors the value + stack trace, sleeps briefly to let the NATS publish flush, then re-panics so systemd still observes the abnormal exit and journalctl still prints the runtime's canonical stack.

`name` defaults to "weft" if empty ; pass the binary name when you have multiple binaries logging to the same NATS subject so classifiers can disambiguate (e.g. "weft-driver-qemu", "weft-microvm-agent").

Off-NATS hosts get identical behaviour as before : slog falls back to stderr, which is where the stack was going to land anyway.

func SetupFromEnv added in v0.2.0

func SetupFromEnv(subject string) (*slog.Logger, io.Closer)

SetupFromEnv wires the canonical openweft slog setup in one call. Returned Logger writes JSON to stderr (always) and additionally publishes WARN+ERROR records to NATS on the given subject (when WEFT_NATS_URL is set + reachable).

Returned io.Closer drains the NATS connection on shutdown ; safe to call when no NATS connection was opened (no-op).

Subject convention :

weft.<component>.<host_or_vm_id>.log

e.g. "weft.agent."+hostUUID+".log",

"weft.driver.qemu."+hostUUID+".log",
"weft.ha.postgres."+nodeName+".log".

Failure modes :

  • WEFT_NATS_URL unset → degraded mode, no error
  • nats.Connect fails → degraded mode + WARN to stderr
  • subject == "" → panic (Handler contract)

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is a slog.Handler fan-out. Exported so callers can type- assert if needed ; constructed via NewHandler.

func NewHandler

func NewHandler(opts Options) *Handler

NewHandler builds the fan-out. Panics on nil Base — see Options.Base rationale.

func (*Handler) Enabled

func (h *Handler) Enabled(ctx context.Context, lvl slog.Level) bool

Enabled : the union of base + nats. Anything either side wants to see, we evaluate. In practice base usually allows INFO+ ; nats allows WARN+ ; the union is INFO+.

func (*Handler) Handle

func (h *Handler) Handle(ctx context.Context, r slog.Record) error

Handle dispatches the record to base (always) and to NATS (when conn != nil AND lvl >= minLevel). Errors from base are propagated ; NATS publish errors are surfaced via OnPublishError and otherwise swallowed — we never want logging itself to fail the caller.

func (*Handler) WithAttrs

func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new handler that wraps both sides with the same attrs. Required for slog.Logger.With() to compose correctly.

func (*Handler) WithGroup

func (h *Handler) WithGroup(name string) slog.Handler

WithGroup is the group equivalent of WithAttrs. Same fan-out shape.

type Options

type Options struct {
	// Base receives every record (stderr / journald / whatever the
	// operator wired). Required ; nil panics — there's no use case
	// for "publish to NATS only", and silent drop on a malformed
	// config would mask outages.
	Base slog.Handler
	// Conn is the NATS connection. nil is ALLOWED and means
	// "publish nowhere, Base only" : useful for dev, tests, and
	// graceful degrade when NATS is unreachable at startup.
	Conn Publisher
	// Subject is the NATS subject WARN+ERROR records are published
	// to. Required when Conn is set. Convention :
	//   weft.<component>.<host_id>.log
	// e.g. weft.agent.dc1-r1-h1.log, weft.microvm-agent.<vm_uuid>.log.
	Subject string
	// MinLevel is the floor at which NATS publishing kicks in.
	// Default slog.LevelWarn — keeps the diagnosis pipeline focused
	// on actionable signal, not DEBUG/INFO chatter.
	MinLevel slog.Level
	// OnPublishError is called when a NATS publish fails. Default :
	// no-op (we don't want logging to itself cascade). Set to a
	// callback when you want visibility (e.g. bump a Prom metric).
	OnPublishError func(err error)
}

Options configures the handler.

type Publisher

type Publisher interface {
	Publish(subject string, data []byte) error
}

Publisher is the NATS subset the handler depends on. Decoupled so tests can drop in a fake without spinning up an embedded NATS.

func FromConn

func FromConn(c *nats.Conn) Publisher

FromConn wraps a *nats.Conn into the Publisher interface so the common call site (NewHandler(... Conn: weftslognats.FromConn(c))) reads naturally.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL