github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/logger/implementation/logrus/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 logrus
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"path/filepath"
    25  	"runtime"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/facebookincubator/go-belt"
    31  	"github.com/facebookincubator/go-belt/pkg/field"
    32  	"github.com/facebookincubator/go-belt/pkg/valuesparser"
    33  	"github.com/facebookincubator/go-belt/tool/logger"
    34  	"github.com/facebookincubator/go-belt/tool/logger/adapter"
    35  	"github.com/facebookincubator/go-belt/tool/logger/types"
    36  	"github.com/sirupsen/logrus"
    37  )
    38  
    39  var (
    40  	// FieldNameTraceIDs is the field name used to store trace IDs.
    41  	FieldNameTraceIDs = "trace_id"
    42  )
    43  
    44  // Emitter implements types.Emitter given a logrus entry.
    45  type Emitter struct {
    46  	// LogrusEntry is the actual emitter. And Emitter structure is only a wrapper
    47  	// to implement another interface based on logrus.Entry.
    48  	//
    49  	// We use logrus.Entry instead of logrus.Logger to be able to store precompiled fields
    50  	LogrusEntry *logrus.Entry
    51  }
    52  
    53  var _ types.Emitter = (*Emitter)(nil)
    54  
    55  // Flush implements types.Emitter
    56  func (l *Emitter) Flush() {}
    57  
    58  // CheckLevel returns true if an event of a given logging level will be logged
    59  func (l *Emitter) CheckLevel(level types.Level) bool {
    60  	return LevelFromLogrus(l.getLogrusEntry().Level) >= level
    61  }
    62  
    63  // Emit implements types.Emitter
    64  func (l *Emitter) Emit(entry *types.Entry) {
    65  	if entry.Level == types.LevelNone || (!l.CheckLevel(entry.Level) && entry.Level > types.LevelPanic) {
    66  		return
    67  	}
    68  	l.logEntry(entry)
    69  }
    70  
    71  func ptr[T any](in T) *T {
    72  	return &in
    73  }
    74  
    75  func (l *Emitter) logEntry(entry *types.Entry) {
    76  	logrusLevel := LevelToLogrus(entry.Level)
    77  	logrusEntry := ptr(*l.LogrusEntry) // shallow copy
    78  	logrusEntry.Level = logrusLevel
    79  	logrusEntry.Caller = nil
    80  	if entry.Caller.Defined() {
    81  		caller := &runtime.Frame{
    82  			PC:   uintptr(entry.Caller),
    83  			Func: runtime.FuncForPC(uintptr(entry.Caller)),
    84  		}
    85  		caller.Function = caller.Func.Name()
    86  		caller.File, caller.Line = entry.Caller.FileLine()
    87  		caller.Entry = entry.Caller.Entry()
    88  
    89  		// this is effectively a no-op line, see RestoreCallerHook's description:
    90  		logrusEntry.Caller = caller
    91  		// the actualizator-line:
    92  		logrusEntry.Context = context.WithValue(
    93  			context.Background(), LogrusCtxKeyCaller, caller,
    94  		)
    95  	}
    96  	logrusEntry.Time = entry.Timestamp
    97  	if !entry.Properties.Has(EntryPropertyIgnoreFields) {
    98  		newFieldsCount := 0
    99  		if entry.Fields != nil {
   100  			newFieldsCount = entry.Fields.Len()
   101  		}
   102  		if newFieldsCount > 0 || entry.TraceIDs != nil {
   103  			fields := make(logrus.Fields, len(logrusEntry.Data)+newFieldsCount+1)
   104  			for k, v := range logrusEntry.Data {
   105  				fields[k] = v
   106  			}
   107  			if entry.Fields != nil {
   108  				entry.Fields.ForEachField(func(f *field.Field) bool {
   109  					fields[f.Key] = f.Value
   110  					return true
   111  				})
   112  			}
   113  			logrusEntry.Data = fields
   114  			if entry.TraceIDs != nil {
   115  				logrusEntry.Data[FieldNameTraceIDs] = entry.TraceIDs
   116  			}
   117  		}
   118  	}
   119  	logrusEntry.Log(logrusLevel, entry.Message)
   120  	// A bug in logrus: if we call `Log(Fatal, ...` instead of `Fatal(...` then
   121  	// it does not call the exit function.
   122  	//
   123  	// Fixing this bug manually here:
   124  	if logrusLevel == logrus.FatalLevel {
   125  		exitFunc := logrusEntry.Logger.ExitFunc
   126  		if exitFunc == nil {
   127  			exitFunc = os.Exit
   128  		}
   129  		exitFunc(1) // we use the same exit code as the original logrus's Fatal.
   130  	}
   131  }
   132  
   133  // NewEmitter returns a new types.Emitter implementation based on a logrus logger.
   134  // This functions takes ownership of the logrus Logger instance.
   135  func NewEmitter(logger *logrus.Logger, logLevel logger.Level) *Emitter {
   136  	// Enforcing Trace level, because we will take care of logging levels ourselves.
   137  	//
   138  	// This is required, because logrus does not support contextual logging levels,
   139  	// so we handle them manually to provide with this feature.
   140  	logger.Level = logrus.TraceLevel
   141  
   142  	// Logrus internally duplicates Entry and does not copy the Caller, so
   143  	// to keep the Caller we implement a workaround hook, which manually restores
   144  	// it.
   145  	//
   146  	// Note:
   147  	// The problem is there with any ReportCaller value, because with
   148  	// ReportCaller disabled the Caller is just set to nil, but will
   149  	// ReportCaller enabled it overwrites Caller using logrus's internal logic.
   150  	// So either it is required to hack-around each Formatter or to use a Hook,
   151  	// or to call `write` method manually (which is extra unsafety).
   152  	logger.Hooks.Add(newRestoreCallerHook())
   153  	logger.ReportCaller = false
   154  
   155  	logrusEntry := newLogrusEntry(logger, LevelToLogrus(logLevel))
   156  	return &Emitter{
   157  		LogrusEntry: logrusEntry,
   158  	}
   159  }
   160  
   161  func newLogrusEntry(logrusLogger *logrus.Logger, level logrus.Level) *logrus.Entry {
   162  	return &logrus.Entry{
   163  		Logger: logrusLogger,
   164  		Level:  level,
   165  	}
   166  }
   167  
   168  type mostlyPersistentData struct {
   169  	fmtBufPool      *sync.Pool
   170  	entryPool       *sync.Pool
   171  	preHooks        types.PreHooks
   172  	hooks           types.Hooks
   173  	traceIDs        belt.TraceIDs
   174  	getCallerFunc   types.GetCallerPC
   175  	messagePrefix   string
   176  	entryProperties types.EntryProperties
   177  }
   178  
   179  // CompactLogger implements types.CompactLogger given a logrus logger.
   180  type CompactLogger struct {
   181  	*mostlyPersistentData
   182  	emitter            *Emitter
   183  	contextFields      *field.FieldsChain
   184  	prepareEmitterOnce sync.Once
   185  }
   186  
   187  var _ adapter.CompactLogger = (*CompactLogger)(nil)
   188  
   189  // Flush implements types.CompactLogger
   190  func (l *CompactLogger) Flush() {
   191  	l.emitter.Flush()
   192  	for _, hook := range l.hooks {
   193  		hook.Flush()
   194  	}
   195  }
   196  
   197  func (l *CompactLogger) acquireEntry() *types.Entry {
   198  	entry := l.entryPool.Get().(*types.Entry)
   199  	entry.Timestamp = time.Now()
   200  	entry.Fields = l.contextFields
   201  	entry.Properties = append(entry.Properties, EntryPropertyIgnoreFields)
   202  	return entry
   203  }
   204  
   205  func (l *CompactLogger) releaseEntry(entry *types.Entry) {
   206  	entry.Caller = 0
   207  	entry.Fields = nil
   208  	entry.Message = ""
   209  	entry.Properties = entry.Properties[:0]
   210  	l.entryPool.Put(entry)
   211  }
   212  
   213  func (l *CompactLogger) acquireBuf() *strings.Builder {
   214  	return l.fmtBufPool.Get().(*strings.Builder)
   215  }
   216  
   217  func (l *CompactLogger) releaseBuf(buf *strings.Builder) {
   218  	if buf.Cap() > 1024 {
   219  		return
   220  	}
   221  	buf.Reset()
   222  	l.fmtBufPool.Put(buf)
   223  }
   224  
   225  // LogEntry logs the given entry.
   226  func (l *CompactLogger) LogEntry(entry *types.Entry) {
   227  	if !l.emitter.CheckLevel(entry.Level) {
   228  		return
   229  	}
   230  	l.logEntry(entry)
   231  }
   232  func (l *CompactLogger) logEntry(entry *types.Entry) {
   233  	if l.messagePrefix != "" {
   234  		entry.Message = l.messagePrefix + entry.Message
   235  	}
   236  	if !entry.Caller.Defined() && l.getCallerFunc != nil {
   237  		entry.Caller = l.getCallerFunc()
   238  	}
   239  	if len(l.entryProperties) != 0 {
   240  		entry.Properties = append(entry.Properties, l.entryProperties)
   241  	}
   242  	if !adapter.ProcessHooks(l.hooks, entry) {
   243  		return
   244  	}
   245  	l.prepareEmitter()
   246  	l.emitter.logEntry(entry)
   247  }
   248  
   249  // WithMessagePrefix implements types.CompactLogger
   250  func (l *CompactLogger) WithMessagePrefix(prefix string) adapter.CompactLogger {
   251  	clone := l.clone()
   252  	clone.messagePrefix += prefix
   253  	return clone
   254  }
   255  
   256  // WithEntryProperties implements types.CompactLogger.
   257  func (l *CompactLogger) WithEntryProperties(props ...types.EntryProperty) adapter.CompactLogger {
   258  	clone := l.clone()
   259  	clone.entryProperties = clone.entryProperties.Add(props)
   260  	return clone
   261  }
   262  
   263  // LogFields implements types.CompactLogger
   264  func (l *CompactLogger) LogFields(level types.Level, message string, fields field.AbstractFields) {
   265  	preHooksResult := adapter.LogFieldsPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), message, fields)
   266  	if preHooksResult.Skip {
   267  		return
   268  	}
   269  
   270  	if preHooksResult.ExtraFields != nil {
   271  		fields = field.Slice[field.AbstractFields]{fields, preHooksResult.ExtraFields}
   272  	}
   273  	logger := l
   274  	if fields.Len() > 0 {
   275  		logger = l.WithFields(fields).(*CompactLogger)
   276  	}
   277  	entry := logger.acquireEntry()
   278  	defer logger.releaseEntry(entry)
   279  
   280  	entry.Level = level
   281  	entry.Message = message
   282  	logger.logEntry(entry)
   283  }
   284  
   285  // Logf implements types.CompactLogger
   286  func (l *CompactLogger) Logf(level types.Level, format string, args ...any) {
   287  	preHooksResult := adapter.LogfPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), format, args...)
   288  	if preHooksResult.Skip {
   289  		return
   290  	}
   291  
   292  	logger := l
   293  	if preHooksResult.ExtraFields != nil {
   294  		logger = logger.WithFields(preHooksResult.ExtraFields).(*CompactLogger)
   295  	}
   296  
   297  	entry := logger.acquireEntry()
   298  	defer logger.releaseEntry(entry)
   299  
   300  	buf := logger.acquireBuf()
   301  	defer logger.releaseBuf(buf)
   302  	fmt.Fprintf(buf, format, args...)
   303  
   304  	entry.Level = level
   305  	entry.Message = buf.String()
   306  	logger.logEntry(entry)
   307  }
   308  
   309  // Log implements types.CompactLogger
   310  func (l *CompactLogger) Log(level types.Level, values ...any) {
   311  	preHooksResult := adapter.LogPreprocess(l.preHooks, l.traceIDs, level, l.emitter.CheckLevel(level), values...)
   312  	if preHooksResult.Skip {
   313  		return
   314  	}
   315  
   316  	if len(values) == 1 {
   317  		if entry, ok := values[0].(*logger.Entry); ok {
   318  			entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields)
   319  			if entry.TraceIDs != nil {
   320  				entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields, &field.Field{Key: FieldNameTraceIDs, Value: entry.TraceIDs})
   321  			} else {
   322  				entry.Fields = field.Add(entry.Fields, preHooksResult.ExtraFields)
   323  			}
   324  			l.logEntry(entry)
   325  			return
   326  		}
   327  	}
   328  
   329  	var finalFields field.AbstractFields
   330  	valuesParser := valuesparser.AnySlice(values)
   331  	if preHooksResult.ExtraFields != nil {
   332  		finalFields = field.Slice[field.AbstractFields]{&valuesParser, preHooksResult.ExtraFields}
   333  	} else {
   334  		finalFields = &valuesParser
   335  	}
   336  
   337  	logger := l
   338  	if finalFields.Len() != 0 {
   339  		logger = logger.WithFields(finalFields).(*CompactLogger)
   340  	}
   341  
   342  	entry := logger.acquireEntry()
   343  	defer logger.releaseEntry(entry)
   344  
   345  	buf := logger.acquireBuf()
   346  	defer logger.releaseBuf(buf)
   347  	valuesParser.WriteUnparsed(buf)
   348  
   349  	entry.Level = level
   350  	entry.Message = buf.String()
   351  	logger.logEntry(entry)
   352  }
   353  
   354  // Level implements types.CompactLogger
   355  func (l *CompactLogger) Level() types.Level {
   356  	return LevelFromLogrus(l.emitter.LogrusEntry.Level)
   357  }
   358  
   359  // WithLevel implements types.CompactLogger
   360  func (l *CompactLogger) WithLevel(newLevel types.Level) adapter.CompactLogger {
   361  	clone := l.clone()
   362  	clone.emitter.LogrusEntry = newLogrusEntry(l.emitter.LogrusEntry.Logger, LevelToLogrus(newLevel))
   363  	return clone
   364  }
   365  
   366  func (l *CompactLogger) branch() *CompactLogger {
   367  	return &CompactLogger{
   368  		mostlyPersistentData: l.mostlyPersistentData,
   369  		emitter:              &Emitter{LogrusEntry: l.emitter.getLogrusEntry()},
   370  		contextFields:        l.contextFields,
   371  	}
   372  }
   373  
   374  func (l *CompactLogger) clone() *CompactLogger {
   375  	clone := l.branch()
   376  	clone.mostlyPersistentData = &[]mostlyPersistentData{*l.mostlyPersistentData}[0]
   377  	return clone
   378  }
   379  
   380  // WithPreHooks implements types.CompactLogger
   381  func (l *CompactLogger) WithPreHooks(preHooks ...types.PreHook) adapter.CompactLogger {
   382  	clone := l.clone()
   383  	clone.preHooks = make(types.PreHooks, len(l.preHooks)+len(preHooks))
   384  	copy(clone.preHooks, l.preHooks)
   385  	copy(clone.preHooks[len(l.preHooks):], preHooks)
   386  	return clone
   387  }
   388  
   389  // WithHooks implements types.CompactLogger
   390  func (l *CompactLogger) WithHooks(hooks ...types.Hook) adapter.CompactLogger {
   391  	clone := l.clone()
   392  	clone.hooks = make(types.Hooks, len(l.hooks)+len(hooks))
   393  	copy(clone.hooks, l.hooks)
   394  	copy(clone.hooks[len(l.hooks):], hooks)
   395  	return clone
   396  }
   397  
   398  // WithField implements types.CompactLogger
   399  func (l *CompactLogger) WithField(
   400  	key field.Key,
   401  	value field.Value,
   402  	props ...field.Property,
   403  ) adapter.CompactLogger {
   404  	branch := l.branch()
   405  	branch.contextFields = l.contextFields.WithField(key, value, props)
   406  	return branch
   407  }
   408  
   409  // WithFields implements types.CompactLogger
   410  func (l *CompactLogger) WithFields(fields field.AbstractFields) adapter.CompactLogger {
   411  	branch := l.branch()
   412  	branch.contextFields = l.contextFields.WithFields(fields)
   413  	return branch
   414  }
   415  
   416  // WithTraceIDs implements types.CompactLogger
   417  func (l *CompactLogger) WithTraceIDs(allTraceIDs belt.TraceIDs, newTraceIDsCount int) belt.Tool {
   418  	clone := l.clone()
   419  	clone.traceIDs = allTraceIDs
   420  	clone.contextFields = clone.contextFields.WithField(FieldNameTraceIDs, allTraceIDs)
   421  	return clone
   422  }
   423  
   424  // WithContextFields implements types.Tool
   425  func (l *CompactLogger) WithContextFields(allFields *field.FieldsChain, newFieldsCount int) belt.Tool {
   426  	branch := l.branch()
   427  	branch.contextFields = allFields
   428  	return branch
   429  }
   430  
   431  func (l *CompactLogger) compileEmitterFields() {
   432  	m := make(logrus.Fields, l.contextFields.Len())
   433  	l.contextFields.ForEachField(func(f *field.Field) bool {
   434  		m[f.Key] = f.Value
   435  		return true
   436  	})
   437  	old := l.emitter.LogrusEntry
   438  	l.emitter.setLogrusEntry(&logrus.Entry{
   439  		Logger:  old.Logger,
   440  		Data:    m,
   441  		Time:    old.Time,
   442  		Level:   old.Level,
   443  		Caller:  old.Caller,
   444  		Message: old.Message,
   445  		Context: old.Context,
   446  	})
   447  }
   448  
   449  func (l *CompactLogger) prepareEmitter() {
   450  	l.prepareEmitterOnce.Do(func() {
   451  		l.compileEmitterFields()
   452  	})
   453  }
   454  
   455  // Emitter implements types.Emitter
   456  func (l *CompactLogger) Emitter() types.Emitter {
   457  	l.prepareEmitter()
   458  	return l.emitter
   459  }
   460  
   461  func newCompactLoggerFromLogrus(logrusLogger *logrus.Logger, level logger.Level, opts ...types.Option) *CompactLogger {
   462  	cfg := types.Options(opts).Config()
   463  	return &CompactLogger{
   464  		emitter: NewEmitter(logrusLogger, level),
   465  		mostlyPersistentData: &mostlyPersistentData{
   466  			getCallerFunc: cfg.GetCallerFunc,
   467  			fmtBufPool: &sync.Pool{
   468  				New: func() any {
   469  					return &strings.Builder{}
   470  				},
   471  			},
   472  			entryPool: &sync.Pool{
   473  				New: func() any {
   474  					return &types.Entry{}
   475  				},
   476  			},
   477  		},
   478  	}
   479  }
   480  
   481  // New returns a types.Logger implementation based on a logrus Logger.
   482  //
   483  // This function takes ownership of the logger instance.
   484  func New(logger *logrus.Logger, opts ...types.Option) types.Logger {
   485  	return adapter.GenericSugar{
   486  		CompactLogger: newCompactLoggerFromLogrus(logger, LevelFromLogrus(logger.Level), opts...),
   487  	}
   488  }
   489  
   490  // DefaultLogrusLogger returns a logrus logger with the default configuration.
   491  //
   492  // The configuration might be changed in future.
   493  //
   494  // Overwritable.
   495  //
   496  // Do not override this anywhere but in the `main` package.
   497  var DefaultLogrusLogger = func() *logrus.Logger {
   498  	l := logrus.New()
   499  	l.Formatter = &logrus.TextFormatter{
   500  		CallerPrettyfier: func(caller *runtime.Frame) (string, string) {
   501  			return "", fmt.Sprintf("%s:%d", filepath.Base(caller.File), caller.Line)
   502  		},
   503  	}
   504  	return l
   505  }
   506  
   507  // Default returns a types.Logger based on logrus, using the default configuration.
   508  //
   509  // The configuration might be changed in future. Use this function if you would like
   510  // to delegate logger configuration to somebody else and you do not rely on specific
   511  // output format.
   512  func Default() types.Logger {
   513  	return New(DefaultLogrusLogger())
   514  }