Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,74 @@ BenchmarkLoggers/dummy-4 1239873 893.2 ns/op
BenchmarkLoggers/console-4 483354 2338 ns/op 128 B/op 1 allocs/op
BenchmarkLoggers/std-text-4 368828 3141 ns/op 132 B/op 3 allocs/op
BenchmarkLoggers/std-json-4 393322 2909 ns/op 248 B/op 4 allocs/op
```
```

## Indentation
`console.Handler` also implements the custom interface:
```go
type Indenter interface {
// SetIndentation configures the prefix and indent strings.
// The prefix, if any, is added first,
// then the indent string is added as many times as the current depth.
SetIndentation(prefix, indent string)

// Increment the indentation depth.
Increment()

// Decrement the indentation depth.
// If the current depth is already zero there is no change.
Decrement()
}
```
Indentation of functions can occasionally be useful for delineating
calls by indenting callees more than callers.

This isn't something that `log.slog` provides.
It is necessary to provide the handler pointer as a global variable or function
in order to use the `Indenter` interface.

Because the current `depth` is stored within the `console.Handler` this functionality is not thread-safe.
In order to use it in a multi-thread environment there must be a handler (and logger) for each thread,
and these items must be passed down through all intervening function calls (in lieu of thread variables).

### Example

```go
package main

import (
"log/slog"
"os"

"github.com/phsym/console-slog"
)

var hdlr *console.Handler

func main() {
hdlr = console.NewHandler(os.Stderr, &console.HandlerOptions{Level: slog.LevelDebug})
hdlr.SetIndentation("", " ")
slog.SetDefault(slog.New(hdlr))
slog.Info("factorial", "result", factorial(7))
}

func factorial(number uint) uint {
hdlr.Increment()
defer hdlr.Decrement()

slog.Debug("factorial", "number", number)
var result uint
if number < 2 {
result = 1
} else {
result = number * factorial(number-1)
}
slog.Debug("factorial", "result", result)
return result
}
```

![factorial-indent](./doc/img/factorial-indent.png)

The specific case of runaway recursion shows up clearly as the messages
march repeatedly across the screen.
Binary file added doc/img/factorial-indent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
32 changes: 32 additions & 0 deletions examples/indent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"log/slog"
"os"

"github.com/phsym/console-slog"
)

var hdlr *console.Handler

func main() {
hdlr = console.NewHandler(os.Stderr, &console.HandlerOptions{Level: slog.LevelDebug})
hdlr.SetIndentation("", " ")
slog.SetDefault(slog.New(hdlr))
slog.Info("factorial", "result", factorial(7))
}

func factorial(number uint) uint {
hdlr.Increment()
defer hdlr.Decrement()

slog.Debug("factorial", "number", number)
var result uint
if number < 2 {
result = 1
} else {
result = number * factorial(number-1)
}
slog.Debug("factorial", "result", result)
return result
}
68 changes: 67 additions & 1 deletion handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,60 @@ type HandlerOptions struct {
Theme Theme
}

type Indenter interface {
// SetIndentation configures the prefix and indent strings.
// The prefix, if any, is added first,
// then the indent string is added as many times as the current depth.
SetIndentation(prefix, indent string)

// Increment the indentation depth.
Increment()

// Decrement the indentation depth.
// If the current depth is already zero there is no change.
Decrement()
}

// indentation defines support data for enhanced message and arg indentation by depth.
type indentation struct {
// Prefix for indentation string.
prefix string

// Indent string for each depth level.
indent string

// Depth is the current indentation depth
depth uint
}

func (hd *indentation) indentMsg(msg string) string {
if hd.prefix == "" && (hd.indent == "" || hd.depth < 1) {
// No indentation.
return msg
}

// Build indentation string.
builder := strings.Builder{}
if hd.prefix != "" {
builder.WriteString(hd.prefix)
}
if hd.indent != "" && hd.depth > 0 {
var i uint
for i = 0; i < hd.depth; i++ {
builder.WriteString(hd.indent)
}
}
builder.WriteString(msg)
return builder.String()
}

type Handler struct {
opts HandlerOptions
out io.Writer
group string
context buffer
enc *encoder
indentation
}

var _ slog.Handler = (*Handler)(nil)
Expand Down Expand Up @@ -89,7 +137,7 @@ func (h *Handler) Handle(_ context.Context, rec slog.Record) error {
if h.opts.AddSource && rec.PC > 0 {
h.enc.writeSource(buf, rec.PC, cwd)
}
h.enc.writeMessage(buf, rec.Level, rec.Message)
h.enc.writeMessage(buf, rec.Level, h.indentMsg(rec.Message))
buf.copy(&h.context)
rec.Attrs(func(a slog.Attr) bool {
h.enc.writeAttr(buf, a, h.group)
Expand Down Expand Up @@ -135,3 +183,21 @@ func (h *Handler) WithGroup(name string) slog.Handler {
enc: h.enc,
}
}

var _ Indenter = (*Handler)(nil)

func (h *Handler) SetIndentation(prefix, indent string) {
h.prefix = prefix
h.indent = indent
h.depth = 0
}

func (h *Handler) Increment() {
h.depth++
}

func (h *Handler) Decrement() {
if h.depth > 0 {
h.depth--
}
}