@@ -54,12 +54,12 @@ const (
54
54
// the Writer's Write method. A Logger can be used simultaneously from
55
55
// multiple goroutines; it guarantees to serialize access to the Writer.
56
56
type Logger struct {
57
- mu sync.Mutex // ensures atomic writes; protects the following fields
58
- prefix string // prefix on each line to identify the logger (but see Lmsgprefix)
59
- flag int // properties
60
- out io. Writer // destination for output
61
- buf [] byte // for accumulating text to write
62
- isDiscard atomic.Bool // whether out == io.Discard
57
+ outMu sync.Mutex
58
+ out io. Writer // destination for output
59
+
60
+ prefix atomic. Pointer [ string ] // prefix on each line to identify the logger (but see Lmsgprefix)
61
+ flag atomic. Int32 // properties
62
+ isDiscard atomic.Bool
63
63
}
64
64
65
65
// New creates a new Logger. The out variable sets the
@@ -68,17 +68,17 @@ type Logger struct {
68
68
// after the log header if the Lmsgprefix flag is provided.
69
69
// The flag argument defines the logging properties.
70
70
func New (out io.Writer , prefix string , flag int ) * Logger {
71
- l := & Logger { out : out , prefix : prefix , flag : flag }
72
- if out == io . Discard {
73
- l . isDiscard . Store ( true )
74
- }
71
+ l := new ( Logger )
72
+ l . SetOutput ( out )
73
+ l . SetPrefix ( prefix )
74
+ l . SetFlags ( flag )
75
75
return l
76
76
}
77
77
78
78
// SetOutput sets the output destination for the logger.
79
79
func (l * Logger ) SetOutput (w io.Writer ) {
80
- l .mu .Lock ()
81
- defer l .mu .Unlock ()
80
+ l .outMu .Lock ()
81
+ defer l .outMu .Unlock ()
82
82
l .out = w
83
83
l .isDiscard .Store (w == io .Discard )
84
84
}
@@ -110,15 +110,15 @@ func itoa(buf *[]byte, i int, wid int) {
110
110
// - date and/or time (if corresponding flags are provided),
111
111
// - file and line number (if corresponding flags are provided),
112
112
// - l.prefix (if it's not blank and Lmsgprefix is set).
113
- func ( l * Logger ) formatHeader (buf * []byte , t time.Time , file string , line int ) {
114
- if l . flag & Lmsgprefix == 0 {
115
- * buf = append (* buf , l . prefix ... )
113
+ func formatHeader (buf * []byte , t time.Time , prefix string , flag int , file string , line int ) {
114
+ if flag & Lmsgprefix == 0 {
115
+ * buf = append (* buf , prefix ... )
116
116
}
117
- if l . flag & (Ldate | Ltime | Lmicroseconds ) != 0 {
118
- if l . flag & LUTC != 0 {
117
+ if flag & (Ldate | Ltime | Lmicroseconds ) != 0 {
118
+ if flag & LUTC != 0 {
119
119
t = t .UTC ()
120
120
}
121
- if l . flag & Ldate != 0 {
121
+ if flag & Ldate != 0 {
122
122
year , month , day := t .Date ()
123
123
itoa (buf , year , 4 )
124
124
* buf = append (* buf , '/' )
@@ -127,22 +127,22 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
127
127
itoa (buf , day , 2 )
128
128
* buf = append (* buf , ' ' )
129
129
}
130
- if l . flag & (Ltime | Lmicroseconds ) != 0 {
130
+ if flag & (Ltime | Lmicroseconds ) != 0 {
131
131
hour , min , sec := t .Clock ()
132
132
itoa (buf , hour , 2 )
133
133
* buf = append (* buf , ':' )
134
134
itoa (buf , min , 2 )
135
135
* buf = append (* buf , ':' )
136
136
itoa (buf , sec , 2 )
137
- if l . flag & Lmicroseconds != 0 {
137
+ if flag & Lmicroseconds != 0 {
138
138
* buf = append (* buf , '.' )
139
139
itoa (buf , t .Nanosecond ()/ 1e3 , 6 )
140
140
}
141
141
* buf = append (* buf , ' ' )
142
142
}
143
143
}
144
- if l . flag & (Lshortfile | Llongfile ) != 0 {
145
- if l . flag & Lshortfile != 0 {
144
+ if flag & (Lshortfile | Llongfile ) != 0 {
145
+ if flag & Lshortfile != 0 {
146
146
short := file
147
147
for i := len (file ) - 1 ; i > 0 ; i -- {
148
148
if file [i ] == '/' {
@@ -157,11 +157,32 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
157
157
itoa (buf , line , - 1 )
158
158
* buf = append (* buf , ": " ... )
159
159
}
160
- if l . flag & Lmsgprefix != 0 {
161
- * buf = append (* buf , l . prefix ... )
160
+ if flag & Lmsgprefix != 0 {
161
+ * buf = append (* buf , prefix ... )
162
162
}
163
163
}
164
164
165
+ var bufferPool = sync.Pool {New : func () any { return new ([]byte ) }}
166
+
167
+ func getBuffer () * []byte {
168
+ p := bufferPool .Get ().(* []byte )
169
+ * p = (* p )[:0 ]
170
+ return p
171
+ }
172
+
173
+ func putBuffer (p * []byte ) {
174
+ // Proper usage of a sync.Pool requires each entry to have approximately
175
+ // the same memory cost. To obtain this property when the stored type
176
+ // contains a variably-sized buffer, we add a hard limit on the maximum buffer
177
+ // to place back in the pool.
178
+ //
179
+ // See https://go.dev/issue/23199
180
+ if cap (* p ) > 64 << 10 {
181
+ * p = nil
182
+ }
183
+ bufferPool .Put (p )
184
+ }
185
+
165
186
// Output writes the output for a logging event. The string s contains
166
187
// the text to print after the prefix specified by the flags of the
167
188
// Logger. A newline is appended if the last character of s is not
@@ -170,28 +191,34 @@ func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
170
191
// paths it will be 2.
171
192
func (l * Logger ) Output (calldepth int , s string ) error {
172
193
now := time .Now () // get this early.
194
+
195
+ // Load prefix and flag once so that their value is consistent within
196
+ // this call regardless of any concurrent changes to their value.
197
+ prefix := l .Prefix ()
198
+ flag := l .Flags ()
199
+
173
200
var file string
174
201
var line int
175
- l .mu .Lock ()
176
- defer l .mu .Unlock ()
177
- if l .flag & (Lshortfile | Llongfile ) != 0 {
178
- // Release lock while getting caller info - it's expensive.
179
- l .mu .Unlock ()
202
+ if flag & (Lshortfile | Llongfile ) != 0 {
180
203
var ok bool
181
204
_ , file , line , ok = runtime .Caller (calldepth )
182
205
if ! ok {
183
206
file = "???"
184
207
line = 0
185
208
}
186
- l .mu .Lock ()
187
209
}
188
- l .buf = l .buf [:0 ]
189
- l .formatHeader (& l .buf , now , file , line )
190
- l .buf = append (l .buf , s ... )
210
+
211
+ buf := getBuffer ()
212
+ defer putBuffer (buf )
213
+ formatHeader (buf , now , prefix , flag , file , line )
214
+ * buf = append (* buf , s ... )
191
215
if len (s ) == 0 || s [len (s )- 1 ] != '\n' {
192
- l . buf = append (l . buf , '\n' )
216
+ * buf = append (* buf , '\n' )
193
217
}
194
- _ , err := l .out .Write (l .buf )
218
+
219
+ l .outMu .Lock ()
220
+ defer l .outMu .Unlock ()
221
+ _ , err := l .out .Write (* buf )
195
222
return err
196
223
}
197
224
@@ -264,37 +291,32 @@ func (l *Logger) Panicln(v ...any) {
264
291
// Flags returns the output flags for the logger.
265
292
// The flag bits are Ldate, Ltime, and so on.
266
293
func (l * Logger ) Flags () int {
267
- l .mu .Lock ()
268
- defer l .mu .Unlock ()
269
- return l .flag
294
+ return int (l .flag .Load ())
270
295
}
271
296
272
297
// SetFlags sets the output flags for the logger.
273
298
// The flag bits are Ldate, Ltime, and so on.
274
299
func (l * Logger ) SetFlags (flag int ) {
275
- l .mu .Lock ()
276
- defer l .mu .Unlock ()
277
- l .flag = flag
300
+ l .flag .Store (int32 (flag ))
278
301
}
279
302
280
303
// Prefix returns the output prefix for the logger.
281
304
func (l * Logger ) Prefix () string {
282
- l .mu .Lock ()
283
- defer l .mu .Unlock ()
284
- return l .prefix
305
+ if p := l .prefix .Load (); p != nil {
306
+ return * p
307
+ }
308
+ return ""
285
309
}
286
310
287
311
// SetPrefix sets the output prefix for the logger.
288
312
func (l * Logger ) SetPrefix (prefix string ) {
289
- l .mu .Lock ()
290
- defer l .mu .Unlock ()
291
- l .prefix = prefix
313
+ l .prefix .Store (& prefix )
292
314
}
293
315
294
316
// Writer returns the output destination for the logger.
295
317
func (l * Logger ) Writer () io.Writer {
296
- l .mu .Lock ()
297
- defer l .mu .Unlock ()
318
+ l .outMu .Lock ()
319
+ defer l .outMu .Unlock ()
298
320
return l .out
299
321
}
300
322
0 commit comments