synced with master
This commit is contained in:
@@ -2,6 +2,7 @@ package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"sync"
|
||||
@@ -252,26 +253,73 @@ func (h *BatchHandler) resolveAttr(data map[string]any, attr slog.Attr) error {
|
||||
data[attr.Key] = groupData
|
||||
}
|
||||
default:
|
||||
switch v := attr.Value.Any().(type) {
|
||||
case validation.Errors:
|
||||
data[attr.Key] = map[string]any{
|
||||
"data": v,
|
||||
"raw": v.Error(),
|
||||
}
|
||||
case error:
|
||||
var ve validation.Errors
|
||||
if errors.As(v, &ve) {
|
||||
data[attr.Key] = map[string]any{
|
||||
"data": ve,
|
||||
"raw": v.Error(),
|
||||
}
|
||||
} else {
|
||||
data[attr.Key] = v.Error()
|
||||
}
|
||||
default:
|
||||
data[attr.Key] = v
|
||||
}
|
||||
data[attr.Key] = normalizeLogAttrValue(attr.Value.Any())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeLogAttrValue(rawAttrValue any) any {
|
||||
switch attrV := rawAttrValue.(type) {
|
||||
case validation.Errors:
|
||||
out := make(map[string]any, len(attrV))
|
||||
for k, v := range attrV {
|
||||
out[k] = serializeLogError(v)
|
||||
}
|
||||
return out
|
||||
case map[string]validation.Error:
|
||||
out := make(map[string]any, len(attrV))
|
||||
for k, v := range attrV {
|
||||
out[k] = serializeLogError(v)
|
||||
}
|
||||
return out
|
||||
case map[string]error:
|
||||
out := make(map[string]any, len(attrV))
|
||||
for k, v := range attrV {
|
||||
out[k] = serializeLogError(v)
|
||||
}
|
||||
return out
|
||||
case map[string]any:
|
||||
out := make(map[string]any, len(attrV))
|
||||
for k, v := range attrV {
|
||||
switch vv := v.(type) {
|
||||
case error:
|
||||
out[k] = serializeLogError(vv)
|
||||
default:
|
||||
out[k] = normalizeLogAttrValue(vv)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case error:
|
||||
// check for wrapped validation.Errors
|
||||
var ve validation.Errors
|
||||
if errors.As(attrV, &ve) {
|
||||
out := make(map[string]any, len(ve))
|
||||
for k, v := range ve {
|
||||
out[k] = serializeLogError(v)
|
||||
}
|
||||
return map[string]any{
|
||||
"data": out,
|
||||
"raw": serializeLogError(attrV),
|
||||
}
|
||||
}
|
||||
return serializeLogError(attrV)
|
||||
default:
|
||||
return attrV
|
||||
}
|
||||
}
|
||||
|
||||
func serializeLogError(err error) any {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prioritize a json structured format (e.g. validation.Errors)
|
||||
jsonErr, ok := err.(json.Marshaler)
|
||||
if ok {
|
||||
return jsonErr
|
||||
}
|
||||
|
||||
// fallback to its original string representation
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
)
|
||||
|
||||
func TestNewBatchHandlerPanic(t *testing.T) {
|
||||
@@ -280,14 +282,45 @@ func TestBatchHandlerAttrsFormat(t *testing.T) {
|
||||
h1.Handle(ctx, record)
|
||||
h2.Handle(ctx, record)
|
||||
|
||||
// errors serialization checks
|
||||
errorsRecord := slog.NewRecord(time.Now(), slog.LevelError, "details", 0)
|
||||
errorsRecord.Add("validation.Errors", validation.Errors{
|
||||
"a": validation.NewError("validation_code", "validation_message"),
|
||||
"b": errors.New("plain"),
|
||||
})
|
||||
errorsRecord.Add("wrapped_validation.Errors", fmt.Errorf("wrapped: %w", validation.Errors{
|
||||
"a": validation.NewError("validation_code", "validation_message"),
|
||||
"b": errors.New("plain"),
|
||||
}))
|
||||
errorsRecord.Add("map[string]any", map[string]any{
|
||||
"a": validation.NewError("validation_code", "validation_message"),
|
||||
"b": errors.New("plain"),
|
||||
"c": "test_any",
|
||||
"d": map[string]any{
|
||||
"nestedA": validation.NewError("nested_code", "nested_message"),
|
||||
"nestedB": errors.New("nested_plain"),
|
||||
},
|
||||
})
|
||||
errorsRecord.Add("map[string]error", map[string]error{
|
||||
"a": validation.NewError("validation_code", "validation_message"),
|
||||
"b": errors.New("plain"),
|
||||
})
|
||||
errorsRecord.Add("map[string]validation.Error", map[string]validation.Error{
|
||||
"a": validation.NewError("validation_code", "validation_message"),
|
||||
"b": nil,
|
||||
})
|
||||
errorsRecord.Add("plain_error", errors.New("plain"))
|
||||
h0.Handle(ctx, errorsRecord)
|
||||
|
||||
expected := []string{
|
||||
`{"name":"test"}`,
|
||||
`{"a":1,"b":"123","name":"test"}`,
|
||||
`{"a":1,"b":"123","sub":{"c":3,"d":{"d.1":1},"e":"example error","name":"test"}}`,
|
||||
`{"map[string]any":{"a":"validation_message","b":"plain","c":"test_any","d":{"nestedA":"nested_message","nestedB":"nested_plain"}},"map[string]error":{"a":"validation_message","b":"plain"},"map[string]validation.Error":{"a":"validation_message","b":null},"plain_error":"plain","validation.Errors":{"a":"validation_message","b":"plain"},"wrapped_validation.Errors":{"data":{"a":"validation_message","b":"plain"},"raw":"wrapped: a: validation_message; b: plain."}}`,
|
||||
}
|
||||
|
||||
if len(beforeLogs) != len(expected) {
|
||||
t.Fatalf("Expected %d logs, got %d", len(beforeLogs), len(expected))
|
||||
t.Fatalf("Expected %d logs, got %d", len(expected), len(beforeLogs))
|
||||
}
|
||||
|
||||
for i, data := range expected {
|
||||
|
||||
Reference in New Issue
Block a user