aboutsummaryrefslogtreecommitdiffstats
path: root/logging/encoding.go
blob: c4de0c819e211ee4c15171fc34aa310fc5df9e86 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package logging

import (
	"encoding/json"
	"fmt"
	"iter"
	"log/slog"
	"runtime"
	"slices"
	"strings"
)

func RecordAll(r slog.Record, replaceAttr func(groups []string, a slog.Attr) slog.Attr) iter.Seq2[[]string, slog.Attr] {
	return func(yield func([]string, slog.Attr) bool) {
		var walk func([]string, slog.Attr) bool
		walk = func(groups []string, a slog.Attr) bool {
			if replaceAttr != nil {
				a = replaceAttr(groups, a)
			}

			if a.Key == "" {
				return true
			}

			a.Value = a.Value.Resolve()

			if a.Value.Kind() == slog.KindGroup {
				newGroups := append(slices.Clone(groups), a.Key)
				for _, child := range a.Value.Group() {
					if !walk(newGroups, child) {
						return false
					}
				}
				return true
			}

			return yield(groups, a)
		}

		if !r.Time.IsZero() {
			if !walk([]string{}, slog.Time(slog.TimeKey, r.Time)) {
				return
			}
		}

		if !walk([]string{}, slog.Any(slog.LevelKey, r.Level)) {
			return
		}
		if !walk([]string{}, slog.String(slog.MessageKey, r.Message)) {
			return
		}

		if r.PC != 0 {
			if !walk([]string{}, slog.Uint64(slog.SourceKey, uint64(r.PC))) {
				return
			}
		}

		r.Attrs(func(attr slog.Attr) bool {
			return walk([]string{}, attr)
		})
	}
}

type Encoder func(iter.Seq2[[]string, slog.Attr]) (string, error)

func LogFmtEncoder(attrs iter.Seq2[[]string, slog.Attr]) (string, error) {
	str := []string{}
	for groups, attr := range attrs {
		if len(groups) == 0 && attr.Key == slog.SourceKey {
			pc := uintptr(attr.Value.Uint64())
			fs := runtime.CallersFrames([]uintptr{pc})
			f, _ := fs.Next()
			attr.Value = slog.StringValue(fmt.Sprintf("%s:%d", f.File, f.Line))
		}

		str = append(str, fmt.Sprintf("%s=%q", strings.Join(append(groups, attr.Key), "."), attr.Value))
	}

	return strings.Join(str, " "), nil
}

func ToMap(seq iter.Seq2[[]string, slog.Attr]) map[string]any {
	out := make(map[string]any)
	for groups, attr := range seq {
		current := out
		for _, group := range groups {
			if next, ok := current[group].(map[string]any); ok {
				current = next
			} else {
				newMap := make(map[string]any)
				current[group] = newMap
				current = newMap
			}
		}
		current[attr.Key] = attr.Value.Any()
	}
	return out
}

func JSONEncoder(attrs iter.Seq2[[]string, slog.Attr]) (string, error) {
	m := ToMap(attrs)
	if v, ok := m[slog.SourceKey]; ok {
		pc := v.(uint64)
		fs := runtime.CallersFrames([]uintptr{uintptr(pc)})
		f, _ := fs.Next()
		m[slog.SourceKey] = fmt.Sprintf("%s:%d", f.File, f.Line)
	}

	b, err := json.Marshal(m)
	if err != nil {
		return "", err
	}

	return string(b), nil
}

func MessageEncoder(attrs iter.Seq2[[]string, slog.Attr]) (string, error) {
	for groups, attr := range attrs {
		if len(groups) == 0 && attr.Key == slog.MessageKey {
			return attr.Value.String(), nil
		}
	}
	return "", nil
}