Documentation
¶
Overview ¶
Package syplslog bridges sypl, and the standard library's structured logger, log/slog - in both directions, using only the standard library:
- `NewHandler` adapts a sypl logger into a `slog.Handler`, so code using the slog API logs through sypl's pipeline (outputs, processors, formatters).
- `Output` adapts an `*slog.Logger` into a sypl output, so a sypl logger forwards its processed messages - fields as attrs - to any slog handler.
Level mapping ¶
slog levels are sparse integers (Debug=-4, Info=0, Warn=4, Error=8), designed to accommodate in-between levels. The bridge maps them as follows:
slog level sypl level note ---------------------- -------------- ---------------------------------- < LevelDebug (-4) level.Trace e.g.: syplslog.LevelTrace (-8) [LevelDebug, LevelInfo) level.Debug [LevelInfo, LevelWarn) level.Info [LevelWarn, LevelError) level.Warn >= LevelError (8) level.Error incl. syplslog.LevelFatal (12)
In-between levels (e.g.: Info+2) map conservatively - DOWN to the nearest standard level - so they are MORE likely to be printed. The reverse - sypl to slog - mapping:
sypl level slog level ------------- ------------------------ level.Trace syplslog.LevelTrace (-8) level.Debug slog.LevelDebug (-4) level.Info slog.LevelInfo (0) level.Warn slog.LevelWarn (4) level.Error slog.LevelError (8) level.Fatal syplslog.LevelFatal (12) level.None slog.LevelInfo (0)
`ToSyplLevel` NEVER yields `level.Fatal` - sypl exits the process on `Fatal`, which a logging bridge must never do.
NOTE: In sypl's ordering, Warn is MORE verbose than Info: an output @ `maxLevel` Info does not print Warn. The bridge is faithful to that.
Groups, and attrs ¶
slog groups are flattened into sypl field keys as "group.key" - e.g.: attr "err" under groups "req", and "db" becomes the field "req.db.err". `LogValuer`s are resolved - a panicking `LogValue` is recovered, yielding an error value describing the panic. The record PC (source) is not forwarded.
Index ¶
Examples ¶
Constants ¶
const ( // LevelTrace is the slog level sypl's `Trace` maps to, and from. It sits // below `slog.LevelDebug`, mirroring the standard four-apart spacing. LevelTrace slog.Level = slog.LevelDebug - 4 // LevelFatal is the slog level sypl's `Fatal` maps to. It sits above // `slog.LevelError`, mirroring the standard four-apart spacing. // // NOTE: The bridge only maps TO it - `ToSyplLevel` never yields // `level.Fatal`, as sypl exits the process on `Fatal`. LevelFatal slog.Level = slog.LevelError + 4 )
Bridge-specific slog levels, extending the standard ones so every sypl level - but `None` - has a distinct slog counterpart.
Variables ¶
This section is empty.
Functions ¶
func NewHandler ¶
func NewHandler(l *sypl.Sypl, opts ...HandlerOption) slog.Handler
NewHandler is the `Handler` factory: a `slog.Handler` forwarding records to the given sypl logger - see `Handler.Handle` for the conversion rules, and the package documentation for the level mapping.
Example ¶
ExampleNewHandler demonstrates the slog -> sypl direction: code using the standard slog API logs through a sypl logger.
package main
import (
"log/slog"
"github.com/thalesfsp/sypl"
"github.com/thalesfsp/sypl/level"
"github.com/thalesfsp/sypl/output"
"github.com/thalesfsp/sypl/syplslog"
)
func main() {
// A sypl logger printing to stdout @ Trace, and above.
l := sypl.New("app", output.Console(level.Trace))
// A standard *slog.Logger backed by the sypl logger.
logger := slog.New(syplslog.NewHandler(l))
logger.Info("Hello from slog!")
}
Output: Hello from slog!
func Output ¶
func Output( name string, sl *slog.Logger, maxLevel level.Level, processors ...processor.IProcessor, ) output.IOutput
Output is a built-in `output` factory: a sypl output forwarding processed messages to the given `*slog.Logger` - fields as attrs, and the sypl level mapped back to a slog level, per `ToSlogLevel`. It's built with `output.New`, so dispatch, and processor semantics are standard: max-level gating, `Force`, `Mute`, and `processors` all behave as in any other output.
Notes:
- The forwarding is implemented as the output's FORMATTER: do NOT replace it via `SetFormatter` - doing so breaks the bridge.
- Messages flagged `Skip`, or `SkipAndForce` - e.g.: `PrintPretty`, `PrintNewLine` - bypass processing, and formatting by design: they are forwarded raw @ the `slog.LevelInfo` level, without fields.
- A `Handle` error on the slog side is contained by the standard output error handling - logged to stderr - and the message is NOT re-forwarded.
Example ¶
ExampleOutput demonstrates the sypl -> slog direction: a sypl logger forwarding processed messages - fields as attrs - to a slog handler.
package main
import (
"log/slog"
"os"
"github.com/thalesfsp/sypl"
"github.com/thalesfsp/sypl/fields"
"github.com/thalesfsp/sypl/level"
"github.com/thalesfsp/sypl/syplslog"
)
func main() {
// A standard slog logger - time dropped for a deterministic example.
sl := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
},
}))
// A sypl logger whose output forwards to the slog logger.
l := sypl.New("app", syplslog.Output("slog", sl, level.Trace))
l.PrintWithOptions(
level.Info,
"Hello from sypl!",
sypl.WithFields(fields.Fields{"user": "thales"}),
)
}
Output: level=INFO msg="Hello from sypl!" user=thales
func ToSlogLevel ¶
ToSlogLevel maps a sypl level to a slog level. See the package documentation for the mapping table. `None` - which sypl never prints, unless forced - and unknown levels map to `slog.LevelInfo`.
func ToSyplLevel ¶
ToSyplLevel maps a slog level to a sypl level. See the package documentation for the mapping table. In-between levels map conservatively - down to the nearest standard level - so they are MORE likely to be printed. It never yields `level.Fatal` - sypl exits the process on `Fatal`.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is a `slog.Handler` forwarding records to a sypl logger. See `NewHandler`.
func (*Handler) Enabled ¶
Enabled reports whether the handler handles records at the given level: the level must be at, or above the optional floor - see `HandlerWithLevel` - and at least one ENABLED sypl output must accept the mapped sypl level.
NOTE: The `SYPL_LEVEL` env var - which overrides output max levels at write time - isn't consulted here.
func (*Handler) Handle ¶
Handle converts the record into a sypl message, and prints it through the sypl logger:
- The record message becomes the content. Each record is a single log line: a trailing linebreak is ensured - sypl strips it for processing, and restores it on write.
- The record level is mapped per `ToSyplLevel`.
- The record time is honored - a zero `Record.Time` is forwarded as the zero timestamp, meaning "no time".
- Record attrs, and `WithAttrs`-accumulated attrs become fields: `LogValuer`s are resolved - a panicking `LogValue` is recovered by `slog.Value.Resolve`, yielding an error value describing the panic - and groups are flattened as "group.key".
- The record PC (source) is not forwarded.
func (*Handler) WithAttrs ¶
WithAttrs returns a NEW handler whose attributes consist of both the receiver's attributes, and the arguments - resolved, and flattened under the currently open groups. The receiver is not modified.
type HandlerOption ¶
type HandlerOption func(*Handler)
HandlerOption allows to specify optional `Handler` configuration.
func HandlerWithLevel ¶
func HandlerWithLevel(leveler slog.Leveler) HandlerOption
HandlerWithLevel sets the minimum (floor) slog level: records below it are discarded before reaching sypl. Without it, `Enabled` is governed by the sypl outputs alone.