Skip to content

Commit

Permalink
logg/slog: a special locked version for entry printOut
Browse files Browse the repository at this point in the history
  • Loading branch information
hedzr committed Oct 30, 2024
1 parent 70537ea commit 7f1d2a3
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 18 deletions.
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,18 +455,50 @@ logg/slog uses a `dualWriter` to serialize the logging contents.

A `dualWriter` sends contents to stdout or stderr in accord to the requesting logging level. For example, a `Info(...)` calling will be dispatched to stdout and a `Warn`, `Error`, `Panic`, or `Fatal` to stderr.

Not only for those, the dualWriter allows you stack many writers as its `Normal` or `Error` output devices. That means, a console `os.Stdout` and a file writer can be bundled into `Normal` at once. How to do it? Simple:
Not only for those, the dualWriter allows you stack many writers as its `Normal` or `Error` output devices. That means, a console `os.Stdout` and a file writer can be bundled into `Normal` at once. How to do it? It's simple:

```go
logger := New("tty+file").AddWriter(slog.NewFileWriter("/tmp/app-stdout.log"))
```

Using `WithWriter` to replace our stocked version, which is stdout+stderr by default.
Using `WithWriter` to replace the stocked version, which is stdout+stderr by default.

Or `AddErrorWrite(w)` can append to the `Error` device.
`AddWriter(w)`/`RemoveWriter`/`ResetWriter` can append/remove/reset the stdout device.

`AddErrorWriter(w)`/`RemoveErrorWriter`/`ResetErrorWriter` can append/remove/reset to the stderr device.

Of course, `ResetWriters` works so we can always back to default state.

A standard `io.Writer` is needed when using AddWriter/WithWriter:

```go
// Simple file
tf, _ := os.CreateTempFile("", "stdout.log")
logger.AddWriter(tf)
```

Your writer can implement `LevelSettable` to handling the requesting logging level.

```go
package mywriter
import (
"logz" "github.com/hedzr/logg/slog"
)
type myWriter struct {
level logz.Level
}
func (s *myWriter) SetLevel(level logz.Level) { s.level = level } // logz.LevelSettable
func (s *myWriter) Write(data []byte) (n int, err error) {
switch(s.level) {
case logz.DebugLevel:
// ...
}
return
}
```

In this scene, logg/slog will call Write following SetLevel. It's safe in many cases, but you can take more safety for it by using locked mechanism: go build tags `-tags=logglock` will enable a special version with wrapping the two calls in a sync.Mutex. See the source code in entry_lock.go.

#### Leveled Writers

Also we provides sub-feature to allow you specify special writer for a special logging level:
Expand Down
17 changes: 2 additions & 15 deletions slog/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type Entry struct {
handlerOpt logslog.Handler
extraFrames int
contextKeys []any

muWrite writeLock
}

// New make a new child Logger, which is identified by a unique name.
Expand Down Expand Up @@ -1261,21 +1263,6 @@ func (s *Entry) printRestLinesOfMsg(ctx context.Context, pc *PrintCtx) {
}
}

func (s *Entry) printOut(lvl Level, ret []byte) {
if w := s.findWriter(lvl); w != nil {
// if a target user-defined writer can be SetLevel, set it before writing.
if x, ok := w.(LevelSettable); ok {
x.SetLevel(lvl)
}

_, err := w.Write(ret)

if err != nil && lvl != WarnLevel { // don't warn on warning to avoid infinite calls
s.Warn("slog print log failed", "error", err)
}
}
}

func (s *Entry) log1(lvl Level, msg string, args ...any) {
ctx := context.Background()
if s.EnabledContext(ctx, lvl) {
Expand Down
26 changes: 26 additions & 0 deletions slog/entry_lock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build logglock
// +build logglock

package slog

type writeLock struct {
mu sync.Mutex
}

func (s *Entry) printOut(lvl Level, ret []byte) {
if w := s.findWriter(lvl); w != nil {
s.muWrite.Lock()
defer s.muWrite.Unlock()

// if a target user-defined writer can be SetLevel, set it before writing.
if x, ok := w.(LevelSettable); ok {
x.SetLevel(lvl)
}

_, err := w.Write(ret)

if err != nil && lvl != WarnLevel { // don't warn on warning to avoid infinite calls
s.Warn("slog print log failed", "error", err)
}
}
}
21 changes: 21 additions & 0 deletions slog/entry_nolock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !logglock
// +build !logglock

package slog

type writeLock struct{}

func (s *Entry) printOut(lvl Level, ret []byte) {
if w := s.findWriter(lvl); w != nil {
// if a target user-defined writer can be SetLevel, set it before writing.
if x, ok := w.(LevelSettable); ok {
x.SetLevel(lvl)
}

_, err := w.Write(ret)

if err != nil && lvl != WarnLevel { // don't warn on warning to avoid infinite calls
s.Warn("slog print log failed", "error", err)
}
}
}

0 comments on commit 7f1d2a3

Please sign in to comment.