Skip to content

Commit

Permalink
feat: adding support for AddSource+ReplaceAttr - moving some code to …
Browse files Browse the repository at this point in the history
…samber/slog-common
  • Loading branch information
samber committed Oct 15, 2023
1 parent 4753179 commit 189ffa9
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 148 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Remove xxx_test.go files
run: rm -rf *_test.go ./examples ./images

- name: Set library version in version.go
run: sed -i 's/VERSION/${{ inputs.semver }}/g' version.go

# cleanup test dependencies
- name: Cleanup dependencies
run: go mod tidy
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,23 @@ type Option struct {

// optional: customize json payload builder
Converter Converter

// optional: see slog.HandlerOptions
AddSource bool
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
}
```

Attributes will be injected in log payload.

Other global parameters:

```go
sloggraylog.SourceKey = "source"
sloggraylog.ContextKey = "extra"
sloggraylog.ErrorKeys = []string{"error", "err"}
```

### Example

```go
Expand Down
115 changes: 29 additions & 86 deletions converter.go
Original file line number Diff line number Diff line change
@@ -1,106 +1,49 @@
package sloggraylog

import (
"encoding"
"fmt"
"reflect"

"log/slog"
)

type Converter func(loggerAttr []slog.Attr, record *slog.Record) map[string]any
slogcommon "github.com/samber/slog-common"
)

func DefaultConverter(loggerAttr []slog.Attr, record *slog.Record) map[string]any {
log := map[string]any{
"timestamp": record.Time.UTC(),
"level": record.Level.String(),
"message": record.Message,
}
var SourceKey = "source"
var ContextKey = "extra"
var ErrorKeys = []string{"error", "err"}

extra := attrsToValue(loggerAttr)
type Converter func(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) map[string]any

record.Attrs(func(attr slog.Attr) bool {
for k, v := range attrsToValue([]slog.Attr{attr}) {
extra[k] = v
}
return true
})
func DefaultConverter(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) map[string]any {
// aggregate all attributes
attrs := slogcommon.AppendRecordAttrsToAttrs(loggerAttr, groups, record)

if err, ok := extra["error"]; ok {
log["error"] = err
delete(extra, "error")
// developer formatters
if addSource {
attrs = append(attrs, slogcommon.Source(SourceKey, record))
}
attrs = slogcommon.ReplaceAttrs(replaceAttr, []string{}, attrs...)

log["extra"] = extra

return log
}

func attrsToValue(attrs []slog.Attr) map[string]any {
log := map[string]any{}

for i := range attrs {
k, v := attrToValue(attrs[i])
log[k] = v
// handler formatter
log := map[string]any{
"logger.name": name,
"logger.version": version,
"timestamp": record.Time.UTC(),
"level": record.Level.String(),
"message": record.Message,
}

return log
}

func attrToValue(attr slog.Attr) (string, any) {
k := attr.Key
v := attr.Value
kind := v.Kind()
extra := slogcommon.AttrsToMap(attrs...)

switch kind {
case slog.KindAny:
if k == "error" {
if err, ok := v.Any().(error); ok {
return k, buildExceptions(err)
for _, errorKey := range ErrorKeys {
if v, ok := extra[errorKey]; ok {
if err, ok := v.(error); ok {
log[errorKey] = slogcommon.FormatError(err)
delete(extra, errorKey)
break
}
}

return k, v.Any()
case slog.KindLogValuer:
return k, v.Any()
case slog.KindGroup:
return k, attrsToValue(v.Group())
case slog.KindInt64:
return k, v.Int64()
case slog.KindUint64:
return k, v.Uint64()
case slog.KindFloat64:
return k, v.Float64()
case slog.KindString:
return k, v.String()
case slog.KindBool:
return k, v.Bool()
case slog.KindDuration:
return k, v.Duration()
case slog.KindTime:
return k, v.Time().UTC()
default:
return k, anyValueToString(v)
}
}

func anyValueToString(v slog.Value) string {
if tm, ok := v.Any().(encoding.TextMarshaler); ok {
data, err := tm.MarshalText()
if err != nil {
return ""
}

return string(data)
}

return fmt.Sprintf("%+v", v.Any())
}
log[ContextKey] = extra

func buildExceptions(err error) map[string]any {
return map[string]any{
"kind": reflect.TypeOf(err).String(),
"error": err.Error(),
"stack": nil, // @TODO
}
return log
}
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ go 1.21

require (
github.com/Graylog2/go-gelf v0.0.0-20170811154226-7ebf4f536d8f
github.com/samber/lo v1.38.1
github.com/samber/slog-common v0.11.0
go.uber.org/goleak v1.2.1
)

require golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
require (
github.com/samber/lo v1.38.1 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/slog-common v0.11.0 h1:JdESCaXcEwdtoTCYHKQFfHGbWN2vZJq0DDGEE/lwTUQ=
github.com/samber/slog-common v0.11.0/go.mod h1:Qjrfhwk79XiCIhBj8+jTq1Cr0u9rlWbjawh3dWXzaHk=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
Expand Down
9 changes: 7 additions & 2 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"

"github.com/Graylog2/go-gelf/gelf"
slogcommon "github.com/samber/slog-common"
)

type Option struct {
Expand All @@ -18,6 +19,10 @@ type Option struct {

// optional: customize json payload builder
Converter Converter

// optional: see slog.HandlerOptions
AddSource bool
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
}

func (o Option) NewGraylogHandler() slog.Handler {
Expand Down Expand Up @@ -54,7 +59,7 @@ func (h *GraylogHandler) Handle(ctx context.Context, record slog.Record) error {
converter = h.option.Converter
}

message := converter(h.attrs, &record)
message := converter(h.option.AddSource, h.option.ReplaceAttr, h.attrs, h.groups, &record)

bytes, err := json.Marshal(message)
if err != nil {
Expand All @@ -69,7 +74,7 @@ func (h *GraylogHandler) Handle(ctx context.Context, record slog.Record) error {
func (h *GraylogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &GraylogHandler{
option: h.option,
attrs: appendAttrsToGroup(h.groups, h.attrs, attrs),
attrs: slogcommon.AppendAttrsToGroup(h.groups, h.attrs, attrs...),
groups: h.groups,
}
}
Expand Down
58 changes: 0 additions & 58 deletions utils.go

This file was deleted.

4 changes: 4 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package sloggraylog

const name = "samber/slog-graylog"
const version = "VERSION" // replaced by .github/workflows/release.yml

0 comments on commit 189ffa9

Please sign in to comment.