github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/logger/README.md (about)

     1  # Example
     2  ```go
     3  package logger_test
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"log"
     9  
    10  	"github.com/facebookincubator/go-belt"
    11  	"github.com/facebookincubator/go-belt/tool/logger"
    12  	"github.com/facebookincubator/go-belt/tool/logger/implementation/glog"
    13  	xlogrus "github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
    14  	"github.com/facebookincubator/go-belt/tool/logger/implementation/stdlib"
    15  	"github.com/facebookincubator/go-belt/tool/logger/implementation/zap"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  func Example() {
    20  	// easy to use:
    21  	l := xlogrus.Default()
    22  	someFunction(1, l)
    23  
    24  	// implementation agnostic:
    25  	l = zap.Default()
    26  	someFunction(2, l)
    27  
    28  	// various implementations:
    29  	l = glog.New()
    30  	someFunction(3, l)
    31  
    32  	// one may still reuse all the features of the backend logger:
    33  	logrusInstance := logrus.New()
    34  	logrusInstance.Formatter = &logrus.JSONFormatter{}
    35  	l = xlogrus.New(logrusInstance)
    36  	someFunction(4, l)
    37  
    38  	// just another example:
    39  	var buf bytes.Buffer
    40  	stdLogInstance := log.New(&buf, "", log.Llongfile)
    41  	l = stdlib.New(stdLogInstance, logger.LevelDebug)
    42  	someFunction(5, l)
    43  
    44  	// use go-belt to manage the logger
    45  	obs := belt.New()
    46  	obs = logger.BeltWithLogger(obs, l)
    47  	someBeltyFunction(6, obs)
    48  
    49  	// use context to manage the logger
    50  	ctx := context.Background()
    51  	ctx = logger.CtxWithLogger(ctx, l)
    52  	someContextyFunction(ctx, 7)
    53  
    54  	// use a singletony Logger:
    55  	logger.Default = func() logger.Logger {
    56  		return l
    57  	}
    58  	oneMoreFunction(8)
    59  }
    60  
    61  func someFunction(arg int, l logger.Logger) {
    62  	// experience close to logrus/zap:
    63  	l = l.WithField("arg", arg)
    64  	anotherFunction(l)
    65  }
    66  
    67  func anotherFunction(l logger.Logger) {
    68  	l.Debugf("hello world, %T!", l)
    69  }
    70  
    71  func someBeltyFunction(arg int, obs *belt.Belt) {
    72  	obs = obs.WithField("arg", arg)
    73  	anotherBeltyFunction(obs)
    74  }
    75  
    76  func anotherBeltyFunction(obs *belt.Belt) {
    77  	logger.FromBelt(obs).Debugf("hello world!")
    78  }
    79  
    80  func someContextyFunction(ctx context.Context, arg int) {
    81  	ctx = belt.WithField(ctx, "arg", arg)
    82  	anotherContextyFunction(ctx)
    83  }
    84  
    85  func anotherContextyFunction(ctx context.Context) {
    86  	logger.FromCtx(ctx).Debugf("hello world!")
    87  	// or a shorter form:
    88  	logger.Debugf(ctx, "hello world!")
    89  }
    90  
    91  func oneMoreFunction(arg int) {
    92  	logger.Default().WithField("arg", arg).Debugf("hello world!")
    93  }
    94  ```
    95  
    96  # Interface
    97  ```go
    98  type Logger interface {
    99  	// Logf logs an unstructured message. Though, of course, all
   100  	// contextual structured fields will also be logged.
   101  	//
   102  	// This method exists mostly for convenience, for people who
   103  	// has not got used to proper structured logging, yet.
   104  	// See `LogFields` and `Log`. If one have variables they want to
   105  	// log, it is better for scalable observability to log them
   106  	// as structured values, instead of injecting them into a
   107  	// non-structured string.
   108  	Logf(level Level, format string, args ...any)
   109  
   110  	// LogFields logs structured fields with a explanation message.
   111  	//
   112  	// Anything that implements field.AbstractFields might be used
   113  	// as a collection of fields to be logged.
   114  	//
   115  	// Examples:
   116  	//
   117  	// 	l.LogFields(logger.LevelDebug, "new_request", field.Fields{{Key: "user_id", Value: userID}, {Key: "group_id", Value: groupID}})
   118  	// 	l.LogFields(logger.LevelInfo, "affected entries", field.Field{Key: "mysql_affected", Value: affectedRows})
   119  	// 	l.LogFields(logger.LevelError, "unable to fetch user info", request) // where `request` implements field.AbstractFields
   120  	//
   121  	// Sometimes it is inconvenient to manually describe each field,
   122  	// and for such cases see method `Log`.
   123  	LogFields(level Level, message string, fields field.AbstractFields)
   124  
   125  	// Log extracts structured fields from provided values, joins
   126  	// the rest into an unstructured message and logs the result.
   127  	//
   128  	// This function provides convenience (relatively to LogFields)
   129  	// at cost of a bit of performance.
   130  	//
   131  	// There are few ways to extract structured fields, which are
   132  	// applied for each value from `values` (in descending priority order):
   133  	// 1. If a `value` is an `*Entry` then the Entry is used (with its fields)
   134  	// 2. If a `value` implements field.AbstractFields then ForEachField method
   135  	//    is used (so it is become similar to LogFields).
   136  	// 3. If a `value` is a structure (or a pointer to a structure) then
   137  	//    fields of the structure are interpreted as structured fields
   138  	//    to be logged (see explanation below).
   139  	// 4. If a `value` is a map then fields a constructed out of this map.
   140  	//
   141  	// Everything that does not fit into any of the rules above is just
   142  	// joined into an nonstructured message (and works the same way
   143  	// as `message` in LogFields).
   144  	//
   145  	// How structures are parsed:
   146  	// Structures are parsed recursively. Each field name of the path in a tree
   147  	// of structures is added to the resulting field name (for example int "struct{A struct{B int}}"
   148  	// the field name will be `A.B`).
   149  	// To enforce another name use tag `log` (for example "struct{A int `log:"anotherName"`}"),
   150  	// to prevent a field from logging use tag `log:"-"`.
   151  	//
   152  	// Examples:
   153  	//
   154  	// 	user, err := getUser()
   155  	// 	if err != nil {
   156  	// 		l.Log(logger.LevelError, err)
   157  	// 		return err
   158  	// 	}
   159  	// 	l.Log(logger.LevelDebug, "current user", user) // fields of structure "user" will be logged
   160  	// 	l.Log(logger.LevelDebug, map[string]any{"user_id": user.ID, "group_id", user.GroupID})
   161  	// 	l.Log(logger.LevelDebug, field.Fields{{Key: "user_id", Value: user.ID}, {Key: "group_id", Value: user.GroupID}})
   162  	// 	l.Log(logger.LevelDebug, "current user ID is ", user.ID, " and group ID is ", user.GroupID) // will result into message "current user ID is 1234 and group ID is 5678".
   163  	Log(level Level, values ...any)
   164  
   165  	// Emitter returns the Emitter (see the description of interface "Emitter").
   166  	Emitter() Emitter
   167  
   168  	// Level returns the current logging level (see description of "Level").
   169  	Level() Level
   170  
   171  	// TraceFields is just a shorthand for LogFields(logger.LevelTrace, ...)
   172  	TraceFields(message string, fields field.AbstractFields)
   173  
   174  	// DebugFields is just a shorthand for LogFields(logger.LevelDebug, ...)
   175  	DebugFields(message string, fields field.AbstractFields)
   176  
   177  	// InfoFields is just a shorthand for LogFields(logger.LevelInfo, ...)
   178  	InfoFields(message string, fields field.AbstractFields)
   179  
   180  	// WarnFields is just a shorthand for LogFields(logger.LevelWarn, ...)
   181  	WarnFields(message string, fields field.AbstractFields)
   182  
   183  	// ErrorFields is just a shorthand for LogFields(logger.LevelError, ...)
   184  	ErrorFields(message string, fields field.AbstractFields)
   185  
   186  	// PanicFields is just a shorthand for LogFields(logger.LevelPanic, ...)
   187  	//
   188  	// Be aware: Panic level also triggers a `panic`.
   189  	PanicFields(message string, fields field.AbstractFields)
   190  
   191  	// FatalFields is just a shorthand for LogFields(logger.LevelFatal, ...)
   192  	//
   193  	// Be aware: Panic level also triggers an `os.Exit`.
   194  	FatalFields(message string, fields field.AbstractFields)
   195  
   196  	// Trace is just a shorthand for Log(logger.LevelTrace, ...)
   197  	Trace(values ...any)
   198  
   199  	// Debug is just a shorthand for Log(logger.LevelDebug, ...)
   200  	Debug(values ...any)
   201  
   202  	// Info is just a shorthand for Log(logger.LevelInfo, ...)
   203  	Info(values ...any)
   204  
   205  	// Warn is just a shorthand for Log(logger.LevelWarn, ...)
   206  	Warn(values ...any)
   207  
   208  	// Error is just a shorthand for Log(logger.LevelError, ...)
   209  	Error(values ...any)
   210  
   211  	// Panic is just a shorthand for Log(logger.LevelPanic, ...)
   212  	//
   213  	// Be aware: Panic level also triggers a `panic`.
   214  	Panic(values ...any)
   215  
   216  	// Fatal is just a shorthand for Log(logger.LevelFatal, ...)
   217  	//
   218  	// Be aware: Fatal level also triggers an `os.Exit`.
   219  	Fatal(values ...any)
   220  
   221  	// Tracef is just a shorthand for Logf(logger.LevelTrace, ...)
   222  	Tracef(format string, args ...any)
   223  
   224  	// Debugf is just a shorthand for Logf(logger.LevelDebug, ...)
   225  	Debugf(format string, args ...any)
   226  
   227  	// Infof is just a shorthand for Logf(logger.LevelInfo, ...)
   228  	Infof(format string, args ...any)
   229  
   230  	// Warnf is just a shorthand for Logf(logger.LevelWarn, ...)
   231  	Warnf(format string, args ...any)
   232  
   233  	// Errorf is just a shorthand for Logf(logger.LevelError, ...)
   234  	Errorf(format string, args ...any)
   235  
   236  	// Panicf is just a shorthand for Logf(logger.LevelPanic, ...)
   237  	//
   238  	// Be aware: Panic level also triggers a `panic`.
   239  	Panicf(format string, args ...any)
   240  
   241  	// Fatalf is just a shorthand for Logf(logger.LevelFatal, ...)
   242  	//
   243  	// Be aware: Fatal level also triggers an `os.Exit`.
   244  	Fatalf(format string, args ...any)
   245  
   246  	// WithLevel returns a logger with logger level set to the given argument.
   247  	//
   248  	// See also the description of type "Level".
   249  	WithLevel(Level) Logger
   250  
   251  	// WithPreHooks returns a Logger which includes/appends pre-hooks from the arguments.
   252  	//
   253  	// See also description of "PreHook".
   254  	//
   255  	// Special case: to reset hooks use `WithHooks()` (without any arguments).
   256  	WithPreHooks(...PreHook) Logger
   257  
   258  	// WithHooks returns a Logger which includes/appends hooks from the arguments.
   259  	//
   260  	// See also description of "Hook".
   261  	//
   262  	// Special case: to reset hooks use `WithHooks()` (without any arguments).
   263  	WithHooks(...Hook) Logger
   264  
   265  	// WithField returns the logger with the added field (used for structured logging).
   266  	WithField(key string, value any, props ...field.Property) Logger
   267  
   268  	// WithMoreFields returns the logger with the added fields (used for structured logging)
   269  	WithMoreFields(fields field.AbstractFields) Logger
   270  
   271  	// WithMessagePrefix adds a string to all messages logged through the derived logger.
   272  	WithMessagePrefix(prefix string) Logger
   273  
   274  	// WithContextFields sets new context-defined fields. Supposed to be called
   275  	// only by an Belt.
   276  	//
   277  	// allFields contains all fields as a chain of additions in a reverse-chronological order,
   278  	// while newFieldsCount tells about how much of the fields are new (since last
   279  	// call of WithContextFields). Thus if one will call
   280  	// field.Slice(allFields, 0, newFieldsCount) they will get only the new fields.
   281  	// At the same time some Tool-s may prefer just to re-set all the fields instead of adding
   282  	// only new fields (due to performance reasons) and they may just use `allFields`.
   283  	WithContextFields(allFields *field.FieldsChain, newFieldsCount int) Tool
   284  
   285  	// WithTraceIDs sets new context-defined TraceIDs. Supposed to be called
   286  	// only by an Belt.
   287  	//
   288  	// traceIDs and newTraceIDsCount has similar properties as allFields and newFieldsCount
   289  	// in the WithContextFields method.
   290  	WithTraceIDs(traceIDs TraceIDs, newTraceIDsCount int) Tool
   291  
   292  	// Flush forces to flush all buffers.
   293  	Flush()
   294  }
   295  ```
   296  
   297  # Implementations
   298  
   299  These implementations are provided out of the box:
   300  
   301  * [`zap`](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/zap) -- is based on Uber's [`zap`](https://github.com/uber-go/zap).
   302  * [`logrus`](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/logrus) -- is based on [`github.com/sirupsen/logrus`](https://github.com/sirupsen/logrus).
   303  * [`glog`](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/glog) -- is based on Google's [`glog`](github.com/golang/glog).
   304  * [`stdlib`](https://pkg.go.dev/github.com/facebookincubator/go-belt/tool/logger/implementation/glog) -- is based on standard Go's [`log`](https://pkg.go.dev/log) package.
   305  
   306  # Custom implementation
   307  
   308  Depending on how many features your logger is ready to provide it should implement one of:
   309  * `interface{ Printf(format string, args ...any)` or just be of type `func(string, ...any)`
   310  * [`Emitter`](https://github.com/facebookincubator/go-belt/blob/a80187cd561e4c30237aff5fccd46f06981d41e2/tool/logger/types/logger.go#L225)
   311  * [`CompactLogger`](https://github.com/facebookincubator/go-belt/blob/a80187cd561e4c30237aff5fccd46f06981d41e2/tool/logger/adapter/compact_logger.go#L17)
   312  * [`Logger`](https://github.com/facebookincubator/go-belt/blob/a80187cd561e4c30237aff5fccd46f06981d41e2/tool/logger/types/logger.go#L25)
   313  
   314  
   315  And then you may call `adapter.LoggerFromAny` and it will convert your logger to `Logger` by adding everything what is missing in a naive generic way.
   316  
   317  So the easiest implementation of a logger might be for example:
   318  ```go
   319  import (
   320  	"github.com/facebookincubator/go-belt/tool/logger"
   321  	"github.com/facebookincubator/go-belt/tool/logger/adapter"
   322  )
   323  
   324  func getLogger() logger.Logger {
   325  	return adapter.LoggerFromAny(func(format string, args ...any) {
   326  		fmt.Printf("[log]"+format+"\n", args...)
   327  	})
   328  }
   329  ```
   330  
   331  If a more complicated logger is required then take a look at existing [implementations](https://github.com/facebookincubator/go-belt/tree/main/tool/logger/implementation).
   332