@@ -12,6 +12,9 @@ import (
12
12
const nAttrsInline = 5
13
13
14
14
// A Record holds information about a log event.
15
+ // Copies of a Record share state.
16
+ // Do not modify a Record after handing out a copy to it.
17
+ // Use [Record.Clone] to create a copy with no shared state.
15
18
type Record struct {
16
19
// The time at which the output method (Log, Info, etc.) was called.
17
20
time time.Time
@@ -28,26 +31,28 @@ type Record struct {
28
31
29
32
// Allocation optimization: an inline array sized to hold
30
33
// the majority of log calls (based on examination of open-source
31
- // code). The array holds the end of the sequence of Attrs.
32
- tail [nAttrsInline ]Attr
34
+ // code). It holds the start of the list of Attrs.
35
+ front [nAttrsInline ]Attr
33
36
34
- // The number of Attrs in tail .
35
- nTail int
37
+ // The number of Attrs in front .
38
+ nFront int
36
39
37
- // The sequence of Attrs except for the tail, represented as a functional
38
- // list of arrays.
39
- attrs list [[nAttrsInline ]Attr ]
40
+ // The list of Attrs except for those in front.
41
+ // Invariants:
42
+ // - len(back) > 0 iff nFront == len(front)
43
+ // - Unused array elements are zero. Used to detect mistakes.
44
+ back []Attr
40
45
}
41
46
42
- // MakeRecord creates a new Record from the given arguments.
43
- // Use [Record.AddAttr ] to add attributes to the Record.
47
+ // NewRecord creates a Record from the given arguments.
48
+ // Use [Record.AddAttrs ] to add attributes to the Record.
44
49
// If calldepth is greater than zero, [Record.SourceLine] will
45
50
// return the file and line number at that depth,
46
- // where 1 means the caller of MakeRecord .
51
+ // where 1 means the caller of NewRecord .
47
52
//
48
- // MakeRecord is intended for logging APIs that want to support a [Handler] as
53
+ // NewRecord is intended for logging APIs that want to support a [Handler] as
49
54
// a backend.
50
- func MakeRecord (t time.Time , level Level , msg string , calldepth int ) Record {
55
+ func NewRecord (t time.Time , level Level , msg string , calldepth int ) Record {
51
56
var p uintptr
52
57
if calldepth > 0 {
53
58
p = pc (calldepth + 1 )
@@ -85,50 +90,97 @@ func (r *Record) SourceLine() (file string, line int) {
85
90
return f .File , f .Line
86
91
}
87
92
88
- // Attrs returns a copy of the sequence of Attrs in r.
89
- func (r * Record ) Attrs () []Attr {
90
- res := make ([]Attr , 0 , r .attrs .len ()* nAttrsInline + r .nTail )
91
- r .attrs = r .attrs .normalize ()
92
- for _ , f := range r .attrs .front {
93
- res = append (res , f [:]... )
93
+ // Clone returns a copy of the record with no shared state.
94
+ // The original record and the clone can both be modified
95
+ // without interfering with each other.
96
+ func (r * Record ) Clone () Record {
97
+ c := * r
98
+ if len (c .back ) > 0 {
99
+ c .back = make ([]Attr , len (c .back ))
100
+ copy (c .back , r .back )
94
101
}
95
- for _ , a := range r .tail [:r .nTail ] {
96
- res = append (res , a )
97
- }
98
- return res
102
+ return c
99
103
}
100
104
101
- // NumAttrs returns the number of Attrs in r .
105
+ // NumAttrs returns the number of attributes in the Record .
102
106
func (r * Record ) NumAttrs () int {
103
- return r .attrs .len ()* nAttrsInline + r .nTail
107
+ return r .nFront + len (r .back )
108
+ }
109
+
110
+ // Attrs calls f on each Attr in the Record.
111
+ func (r * Record ) Attrs (f func (Attr )) {
112
+ for i := 0 ; i < r .nFront ; i ++ {
113
+ f (r .front [i ])
114
+ }
115
+ for _ , a := range r .back {
116
+ f (a )
117
+ }
104
118
}
105
119
106
- // Attr returns the i'th Attr in r.
107
- func (r * Record ) Attr (i int ) Attr {
108
- if r .attrs .back != nil {
109
- r .attrs = r .attrs .normalize ()
120
+ // AddAttrs appends the given attrs to the Record's list of Attrs.
121
+ func (r * Record ) AddAttrs (attrs ... Attr ) {
122
+ n := copy (r .front [r .nFront :], attrs )
123
+ r .nFront += n
124
+ // Check if a copy was modified by slicing past the end
125
+ // and seeing if the Attr there is non-zero.
126
+ if cap (r .back ) > len (r .back ) {
127
+ end := r .back [:len (r .back )+ 1 ][len (r .back )]
128
+ if end != (Attr {}) {
129
+ panic ("copies of a slog.Record were both modified" )
130
+ }
110
131
}
111
- alen := r .attrs .len () * nAttrsInline
112
- if i < alen {
113
- return r .attrs .at (i / nAttrsInline )[i % nAttrsInline ]
132
+ r .back = append (r .back , attrs [n :]... )
133
+ }
134
+
135
+ func (r * Record ) setAttrsFromArgs (args []any ) {
136
+ var a Attr
137
+ for len (args ) > 0 {
138
+ a , args = argsToAttr (args )
139
+ if r .nFront < len (r .front ) {
140
+ r .front [r .nFront ] = a
141
+ r .nFront ++
142
+ } else {
143
+ if r .back == nil {
144
+ r .back = make ([]Attr , 0 , countAttrs (args ))
145
+ }
146
+ r .back = append (r .back , a )
147
+ }
114
148
}
115
- return r . tail [ i - alen ]
149
+
116
150
}
117
151
118
- // AddAttr appends an attributes to the record's list of attributes.
119
- // It does not check for duplicate keys.
120
- func (r * Record ) AddAttr (a Attr ) {
121
- if r .nTail == len (r .tail ) {
122
- r .attrs = r .attrs .append (r .tail )
123
- r .nTail = 0
152
+ // countAttrs returns the number of Attrs that would be created from args.
153
+ func countAttrs (args []any ) int {
154
+ n := 0
155
+ for i := 0 ; i < len (args ); i ++ {
156
+ n ++
157
+ if _ , ok := args [i ].(string ); ok {
158
+ i ++
159
+ }
124
160
}
125
- r .tail [r .nTail ] = a
126
- r .nTail ++
161
+ return n
127
162
}
128
163
129
- func (r * Record ) addAttrs (attrs []Attr ) {
130
- // TODO: be cleverer.
131
- for _ , a := range attrs {
132
- r .AddAttr (a )
164
+ const badKey = "!BADKEY"
165
+
166
+ // argsToAttr turns a prefix of the nonempty args slice into an Attr
167
+ // and returns the unconsumed portion of the slice.
168
+ // If args[0] is an Attr, it returns it.
169
+ // If args[0] is a string, it treats the first two elements as
170
+ // a key-value pair.
171
+ // Otherwise, it treats args[0] as a value with a missing key.
172
+ func argsToAttr (args []any ) (Attr , []any ) {
173
+ switch x := args [0 ].(type ) {
174
+ case string :
175
+ if len (args ) == 1 {
176
+ return String (badKey , x ), nil
177
+ }
178
+ return Any (x , args [1 ]), args [2 :]
179
+
180
+ case Attr :
181
+ return x , args [1 :]
182
+
183
+ default :
184
+ return Any (badKey , x ), args [1 :]
133
185
}
134
186
}
0 commit comments