github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/logger/implementation/zap/logger.go (about) 1 // Copyright 2022 Meta Platforms, Inc. and affiliates. 2 // 3 // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 // 5 // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 // 7 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 // 9 // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 // 11 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 13 // Copyright (c) Facebook, Inc. and its affiliates. 14 // 15 // This source code is licensed under the MIT license found in the 16 // LICENSE file in the root directory of this source tree. 17 18 package zap 19 20 import ( 21 "fmt" 22 "os" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/facebookincubator/go-belt" 29 "github.com/facebookincubator/go-belt/pkg/field" 30 "github.com/facebookincubator/go-belt/pkg/valuesparser" 31 "github.com/facebookincubator/go-belt/tool/logger" 32 "github.com/facebookincubator/go-belt/tool/logger/adapter" 33 "github.com/facebookincubator/go-belt/tool/logger/types" 34 "go.uber.org/zap" 35 "go.uber.org/zap/zapcore" 36 ) 37 38 var ( 39 // FieldNameTraceIDs is the field name used to store belt.TraceIDs. 40 FieldNameTraceIDs = "trace_id" 41 ) 42 43 // Emitter is the implementation of types.Emitter based on a zap (non-sugared) logger. 44 type Emitter struct { 45 ZapLogger *zap.Logger 46 } 47 48 var _ types.Emitter = (*Emitter)(nil) 49 50 // NewEmitter returns a new instance of Emitter 51 func NewEmitter(zapLogger *zap.Logger) Emitter { 52 return Emitter{ 53 ZapLogger: zapLogger, 54 } 55 } 56 57 const ( 58 preallocateFieldsSize = 16 59 ) 60 61 var fieldsPool = sync.Pool{ 62 New: func() any { 63 fields := make([]zap.Field, 0, preallocateFieldsSize) 64 return &fields 65 }, 66 } 67 68 func acquireZapFields() *[]zap.Field { 69 return fieldsPool.Get().(*[]zap.Field) 70 } 71 72 func releaseZapFields(fieldsPtr *[]zap.Field) { 73 if fieldsPtr == nil || len(*fieldsPtr) >= 256 { 74 return 75 } 76 *fieldsPtr = (*fieldsPtr)[:0] 77 fieldsPool.Put(fieldsPtr) 78 } 79 80 // Flush implements types.Emitter. 81 func (l Emitter) Flush() { 82 err := l.getZapLogger().Sync() 83 if err == nil { 84 return 85 } 86 87 errDesc := err.Error() 88 if strings.Contains(errDesc, "sync /dev/stderr: invalid argument") { 89 // Intentionally ignore this error, see https://github.com/uber-go/zap/issues/328 90 return 91 } 92 93 l.Emit(&types.Entry{ 94 Timestamp: time.Now(), 95 Level: types.LevelError, 96 Message: fmt.Sprintf("unable to Sync(): %s", errDesc), 97 }) 98 } 99 100 // CheckZapLevel returns true if the given zap's logging level is enabled in this logger. 101 func (l Emitter) CheckZapLevel(level zapcore.Level) bool { 102 return l.getZapLogger().Core().Enabled(level) 103 } 104 105 // CheckLevel returns true if the given types.Level is enabled in this logger. 106 func (l Emitter) CheckLevel(level types.Level) bool { 107 return l.CheckZapLevel(LevelToZap(level)) 108 } 109 110 // Emit implements types.Emitter. 111 func (l Emitter) Emit(entry *types.Entry) { 112 // We log panics and fatals even if logging level is lower because 113 // they supposed to trigger panic or os.Exit, and we cannot just return without that. 114 // So it should be silent, but destructive. 115 if entry.Level == types.LevelNone || !l.CheckLevel(entry.Level) && entry.Level > types.LevelPanic { 116 return 117 } 118 119 var zapFields []zap.Field 120 if !entry.Properties.Has(EntryPropertyIgnoreFields) && entry.Fields != nil { 121 zapFieldsPtr := fieldsToZap(entry.Fields.Len(), entry.Fields.ForEachField) 122 if zapFieldsPtr != nil { 123 defer releaseZapFields(zapFieldsPtr) 124 zapFields = *zapFieldsPtr 125 } 126 } 127 128 zapEntry := zapcore.Entry{ 129 Level: LevelToZap(entry.Level), 130 Time: entry.Timestamp, 131 LoggerName: "", 132 Message: entry.Message, 133 Stack: "", 134 } 135 if entry.Caller != 0 { 136 file, line := entry.Caller.FileLine() 137 zapEntry.Caller = zapcore.EntryCaller{ 138 Defined: true, 139 PC: uintptr(entry.Caller), 140 File: file, 141 Line: line, 142 } 143 } 144 145 l.LogZapEntry(zapEntry, zapFields...) 146 } 147 148 // LogZapEntry logs an entry given its zap structure. 149 func (l Emitter) LogZapEntry(zapEntry zapcore.Entry, zapFields ...zap.Field) { 150 l.ZapLogger.Core().Check(zapEntry, nil).Write(zapFields...) 151 } 152 153 type mostlyPersistentData struct { 154 entryPool *sync.Pool 155 zapEntryPool *sync.Pool 156 fmtBufPool *sync.Pool 157 preHooks logger.PreHooks 158 hooks logger.Hooks 159 traceIDs belt.TraceIDs 160 getCallerFunc types.GetCallerPC 161 messagePrefix string 162 entryProperties types.EntryProperties 163 } 164 165 // CompactLogger is an implementation of types.CompactLogger based on a zap logger. 166 type CompactLogger struct { 167 *mostlyPersistentData 168 emitter Emitter 169 contextFields *field.FieldsChain 170 contextNewFields uint32 171 prepareEmitterOnce sync.Once 172 } 173 174 var _ adapter.CompactLogger = (*CompactLogger)(nil) 175 176 // Flush implements types.CompactLogger. 177 func (l *CompactLogger) Flush() { 178 l.emitter.Flush() 179 for _, hook := range l.hooks { 180 hook.Flush() 181 } 182 } 183 184 var timeNow = time.Now 185 186 func (l *CompactLogger) acquireEntry() *types.Entry { 187 entry := l.entryPool.Get().(*types.Entry) 188 entry.Timestamp = timeNow() 189 entry.Fields = l.contextFields 190 entry.Properties = append(entry.Properties, EntryPropertyIgnoreFields) 191 return entry 192 } 193 194 func (l *CompactLogger) releaseEntry(entry *types.Entry) { 195 entry.Caller = 0 196 entry.Fields = nil 197 entry.Message = "" 198 entry.Properties = entry.Properties[:0] 199 l.entryPool.Put(entry) 200 } 201 202 func (l *CompactLogger) acquireZapEntry() *zapcore.Entry { 203 entry := l.zapEntryPool.Get().(*zapcore.Entry) 204 entry.Time = timeNow() 205 return entry 206 } 207 208 func (l *CompactLogger) releaseZapEntry(entry *zapcore.Entry) { 209 entry.Message = "" 210 entry.Stack = "" 211 entry.Caller.Defined = false 212 l.zapEntryPool.Put(entry) 213 } 214 215 func (l *CompactLogger) acquireBuf() *strings.Builder { 216 return l.fmtBufPool.Get().(*strings.Builder) 217 } 218 219 func (l *CompactLogger) releaseBuf(buf *strings.Builder) { 220 if buf.Cap() > 1024 { 221 return 222 } 223 buf.Reset() 224 l.fmtBufPool.Put(buf) 225 } 226 227 func (l *CompactLogger) logEntry(entry *logger.Entry) { 228 if !entry.Caller.Defined() && l.getCallerFunc != nil { 229 entry.Caller = l.getCallerFunc() 230 } 231 if len(l.entryProperties) > 0 { 232 entry.Properties = append(entry.Properties, l.entryProperties) 233 } 234 if !adapter.ProcessHooks(l.hooks, entry) { 235 return 236 } 237 238 zapEntry := l.acquireZapEntry() 239 defer l.releaseZapEntry(zapEntry) 240 241 zapEntry.Time = entry.Timestamp 242 zapEntry.Caller.Defined = entry.Caller.Defined() 243 if zapEntry.Caller.Defined { 244 file, line := entry.Caller.FileLine() 245 zapEntry.Caller = zapcore.EntryCaller{ 246 Defined: true, 247 PC: uintptr(entry.Caller), 248 File: file, 249 Line: line, 250 } 251 } 252 zapEntry.Message = entry.Message 253 zapEntry.Level = LevelToZap(entry.Level) 254 var zapFields []zap.Field 255 zapFieldsPtr := fieldsToZap(entry.Fields.Len(), entry.Fields.ForEachField) 256 if zapFieldsPtr != nil { 257 defer releaseZapFields(zapFieldsPtr) 258 zapFields = *zapFieldsPtr 259 } 260 261 l.logZapEntryNoHooks(zapEntry, zapFields...) 262 } 263 264 func (l *CompactLogger) logZapEntryNoHooks(zapEntry *zapcore.Entry, zapFields ...zapcore.Field) { 265 l.prepareEmitter() 266 l.zapSetCaller(zapEntry) 267 l.emitter.LogZapEntry(*zapEntry, zapFields...) 268 269 // zap authors decided not to panic or/and exit on Panics and Fatals, 270 // see: https://github.com/uber-go/zap/issues/358 271 // 272 // So doing this manually here: 273 switch zapEntry.Level { 274 case zap.PanicLevel, zap.DPanicLevel: 275 panic(fmt.Sprintf("%#+v: %#+v", *zapEntry, zapFields)) 276 case zap.FatalLevel: 277 os.Exit(1) 278 } 279 } 280 281 func (l *CompactLogger) zapSetCaller(zapEntry *zapcore.Entry) { 282 if zapEntry.Caller.Defined || l.getCallerFunc == nil { 283 return 284 } 285 286 caller := l.getCallerFunc() 287 if !caller.Defined() { 288 return 289 } 290 file, line := caller.FileLine() 291 292 zapEntry.Caller = zapcore.NewEntryCaller( 293 uintptr(caller), 294 file, 295 line, 296 true, 297 ) 298 } 299 300 // LogFields implements types.CompactLogger. 301 func (l *CompactLogger) LogFields(level types.Level, message string, fields field.AbstractFields) { 302 preHooksResult := adapter.LogFieldsPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), message, fields) 303 if preHooksResult.Skip { 304 return 305 } 306 307 if preHooksResult.ExtraFields != nil { 308 fields = field.Slice[field.AbstractFields]{fields, preHooksResult.ExtraFields} 309 } 310 311 if len(l.hooks) != 0 { 312 entry := l.acquireEntry() 313 defer l.releaseEntry(entry) 314 315 entry.Level = level 316 entry.Message = l.messagePrefix + message 317 entry.Fields = fields 318 l.logEntry(entry) 319 return 320 } 321 322 entry := l.acquireZapEntry() 323 defer l.releaseZapEntry(entry) 324 325 entry.Level = LevelToZap(level) 326 entry.Message = l.messagePrefix + message 327 328 var ( 329 zapFields []zap.Field 330 zapFieldsPtr *[]zap.Field 331 ) 332 zapFieldsPtr = fieldsToZap(fields.Len(), fields.ForEachField) 333 if zapFieldsPtr != nil { 334 defer releaseZapFields(zapFieldsPtr) 335 zapFields = *zapFieldsPtr 336 } 337 l.logZapEntryNoHooks(entry, zapFields...) 338 } 339 340 // Logf implements types.CompactLogger. 341 func (l *CompactLogger) Logf(level types.Level, format string, args ...any) { 342 preHooksResult := adapter.LogfPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), format, args...) 343 if preHooksResult.Skip { 344 return 345 } 346 347 buf := l.acquireBuf() 348 defer l.releaseBuf(buf) 349 if len(l.messagePrefix) > 0 { 350 buf.WriteString(l.messagePrefix) 351 } 352 fmt.Fprintf(buf, format, args...) 353 354 if len(l.hooks) != 0 { 355 entry := l.acquireEntry() 356 defer l.releaseEntry(entry) 357 358 entry.Level = level 359 entry.Message = buf.String() 360 entry.Fields = preHooksResult.ExtraFields 361 l.logEntry(entry) 362 return 363 } 364 365 entry := l.acquireZapEntry() 366 defer l.releaseZapEntry(entry) 367 368 entry.Level = LevelToZap(level) 369 entry.Message = buf.String() 370 371 var zapFields []zap.Field 372 if preHooksResult.ExtraFields != nil { 373 zapFieldsPtr := fieldsToZap(preHooksResult.ExtraFields.Len(), preHooksResult.ExtraFields.ForEachField) 374 if zapFieldsPtr != nil { 375 defer releaseZapFields(zapFieldsPtr) 376 zapFields = *zapFieldsPtr 377 } 378 } 379 380 l.logZapEntryNoHooks(entry, zapFields...) 381 } 382 383 // Log implements types.CompactLogger. 384 func (l *CompactLogger) Log(level types.Level, values ...any) { 385 forceProcess := level == logger.LevelFatal || level == logger.LevelPanic 386 387 preHooksResult := adapter.LogPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), values...) 388 if preHooksResult.Skip && !forceProcess { 389 return 390 } 391 392 if len(values) == 1 { 393 if entry, ok := values[0].(*logger.Entry); ok { 394 if entry.TraceIDs != nil { 395 entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields, &field.Field{Key: FieldNameTraceIDs, Value: entry.TraceIDs}) 396 } else { 397 entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields) 398 } 399 l.logEntry(entry) 400 return 401 } 402 } 403 404 valuesParser := valuesparser.AnySlice(values) 405 406 if len(l.hooks) != 0 { 407 entry := l.acquireEntry() 408 defer l.releaseEntry(entry) 409 410 entry.Level = level 411 entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields) 412 413 buf := l.acquireBuf() 414 defer l.releaseBuf(buf) 415 valuesParser.ForEachField(func(f *field.Field) bool { return true }) // just causing to parse the fields to make valuesParser.WriteUnparsed(buf) work correctly 416 buf.WriteString(l.messagePrefix) 417 valuesParser.WriteUnparsed(buf) 418 entry.Message = buf.String() 419 420 l.logEntry(entry) 421 return 422 } 423 424 entry := l.acquireZapEntry() 425 defer l.releaseZapEntry(entry) 426 427 entry.Level = LevelToZap(level) 428 429 var ( 430 zapFields []zap.Field 431 zapFieldsPtr *[]zap.Field 432 ) 433 if preHooksResult.ExtraFields != nil { 434 fields := field.Slice[field.AbstractFields]{&valuesParser, preHooksResult.ExtraFields} 435 zapFieldsPtr = fieldsToZap(fields.Len(), fields.ForEachField) 436 } else { 437 zapFieldsPtr = fieldsToZap(valuesParser.Len(), valuesParser.ForEachField) 438 } 439 if zapFieldsPtr != nil { 440 defer releaseZapFields(zapFieldsPtr) 441 zapFields = *zapFieldsPtr 442 } 443 444 buf := l.acquireBuf() 445 defer l.releaseBuf(buf) 446 buf.WriteString(l.messagePrefix) 447 valuesParser.WriteUnparsed(buf) 448 entry.Message = buf.String() 449 450 l.logZapEntryNoHooks(entry, zapFields...) 451 } 452 453 // Level implements types.CompactLogger. 454 func (l *CompactLogger) Level() types.Level { 455 core := l.emitter.ZapLogger.Core() 456 for level := types.EndOfLevel - 1; level >= 0; level-- { 457 if core.Enabled(LevelToZap(level)) { 458 return level 459 } 460 } 461 return types.LevelNone 462 } 463 464 // WithLevel implements types.CompactLogger. 465 func (l *CompactLogger) WithLevel(newLevel types.Level) adapter.CompactLogger { 466 branch := l.branch() 467 branch.emitter.ZapLogger = branch.emitter.ZapLogger.WithOptions(zap.IncreaseLevel(LevelToZap(newLevel))) 468 return branch 469 } 470 471 func (l *CompactLogger) branch() *CompactLogger { 472 return &CompactLogger{ 473 mostlyPersistentData: l.mostlyPersistentData, 474 emitter: Emitter{ZapLogger: l.emitter.getZapLogger()}, 475 contextFields: l.contextFields, 476 contextNewFields: atomic.LoadUint32(&l.contextNewFields), 477 } 478 } 479 480 func (l *CompactLogger) clone() *CompactLogger { 481 clone := l.branch() 482 clone.mostlyPersistentData = &[]mostlyPersistentData{*l.mostlyPersistentData}[0] 483 return clone 484 } 485 486 // WithMessagePrefix implements types.CompactLogger. 487 func (l *CompactLogger) WithMessagePrefix(prefix string) adapter.CompactLogger { 488 clone := l.clone() 489 clone.messagePrefix += prefix 490 return clone 491 } 492 493 // WithEntryProperties implements types.CompactLogger. 494 func (l *CompactLogger) WithEntryProperties(props ...types.EntryProperty) adapter.CompactLogger { 495 clone := l.clone() 496 clone.entryProperties = clone.entryProperties.Add(props) 497 return clone 498 } 499 500 // WithHooks implements types.CompactLogger. 501 func (l *CompactLogger) WithHooks(hooks ...types.Hook) adapter.CompactLogger { 502 clone := l.clone() 503 clone.hooks = make(types.Hooks, len(l.hooks)+len(hooks)) 504 copy(clone.hooks, l.hooks) 505 copy(clone.hooks[len(l.hooks):], hooks) 506 return clone 507 } 508 509 // WithPreHooks implements types.CompactLogger. 510 func (l *CompactLogger) WithPreHooks(preHooks ...types.PreHook) adapter.CompactLogger { 511 clone := l.clone() 512 clone.preHooks = make(types.PreHooks, len(l.preHooks)+len(preHooks)) 513 copy(clone.preHooks, l.preHooks) 514 copy(clone.preHooks[len(l.preHooks):], preHooks) 515 return clone 516 } 517 518 // WithField implements types.CompactLogger. 519 func (l *CompactLogger) WithField( 520 key field.Key, 521 value field.Value, 522 props ...field.Property, 523 ) adapter.CompactLogger { 524 branch := l.branch() 525 branch.contextFields = l.contextFields.WithField(key, value, props) 526 branch.contextNewFields++ 527 return branch 528 } 529 530 // WithFields implements types.CompactLogger. 531 func (l *CompactLogger) WithFields(fields field.AbstractFields) adapter.CompactLogger { 532 branch := l.branch() 533 branch.contextFields = l.contextFields.WithFields(fields) 534 branch.contextNewFields += uint32(fields.Len()) 535 return branch 536 } 537 538 // WithTraceIDs implements types.CompactLogger and belt.Tool. 539 func (l *CompactLogger) WithTraceIDs(allTraceIDs belt.TraceIDs, newTraceIDsCount int) belt.Tool { 540 clone := l.clone() 541 clone.traceIDs = allTraceIDs 542 clone.contextFields = clone.contextFields.WithField(FieldNameTraceIDs, allTraceIDs) 543 clone.contextNewFields++ 544 return clone 545 } 546 547 // WithContextFields implements types.CompactLogger and belt.Tool. 548 func (l *CompactLogger) WithContextFields(allFields *field.FieldsChain, newFieldsCount int) belt.Tool { 549 branch := l.branch() 550 branch.contextFields = allFields 551 branch.contextNewFields += uint32(newFieldsCount) 552 return branch 553 } 554 555 func (l *CompactLogger) compileEmitterFields() { 556 // This function is the only one which can change 557 // the value of l.contextNewFields, and it is called 558 // only within l.prepareEmitterOnce.Do, so: 559 // * We do not need to atomically load the l.contextNewFields value. 560 // * We still need to atomically store the l.contextNewFields value, 561 // because other goroutines may read it concurrently in other functions. 562 if l.contextNewFields == 0 { 563 return 564 } 565 defer atomic.StoreUint32(&l.contextNewFields, 0) 566 zapFieldsPtr := fieldsToZap(int(l.contextNewFields), l.contextFields.ForEachField) 567 if zapFieldsPtr == nil { 568 return 569 } 570 defer releaseZapFields(zapFieldsPtr) 571 l.emitter.setZapLogger(l.emitter.ZapLogger.With(*zapFieldsPtr...)) 572 } 573 574 func (l *CompactLogger) prepareEmitter() { 575 l.prepareEmitterOnce.Do(func() { 576 l.compileEmitterFields() 577 }) 578 } 579 580 // Emitter implements types.CompactLogger. 581 func (l *CompactLogger) Emitter() types.Emitter { 582 l.prepareEmitter() 583 return l.emitter 584 } 585 586 // DefaultZapLogger is the (overridable) function which returns 587 // a zap logger with the default configuration. 588 // 589 // Do not override this anywhere but in the `main` package. 590 var DefaultZapLogger = func() *zap.Logger { 591 cfg := zap.NewProductionConfig() 592 cfg.Level = zap.NewAtomicLevelAt(LevelToZap(logger.LevelTrace)) 593 zapLogger, err := cfg.Build() 594 if err != nil { 595 panic(err) 596 } 597 return zapLogger 598 } 599 600 // Default returns a logger.Logger using the default zap logger 601 // (see DefaultZapLogger). 602 func Default() types.Logger { 603 return New(DefaultZapLogger()) 604 } 605 606 // New returns a new instance of logger.Logger given a zap logger. 607 func New(logger *zap.Logger, opts ...types.Option) types.Logger { 608 return adapter.GenericSugar{ 609 CompactLogger: newCompactLoggerFromZap(logger, opts...), 610 } 611 } 612 613 func newCompactLoggerFromZap(zapLogger *zap.Logger, opts ...types.Option) *CompactLogger { 614 cfg := types.Options(opts).Config() 615 return &CompactLogger{ 616 emitter: NewEmitter(zapLogger), 617 mostlyPersistentData: &mostlyPersistentData{ 618 getCallerFunc: cfg.GetCallerFunc, 619 fmtBufPool: &sync.Pool{ 620 New: func() any { 621 return &strings.Builder{} 622 }, 623 }, 624 entryPool: &sync.Pool{ 625 New: func() any { 626 return &types.Entry{} 627 }, 628 }, 629 zapEntryPool: &sync.Pool{ 630 New: func() any { 631 return &zapcore.Entry{} 632 }, 633 }, 634 }, 635 } 636 }