Skip to content

Commit

Permalink
slogutil: jsonhybrid
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizzick committed Oct 22, 2024
1 parent abce6ff commit 9d54b20
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 78 deletions.
60 changes: 26 additions & 34 deletions logutil/slogutil/jsonhybrid.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,50 @@ const (

// NewJSONHybridHandler creates a new properly initialized *JSONHybridHandler.
// opts are used for the underlying JSON handler.
func NewJSONHybridHandler(
w io.Writer,
opts *slog.HandlerOptions,
removeTime bool,
) (h *JSONHybridHandler) {
var replaceAttr func(groups []string, a slog.Attr) slog.Attr
if removeTime {
replaceAttr = func(groups []string, a slog.Attr) (res slog.Attr) {
return replaceAttrKey(groups, RemoveTime(groups, a))
}
} else {
replaceAttr = replaceAttrKey
}

handlerOpts := &slog.HandlerOptions{
ReplaceAttr: replaceAttr,
func NewJSONHybridHandler(w io.Writer, opts *slog.HandlerOptions) (h *JSONHybridHandler) {
lvl := slog.LevelInfo
if opts != nil && opts.Level != nil {
lvl = opts.Level.Level()
}

return &JSONHybridHandler{
json: slog.NewJSONHandler(w, opts),
json: slog.NewJSONHandler(w, &slog.HandlerOptions{
Level: lvl,
ReplaceAttr: renameAttrs,
}),
attrPool: syncutil.NewSlicePool[slog.Attr](initAttrsLenEst),
bufTextPool: syncutil.NewPool(func() (bufTextHdlr *bufferedTextHandler) {
return newBufferedTextHandler(initLineLenEst, handlerOpts)
return newBufferedTextHandler(initLineLenEst, opts)
}),
textAttrs: nil,
}
}

// replaceAttrKey is a [slog.HandlerOptions.ReplaceAttr] function that removes
// "msg" and "source" attributes. It also adds [LevelTrace] custom name for
// level attribute.
func replaceAttrKey(groups []string, a slog.Attr) (res slog.Attr) {
// normalAttrValue is a NORMAL value under the [slog.LevelKey] key.
var normalAttrValue = slog.StringValue("NORMAL")

// renameAttrs is a [slog.HandlerOptions.ReplaceAttr] function that renames the
// [slog.LevelKey] key to [keySeverity], and the [slog.MessageKey] key to
// [keyMessage]. It also sets the level value to "NORMAL" for levels less than
// [LevelError].
func renameAttrs(groups []string, a slog.Attr) (res slog.Attr) {
if len(groups) > 0 {
return a
}

switch a.Key {
case slog.MessageKey, slog.SourceKey:
return slog.Attr{}
case slog.LevelKey:
case KeyLevel:
lvl := a.Value.Any().(slog.Level)
if lvl == LevelTrace {
a.Value = traceAttrValue
if lvl < LevelError {
a.Value = normalAttrValue
}

return a
default:
return a
a.Key = keySeverity
case KeyMessage:
a.Key = keyMessage
}

return a
}

// type check
Expand All @@ -108,9 +103,6 @@ func (h *JSONHybridHandler) Handle(ctx context.Context, r slog.Record) (err erro

bufTextHdlr.reset()

_, _ = bufTextHdlr.buffer.WriteString(r.Message)
_, _ = bufTextHdlr.buffer.WriteString("; attrs: ")

textAttrsPtr := h.attrPool.Get()
defer h.attrPool.Put(textAttrsPtr)

Expand All @@ -121,7 +113,7 @@ func (h *JSONHybridHandler) Handle(ctx context.Context, r slog.Record) (err erro
return true
})

textRec := slog.NewRecord(r.Time, r.Level, "", 0)
textRec := slog.NewRecord(r.Time, r.Level, r.Message, 0)
textRec.AddAttrs(h.textAttrs...)
textRec.AddAttrs(*textAttrsPtr...)

Expand Down
20 changes: 10 additions & 10 deletions logutil/slogutil/jsonhybrid_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ func ExampleJSONHybridHandler() {
AddSource: false,
Level: slog.LevelDebug,
// Use slogutil.RemoveTime to make the example reproducible.
ReplaceAttr: slogutil.RenameAttrs,
}, true)
ReplaceAttr: slogutil.RemoveTime,
})
l := slog.New(h)

l.Debug("debug with no attributes")
Expand All @@ -30,12 +30,12 @@ func ExampleJSONHybridHandler() {
l.Error("error with attributes", "number", 123)

// Output:
// {"severity":"NORMAL","message":"debug with no attributes; attrs: level=DEBUG"}
// {"severity":"NORMAL","message":"debug with attributes; attrs: level=DEBUG number=123"}
// {"severity":"NORMAL","message":"info with no attributes; attrs: level=INFO"}
// {"severity":"NORMAL","message":"info with attributes; attrs: level=INFO number=123"}
// {"severity":"NORMAL","message":"new info with no attributes; attrs: level=INFO attr=abc"}
// {"severity":"NORMAL","message":"new info with attributes; attrs: level=INFO attr=abc number=123"}
// {"severity":"ERROR","message":"error with no attributes; attrs: level=ERROR attr=abc"}
// {"severity":"ERROR","message":"error with attributes; attrs: level=ERROR attr=abc number=123"}
// {"severity":"NORMAL","message":"level=DEBUG msg=\"debug with no attributes\""}
// {"severity":"NORMAL","message":"level=DEBUG msg=\"debug with attributes\" number=123"}
// {"severity":"NORMAL","message":"level=INFO msg=\"info with no attributes\""}
// {"severity":"NORMAL","message":"level=INFO msg=\"info with attributes\" number=123"}
// {"severity":"NORMAL","message":"level=INFO msg=\"new info with no attributes\" attr=abc"}
// {"severity":"NORMAL","message":"level=INFO msg=\"new info with attributes\" attr=abc number=123"}
// {"severity":"ERROR","message":"level=ERROR msg=\"error with no attributes\" attr=abc"}
// {"severity":"ERROR","message":"level=ERROR msg=\"error with attributes\" attr=abc number=123"}
}
10 changes: 5 additions & 5 deletions logutil/slogutil/jsonhybrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestJSONHybridHandler_Handle(t *testing.T) {
var (
hybridHdlr = slogutil.NewJSONHybridHandler(hybridOutput, &slog.HandlerOptions{
ReplaceAttr: slogutil.RemoveTime,
}, true)
})
textHdlr = slog.NewTextHandler(textOutput, &slog.HandlerOptions{
ReplaceAttr: slogutil.RemoveTime,
})
Expand Down Expand Up @@ -64,18 +64,18 @@ func TestJSONHybridHandler_Handle(t *testing.T) {

for i := 0; i < numGoroutine; i++ {
textString := textOutputStrings[i]
expectedString := strings.Replace(textString, `msg="test message" `, "", 1)
expectedString := strings.Replace(textString, `level=INFO msg="test message" `, "", 1)

jsonString := hybridOutputStrings[i]
gotString := strings.Replace(jsonString, `{"level":"INFO","msg":"test message; attrs: `, "", 1)
gotString := strings.Replace(jsonString, `{"severity":"NORMAL","message":"level=INFO msg=\"test message\" `, "", 1)
gotString = strings.Replace(gotString, `"}`, "", 1)

assert.Equal(t, expectedString, gotString)
}
}

func BenchmarkJSONHybridHandler_Handle(b *testing.B) {
h := slogutil.NewJSONHybridHandler(io.Discard, nil, false)
h := slogutil.NewJSONHybridHandler(io.Discard, nil)

ctx := context.Background()
r := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0)
Expand All @@ -96,5 +96,5 @@ func BenchmarkJSONHybridHandler_Handle(b *testing.B) {
// goarch: arm64
// pkg: github.com/AdguardTeam/golibs/logutil/slogutil
// cpu: Apple M1 Pro
// BenchmarkJSONHybridHandler_Handle-8 1596511 742.7 ns/op 104 B/op 2 allocs/op
// BenchmarkJSONHybridHandler_Handle-8 1364242 959.7 ns/op 96 B/op 1 allocs/op
}
31 changes: 2 additions & 29 deletions logutil/slogutil/slogutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ func New(c *Config) (l *slog.Logger) {
case FormatJSONHybrid:
h = NewJSONHybridHandler(output, &slog.HandlerOptions{
Level: lvl,
ReplaceAttr: RenameAttrs,
}, !c.AddTimestamp)
ReplaceAttr: replaceAttr,
})
case FormatText:
h = slog.NewTextHandler(output, &slog.HandlerOptions{
Level: lvl,
Expand Down Expand Up @@ -145,33 +145,6 @@ func ReplaceLevel(groups []string, a slog.Attr) (res slog.Attr) {
return a
}

// normalAttrValue is a NORMAL value under the [slog.LevelKey] key.
var normalAttrValue = slog.StringValue("NORMAL")

// RenameAttrs is a [slog.HandlerOptions.ReplaceAttr] function that renames the
// [slog.LevelKey] key to [keySeverity], and the [slog.MessageKey] key to
// [keyMessage]. It also sets the level value to "NORMAL" for levels less than
// [LevelError].
func RenameAttrs(groups []string, a slog.Attr) (res slog.Attr) {
if len(groups) > 0 {
return a
}

switch a.Key {
case KeyLevel:
lvl := a.Value.Any().(slog.Level)
if lvl < LevelError {
a.Value = normalAttrValue
}

a.Key = keySeverity
case KeyMessage:
a.Key = keyMessage
}

return a
}

// RemoveTime is a function for [slog.HandlerOptions.ReplaceAttr] that removes
// the "time" attribute.
func RemoveTime(groups []string, a slog.Attr) (res slog.Attr) {
Expand Down

0 comments on commit 9d54b20

Please sign in to comment.