Repository navigation
Expand file tree
/
Copy pathvalue.go
More file actions
192 lines (158 loc) · 5.17 KB
/
Copy pathvalue.go
File metadata and controls
192 lines (158 loc) · 5.17 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package logging
import (
"time"
"go.uber.org/zap"
)
// Value is a key with a value to log. Keys are declared once as constants of a module and give the values
// with the Value method of the key: KeyUser.Value(42). Use WithValuesFrom to add values to a logger.
type Value struct {
key Key
value any
}
// Value makes a Value having the key and the value. The value is not inspected until the record is written.
func (k Key) Value(value any) Value {
return Value{key: k, value: value}
}
// Key returns the key of the value.
func (v Value) Key() Key {
return v.key
}
// Any returns the value. It is "***" for values of a RedactedKey.
func (v Value) Any() any {
return v.value
}
// LoggingValues implements LoggingValuer: a Value provides itself.
func (v Value) LoggingValues() []Value {
return []Value{v}
}
// Values is a set of Value, f.e. static values of a module. It implements LoggingValuer.
type Values []Value
// LoggingValues implements LoggingValuer.
func (v Values) LoggingValues() []Value {
return v
}
// LoggingValuer is implemented by types providing their values for logging, f.e. a request or an order:
//
// func (o Order) LoggingValues() []logging.Value {
// return []logging.Value{KeyOrderID.Value(o.ID), KeyAmount.Value(o.Sum)}
// }
//
// log = logging.WithValuesFrom(log, order)
//
// The method is called on every WithValuesFrom call, it should be cheap. A panic of the method is not recovered.
type LoggingValuer interface {
LoggingValues() []Value
}
// TypedKey is a key accepting values of type T only. It is a string so keys can be constants:
//
// const KeyUser logging.TypedKey[int] = "user"
//
// KeyUser.Value(42) // KeyUser.Value("x") does not compile
type TypedKey[T any] string
// Value makes a Value having the key and the value.
func (k TypedKey[T]) Value(value T) Value {
return Value{key: Key(k), value: value}
}
// Key returns the untyped key, f.e. for context extractors.
func (k TypedKey[T]) Key() Key {
return Key(k)
}
// String returns the key name.
func (k TypedKey[T]) String() string {
return string(k)
}
// Aliases of TypedKey for common types.
type (
// StringKey accepts string values.
StringKey = TypedKey[string]
// IntKey accepts int values.
IntKey = TypedKey[int]
// Int64Key accepts int64 values.
Int64Key = TypedKey[int64]
// Float64Key accepts float64 values.
Float64Key = TypedKey[float64]
// BoolKey accepts bool values.
BoolKey = TypedKey[bool]
// DurationKey accepts time.Duration values.
DurationKey = TypedKey[time.Duration]
// TimeKey accepts time.Time values.
TimeKey = TypedKey[time.Time]
// ErrorKey accepts error values.
ErrorKey = TypedKey[error]
)
// RedactedKey is a key whose value is always replaced with "***", whatever Config.Redact is.
// The given value is never kept, inspected or encoded. It is a string so keys can be constants:
//
// const KeyPassword logging.RedactedKey[string] = "password"
//
// It has no Key and SetToCtx methods on purpose, so the secret does not go around the masking via context.
type RedactedKey[T any] string
// Value makes a Value having the key and the masked value. The argument is ignored, it only keeps the type check.
func (k RedactedKey[T]) Value(_ T) Value {
return Value{key: Key(k), value: redactedValue}
}
// String returns the key name.
func (k RedactedKey[T]) String() string {
return string(k)
}
// valuesLogger is implemented by loggers adding several values by one call.
type valuesLogger interface {
withValues(values []Value) Logger
}
// WithValuesFrom returns a logger having values of all the sources added, in order of the sources and their values.
// A source is a Value, Values or any LoggingValuer. Nil sources and values without a key are skipped.
// Values with the same key are not merged. Returns the logger itself if there is nothing to add.
func WithValuesFrom(logger Logger, sources ...LoggingValuer) Logger {
values := make([]Value, 0, len(sources))
for _, source := range sources {
switch typed := source.(type) {
case nil:
case Value:
values = append(values, typed)
default:
values = append(values, source.LoggingValues()...)
}
}
filtered := values[:0]
for _, value := range values {
if value.key != "" {
filtered = append(filtered, value)
}
}
if len(filtered) == 0 {
return logger
}
if vl, ok := logger.(valuesLogger); ok {
return vl.withValues(filtered)
}
for _, value := range filtered {
logger = logger.WithKey(value.key.String(), value.value)
}
return logger
}
var (
_ valuesLogger = (*zapLogger)(nil)
_ valuesLogger = (*Default)(nil)
_ valuesLogger = (*TestLogger)(nil)
)
// withValues adds all the values by one call, config Redact is applied.
func (logger *zapLogger) withValues(values []Value) Logger {
fields := make([]zap.Field, len(values))
for i, value := range values {
fields[i] = logger.st.redact.field(value.key.String(), value.value)
}
return logger.with(fields...)
}
func (l *Default) withValues(values []Value) Logger {
plain := l.get().plain
if vl, ok := plain.(valuesLogger); ok {
return vl.withValues(values)
}
for _, value := range values {
plain = plain.WithKey(value.key.String(), value.value)
}
return plain
}
func (tl *TestLogger) withValues(values []Value) Logger {
return tl.zl.withValues(values)
}