synced with master

This commit is contained in:
Gani Georgiev
2025-03-24 18:26:25 +02:00
33 changed files with 148 additions and 57 deletions
+67 -19
View File
@@ -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()
}
+34 -1
View File
@@ -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 {