Codebase list golang-github-go-kit-kit / b58baec
Merge pull request #481 from go-kit/unexport-log-context log: Unexport Context Chris Hines authored 7 years ago GitHub committed 7 years ago
27 changed file(s) with 228 addition(s) and 258 deletion(s). Raw diff Collapse all Expand all
0 package main
0 package main
11
22 import (
33 "context"
5050 var logger log.Logger
5151 {
5252 logger = log.NewLogfmtLogger(os.Stdout)
53 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
54 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
53 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
54 logger = log.With(logger, "caller", log.DefaultCaller)
5555 }
5656 logger.Log("msg", "hello")
5757 defer logger.Log("msg", "goodbye")
8585 var tracer stdopentracing.Tracer
8686 {
8787 if *zipkinAddr != "" {
88 logger := log.NewContext(logger).With("tracer", "ZipkinHTTP")
88 logger := log.With(logger, "tracer", "ZipkinHTTP")
8989 logger.Log("addr", *zipkinAddr)
9090
9191 // endpoint typically looks like: http://zipkinhost:9411/api/v1/spans
104104 os.Exit(1)
105105 }
106106 } else if *zipkinKafkaAddr != "" {
107 logger := log.NewContext(logger).With("tracer", "ZipkinKafka")
107 logger := log.With(logger, "tracer", "ZipkinKafka")
108108 logger.Log("addr", *zipkinKafkaAddr)
109109
110110 collector, err := zipkin.NewKafkaCollector(
125125 os.Exit(1)
126126 }
127127 } else if *appdashAddr != "" {
128 logger := log.NewContext(logger).With("tracer", "Appdash")
128 logger := log.With(logger, "tracer", "Appdash")
129129 logger.Log("addr", *appdashAddr)
130130 tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr))
131131 } else if *lightstepToken != "" {
132 logger := log.NewContext(logger).With("tracer", "LightStep")
132 logger := log.With(logger, "tracer", "LightStep")
133133 logger.Log() // probably don't want to print out the token :)
134134 tracer = lightstep.NewTracer(lightstep.Options{
135135 AccessToken: *lightstepToken,
136136 })
137137 defer lightstep.FlushLightStepTracer(tracer)
138138 } else {
139 logger := log.NewContext(logger).With("tracer", "none")
139 logger := log.With(logger, "tracer", "none")
140140 logger.Log()
141141 tracer = stdopentracing.GlobalTracer() // no-op
142142 }
154154 var sumEndpoint endpoint.Endpoint
155155 {
156156 sumDuration := duration.With("method", "Sum")
157 sumLogger := log.NewContext(logger).With("method", "Sum")
157 sumLogger := log.With(logger, "method", "Sum")
158158
159159 sumEndpoint = addsvc.MakeSumEndpoint(service)
160160 sumEndpoint = opentracing.TraceServer(tracer, "Sum")(sumEndpoint)
164164 var concatEndpoint endpoint.Endpoint
165165 {
166166 concatDuration := duration.With("method", "Concat")
167 concatLogger := log.NewContext(logger).With("method", "Concat")
167 concatLogger := log.With(logger, "method", "Concat")
168168
169169 concatEndpoint = addsvc.MakeConcatEndpoint(service)
170170 concatEndpoint = opentracing.TraceServer(tracer, "Concat")(concatEndpoint)
189189
190190 // Debug listener.
191191 go func() {
192 logger := log.NewContext(logger).With("transport", "debug")
192 logger := log.With(logger, "transport", "debug")
193193
194194 m := http.NewServeMux()
195195 m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
205205
206206 // HTTP transport.
207207 go func() {
208 logger := log.NewContext(logger).With("transport", "HTTP")
208 logger := log.With(logger, "transport", "HTTP")
209209 h := addsvc.MakeHTTPHandler(endpoints, tracer, logger)
210210 logger.Log("addr", *httpAddr)
211211 errc <- http.ListenAndServe(*httpAddr, h)
213213
214214 // gRPC transport.
215215 go func() {
216 logger := log.NewContext(logger).With("transport", "gRPC")
216 logger := log.With(logger, "transport", "gRPC")
217217
218218 ln, err := net.Listen("tcp", *grpcAddr)
219219 if err != nil {
231231
232232 // Thrift transport.
233233 go func() {
234 logger := log.NewContext(logger).With("transport", "Thrift")
234 logger := log.With(logger, "transport", "Thrift")
235235
236236 var protocolFactory thrift.TProtocolFactory
237237 switch *thriftProtocol {
4343 var logger log.Logger
4444 {
4545 logger = log.NewLogfmtLogger(os.Stderr)
46 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
47 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
46 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
47 logger = log.With(logger, "caller", log.DefaultCaller)
4848 }
4949
5050 // Service discovery domain. In this example we use Consul.
0 package main
0 package main
11
22 import (
33 "flag"
2020 var logger log.Logger
2121 {
2222 logger = log.NewLogfmtLogger(os.Stderr)
23 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
24 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
23 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
24 logger = log.With(logger, "caller", log.DefaultCaller)
2525 }
2626
2727 var s profilesvc.Service
3232
3333 var h http.Handler
3434 {
35 h = profilesvc.MakeHTTPHandler(s, log.NewContext(logger).With("component", "HTTP"))
35 h = profilesvc.MakeHTTPHandler(s, log.With(logger, "component", "HTTP"))
3636 }
3737
3838 errs := make(chan error)
4646 var logger log.Logger
4747 logger = log.NewLogfmtLogger(os.Stderr)
4848 logger = &serializedLogger{Logger: logger}
49 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
49 logger = log.With(logger, "ts", log.DefaultTimestampUTC)
5050
5151 var (
5252 cargos = inmem.NewCargoRepository()
7777
7878 var bs booking.Service
7979 bs = booking.NewService(cargos, locations, handlingEvents, rs)
80 bs = booking.NewLoggingService(log.NewContext(logger).With("component", "booking"), bs)
80 bs = booking.NewLoggingService(log.With(logger, "component", "booking"), bs)
8181 bs = booking.NewInstrumentingService(
8282 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
8383 Namespace: "api",
9696
9797 var ts tracking.Service
9898 ts = tracking.NewService(cargos, handlingEvents)
99 ts = tracking.NewLoggingService(log.NewContext(logger).With("component", "tracking"), ts)
99 ts = tracking.NewLoggingService(log.With(logger, "component", "tracking"), ts)
100100 ts = tracking.NewInstrumentingService(
101101 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
102102 Namespace: "api",
115115
116116 var hs handling.Service
117117 hs = handling.NewService(handlingEvents, handlingEventFactory, handlingEventHandler)
118 hs = handling.NewLoggingService(log.NewContext(logger).With("component", "handling"), hs)
118 hs = handling.NewLoggingService(log.With(logger, "component", "handling"), hs)
119119 hs = handling.NewInstrumentingService(
120120 kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
121121 Namespace: "api",
132132 hs,
133133 )
134134
135 httpLogger := log.NewContext(logger).With("component", "http")
135 httpLogger := log.With(logger, "component", "http")
136136
137137 mux := http.NewServeMux()
138138
2121
2222 var logger log.Logger
2323 logger = log.NewLogfmtLogger(os.Stderr)
24 logger = log.NewContext(logger).With("listen", *listen).With("caller", log.DefaultCaller)
24 logger = log.With(logger, "listen", *listen, "caller", log.DefaultCaller)
2525
2626 fieldKeys := []string{"method", "error"}
2727 requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
00 # package log
11
22 `package log` provides a minimal interface for structured logging in services.
3 It may be wrapped to encode conventions, enforce type-safety, provide leveled logging, and so on.
4 It can be used for both typical application log events, and log-structured data streams.
3 It may be wrapped to encode conventions, enforce type-safety, provide leveled
4 logging, and so on. It can be used for both typical application log events,
5 and log-structured data streams.
56
67 ## Structured logging
78
8 Structured logging is, basically, conceding to the reality that logs are _data_,
9 and warrant some level of schematic rigor.
10 Using a stricter, key/value-oriented message format for our logs,
11 containing contextual and semantic information,
12 makes it much easier to get insight into the operational activity of the systems we build.
13 Consequently, `package log` is of the strong belief that
14 "[the benefits of structured logging outweigh the minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
9 Structured logging is, basically, conceding to the reality that logs are
10 _data_, and warrant some level of schematic rigor. Using a stricter,
11 key/value-oriented message format for our logs, containing contextual and
12 semantic information, makes it much easier to get insight into the
13 operational activity of the systems we build. Consequently, `package log` is
14 of the strong belief that "[the benefits of structured logging outweigh the
15 minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
1516
16 Migrating from unstructured to structured logging is probably a lot easier than you'd expect.
17 Migrating from unstructured to structured logging is probably a lot easier
18 than you'd expect.
1719
1820 ```go
1921 // Unstructured
3638 // question="what is the meaning of life?" answer=42
3739 ```
3840
39 ### Log contexts
41 ### Contextual Loggers
4042
4143 ```go
4244 func main() {
4345 var logger log.Logger
4446 logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
45 logger = log.NewContext(logger).With("instance_id", 123)
47 logger = log.With(logger, "instance_id", 123)
4648
4749 logger.Log("msg", "starting")
48 NewWorker(log.NewContext(logger).With("component", "worker")).Run()
49 NewSlacker(log.NewContext(logger).With("component", "slacker")).Run()
50 NewWorker(log.With(logger, "component", "worker")).Run()
51 NewSlacker(log.With(logger, "component", "slacker")).Run()
5052 }
5153
5254 // Output:
7678 // {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
7779 ```
7880
79 Or, if, for legacy reasons,
80 you need to pipe all of your logging through the stdlib log package,
81 you can redirect Go kit logger to the stdlib logger.
81 Or, if, for legacy reasons, you need to pipe all of your logging through the
82 stdlib log package, you can redirect Go kit logger to the stdlib logger.
8283
8384 ```go
8485 logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
9394 ```go
9495 var logger log.Logger
9596 logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
96 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
97 logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
9798
9899 logger.Log("msg", "hello")
99100
103104
104105 ## Supported output formats
105106
106 - [Logfmt](https://brandur.org/logfmt)
107 - [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
107108 - JSON
108109
109110 ## Enhancements
116117 }
117118 ```
118119
119 This interface, and its supporting code like [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
120 is the product of much iteration and evaluation.
121 For more details on the evolution of the Logger interface,
122 see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
123 a talk by [Chris Hines](https://github.com/ChrisHines).
120 This interface, and its supporting code like is the product of much iteration
121 and evaluation. For more details on the evolution of the Logger interface,
122 see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
123 a talk by [Chris Hines](https://github.com/ChrisHines).
124124 Also, please see
125 [#63](https://github.com/go-kit/kit/issues/63),
126 [#76](https://github.com/go-kit/kit/pull/76),
127 [#131](https://github.com/go-kit/kit/issues/131),
128 [#157](https://github.com/go-kit/kit/pull/157),
129 [#164](https://github.com/go-kit/kit/issues/164), and
130 [#252](https://github.com/go-kit/kit/pull/252)
131 to review historical conversations about package log and the Logger interface.
125 [#63](https://github.com/go-kit/kit/issues/63),
126 [#76](https://github.com/go-kit/kit/pull/76),
127 [#131](https://github.com/go-kit/kit/issues/131),
128 [#157](https://github.com/go-kit/kit/pull/157),
129 [#164](https://github.com/go-kit/kit/issues/164), and
130 [#252](https://github.com/go-kit/kit/pull/252)
131 to review historical conversations about package log and the Logger interface.
132132
133133 Value-add packages and suggestions,
134 like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/levels),
135 are of course welcome.
136 Good proposals should
134 like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level),
135 are of course welcome. Good proposals should
137136
138 - Be composable with [log.Context](https://godoc.org/github.com/go-kit/kit/log#Context),
139 - Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped context, and
137 - Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With),
138 - Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and
140139 - Be friendly to packages that accept only an unadorned log.Logger.
141140
142141 ## Benchmarks & comparisons
66 )
77
88 func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
9 lc := log.NewContext(logger).With("common_key", "common_value")
9 lc := log.With(logger, "common_key", "common_value")
1010 b.ReportAllocs()
1111 b.ResetTimer()
1212 for i := 0; i < b.N; i++ {
1616
1717 var (
1818 baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
19 withMessage = func(logger log.Logger) { log.NewContext(logger).With("a", "b").Log("c", "d") }
19 withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") }
2020 )
66 // want a different set of levels, you can create your own levels type very
77 // easily, and you can elide the configuration.
88 type Levels struct {
9 ctx *log.Context
9 logger log.Logger
1010 levelKey string
1111
1212 // We have a choice between storing level values in string fields or
3333 // New creates a new leveled logger, wrapping the passed logger.
3434 func New(logger log.Logger, options ...Option) Levels {
3535 l := Levels{
36 ctx: log.NewContext(logger),
36 logger: logger,
3737 levelKey: "level",
3838
3939 debugValue: "debug",
5151 // With returns a new leveled logger that includes keyvals in all log events.
5252 func (l Levels) With(keyvals ...interface{}) Levels {
5353 return Levels{
54 ctx: l.ctx.With(keyvals...),
54 logger: log.With(l.logger, keyvals...),
5555 levelKey: l.levelKey,
5656 debugValue: l.debugValue,
5757 infoValue: l.infoValue,
6363
6464 // Debug returns a debug level logger.
6565 func (l Levels) Debug() log.Logger {
66 return l.ctx.WithPrefix(l.levelKey, l.debugValue)
66 return log.WithPrefix(l.logger, l.levelKey, l.debugValue)
6767 }
6868
6969 // Info returns an info level logger.
7070 func (l Levels) Info() log.Logger {
71 return l.ctx.WithPrefix(l.levelKey, l.infoValue)
71 return log.WithPrefix(l.logger, l.levelKey, l.infoValue)
7272 }
7373
7474 // Warn returns a warning level logger.
7575 func (l Levels) Warn() log.Logger {
76 return l.ctx.WithPrefix(l.levelKey, l.warnValue)
76 return log.WithPrefix(l.logger, l.levelKey, l.warnValue)
7777 }
7878
7979 // Error returns an error level logger.
8080 func (l Levels) Error() log.Logger {
81 return l.ctx.WithPrefix(l.levelKey, l.errorValue)
81 return log.WithPrefix(l.logger, l.levelKey, l.errorValue)
8282 }
8383
8484 // Crit returns a critical level logger.
8585 func (l Levels) Crit() log.Logger {
86 return l.ctx.WithPrefix(l.levelKey, l.critValue)
86 return log.WithPrefix(l.logger, l.levelKey, l.critValue)
8787 }
8888
8989 // Option sets a parameter for leveled loggers.
3434 // idea to log simple values without formatting them. This practice allows
3535 // the chosen logger to encode values in the most appropriate way.
3636 //
37 // Log Context
37 // Contextual Loggers
3838 //
39 // A log context stores keyvals that it includes in all log events. Building
40 // appropriate log contexts reduces repetition and aids consistency in the
41 // resulting log output. We can use a context to improve the RunTask example.
39 // A contextual logger stores keyvals that it includes in all log events.
40 // Building appropriate contextual loggers reduces repetition and aids
41 // consistency in the resulting log output. With and WithPrefix add context to
42 // a logger. We can use With to improve the RunTask example.
4243 //
4344 // func RunTask(task Task, logger log.Logger) string {
44 // logger = log.NewContext(logger).With("taskID", task.ID)
45 // logger = log.With(logger, "taskID", task.ID)
4546 // logger.Log("event", "starting task")
4647 // ...
4748 // taskHelper(task.Cmd, logger)
5051 // }
5152 //
5253 // The improved version emits the same log events as the original for the
53 // first and last calls to Log. The call to taskHelper highlights that a
54 // context may be passed as a logger to other functions. Each log event
55 // created by the called function will include the task.ID even though the
56 // function does not have access to that value. Using log contexts this way
57 // simplifies producing log output that enables tracing the life cycle of
58 // individual tasks. (See the Context example for the full code of the
59 // above snippet.)
54 // first and last calls to Log. Passing the contextual logger to taskHelper
55 // enables each log event created by taskHelper to include the task.ID even
56 // though taskHelper does not have access to that value. Using contextual
57 // loggers this way simplifies producing log output that enables tracing the
58 // life cycle of individual tasks. (See the Contextual example for the full
59 // code of the above snippet.)
6060 //
61 // Dynamic Context Values
61 // Dynamic Contextual Values
6262 //
63 // A Valuer function stored in a log context generates a new value each time
64 // the context logs an event. The Valuer example demonstrates how this
65 // feature works.
63 // A Valuer function stored in a contextual logger generates a new value each
64 // time an event is logged. The Valuer example demonstrates how this feature
65 // works.
6666 //
6767 // Valuers provide the basis for consistently logging timestamps and source
6868 // code location. The log package defines several valuers for that purpose.
7171 // entries contain a timestamp and source location looks like this:
7272 //
7373 // logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
74 // logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
74 // logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
7575 //
7676 // Concurrent Safety
7777 //
2828 // taskID=1 event="task complete"
2929 }
3030
31 func Example_context() {
31 func Example_contextual() {
3232 logger := log.NewLogfmtLogger(os.Stdout)
3333
3434 type Task struct {
4242 }
4343
4444 RunTask := func(task Task, logger log.Logger) {
45 logger = log.NewContext(logger).With("taskID", task.ID)
45 logger = log.With(logger, "taskID", task.ID)
4646 logger.Log("event", "starting task")
4747
4848 taskHelper(task.Cmd, logger)
6767 return count
6868 }
6969
70 logger = log.NewContext(logger).With("count", log.Valuer(counter))
70 logger = log.With(logger, "count", log.Valuer(counter))
7171
7272 logger.Log("call", "first")
7373 logger.Log("call", "second")
8787 return baseTime
8888 }
8989
90 logger = log.NewContext(logger).With("time", log.Timestamp(mockTime), "caller", log.DefaultCaller)
90 logger = log.With(logger, "time", log.Timestamp(mockTime), "caller", log.DefaultCaller)
9191
9292 logger.Log("call", "first")
9393 logger.Log("call", "second")
1212 t.Parallel()
1313 buf := &bytes.Buffer{}
1414 logger := log.NewJSONLogger(buf)
15 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
15 logger = log.With(logger, "caller", log.DefaultCaller)
1616
1717 if err := logger.Log(); err != nil {
1818 t.Fatal(err)
1616 return l
1717 }},
1818 {"TimeContext", func(l log.Logger) log.Logger {
19 return log.NewContext(l).With("time", log.DefaultTimestampUTC)
19 return log.With(l, "time", log.DefaultTimestampUTC)
2020 }},
2121 {"CallerContext", func(l log.Logger) log.Logger {
22 return log.NewContext(l).With("caller", log.DefaultCaller)
22 return log.With(l, "caller", log.DefaultCaller)
2323 }},
2424 {"TimeCallerReqIDContext", func(l log.Logger) log.Logger {
25 return log.NewContext(l).With("time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
25 return log.With(l, "time", log.DefaultTimestampUTC, "caller", log.DefaultCaller, "reqID", 29)
2626 }},
2727 }
2828
1111 // setup logger with level filter
1212 logger := log.NewLogfmtLogger(os.Stdout)
1313 logger = level.NewFilter(logger, level.AllowInfo())
14 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
14 logger = log.With(logger, "caller", log.DefaultCaller)
1515
1616 // use level helpers to log at different levels
1717 level.Error(logger).Log("err", errors.New("bad data"))
33
44 // Error returns a logger that includes a Key/ErrorValue pair.
55 func Error(logger log.Logger) log.Logger {
6 return log.NewContext(logger).WithPrefix(Key(), ErrorValue())
6 return log.WithPrefix(logger, Key(), ErrorValue())
77 }
88
99 // Warn returns a logger that includes a Key/WarnValue pair.
1010 func Warn(logger log.Logger) log.Logger {
11 return log.NewContext(logger).WithPrefix(Key(), WarnValue())
11 return log.WithPrefix(logger, Key(), WarnValue())
1212 }
1313
1414 // Info returns a logger that includes a Key/InfoValue pair.
1515 func Info(logger log.Logger) log.Logger {
16 return log.NewContext(logger).WithPrefix(Key(), InfoValue())
16 return log.WithPrefix(logger, Key(), InfoValue())
1717 }
1818
1919 // Debug returns a logger that includes a Key/DebugValue pair.
2020 func Debug(logger log.Logger) log.Logger {
21 return log.NewContext(logger).WithPrefix(Key(), DebugValue())
21 return log.WithPrefix(logger, Key(), DebugValue())
2222 }
2323
2424 // NewFilter wraps next and implements level filtering. See the commentary on
143143 var logger log.Logger
144144 logger = log.NewLogfmtLogger(&buf)
145145 logger = level.NewFilter(logger, level.AllowAll())
146 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
146 logger = log.With(logger, "caller", log.DefaultCaller)
147147
148148 level.Info(logger).Log("foo", "bar")
149149 if want, have := `level=info caller=level_test.go:149 foo=bar`, strings.TrimSpace(buf.String()); want != have {
158158 // to specify a higher callstack depth value.
159159 var logger log.Logger
160160 logger = log.NewLogfmtLogger(&buf)
161 logger = log.NewContext(logger).With("caller", log.Caller(5))
161 logger = log.With(logger, "caller", log.Caller(5))
162162 logger = level.NewFilter(logger, level.AllowAll())
163163
164164 level.Info(logger).Log("foo", "bar")
55 // log event from keyvals, a variadic sequence of alternating keys and values.
66 // Implementations must be safe for concurrent use by multiple goroutines. In
77 // particular, any implementation of Logger that appends to keyvals or
8 // modifies any of its elements must make a copy first.
8 // modifies or retains any of its elements must make a copy first.
99 type Logger interface {
1010 Log(keyvals ...interface{}) error
1111 }
1414 // the missing value.
1515 var ErrMissingValue = errors.New("(MISSING)")
1616
17 // NewContext returns a new Context that logs to logger.
18 func NewContext(logger Logger) *Context {
19 if c, ok := logger.(*Context); ok {
20 return c
17 // With returns a new contextual logger with keyvals prepended to those passed
18 // to calls to Log. If logger is also a contextual logger created by With or
19 // WithPrefix, keyvals is appended to the existing context.
20 //
21 // The returned Logger replaces all value elements (odd indexes) containing a
22 // Valuer with their generated value for each call to its Log method.
23 func With(logger Logger, keyvals ...interface{}) Logger {
24 if len(keyvals) == 0 {
25 return logger
2126 }
22 return &Context{logger: logger}
27 l := newContext(logger)
28 kvs := append(l.keyvals, keyvals...)
29 if len(kvs)%2 != 0 {
30 kvs = append(kvs, ErrMissingValue)
31 }
32 return &context{
33 logger: l.logger,
34 // Limiting the capacity of the stored keyvals ensures that a new
35 // backing array is created if the slice must grow in Log or With.
36 // Using the extra capacity without copying risks a data race that
37 // would violate the Logger interface contract.
38 keyvals: kvs[:len(kvs):len(kvs)],
39 hasValuer: l.hasValuer || containsValuer(keyvals),
40 }
2341 }
2442
25 // Context must always have the same number of stack frames between calls to
43 // WithPrefix returns a new contextual logger with keyvals prepended to those
44 // passed to calls to Log. If logger is also a contextual logger created by
45 // With or WithPrefix, keyvals is prepended to the existing context.
46 //
47 // The returned Logger replaces all value elements (odd indexes) containing a
48 // Valuer with their generated value for each call to its Log method.
49 func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
50 if len(keyvals) == 0 {
51 return logger
52 }
53 l := newContext(logger)
54 // Limiting the capacity of the stored keyvals ensures that a new
55 // backing array is created if the slice must grow in Log or With.
56 // Using the extra capacity without copying risks a data race that
57 // would violate the Logger interface contract.
58 n := len(l.keyvals) + len(keyvals)
59 if len(keyvals)%2 != 0 {
60 n++
61 }
62 kvs := make([]interface{}, 0, n)
63 kvs = append(kvs, keyvals...)
64 if len(kvs)%2 != 0 {
65 kvs = append(kvs, ErrMissingValue)
66 }
67 kvs = append(kvs, l.keyvals...)
68 return &context{
69 logger: l.logger,
70 keyvals: kvs,
71 hasValuer: l.hasValuer || containsValuer(keyvals),
72 }
73 }
74
75 // context is the Logger implementation returned by With and WithPrefix. It
76 // wraps a Logger and holds keyvals that it includes in all log events. Its
77 // Log method calls bindValues to generate values for each Valuer in the
78 // context keyvals.
79 //
80 // A context must always have the same number of stack frames between calls to
2681 // its Log method and the eventual binding of Valuers to their value. This
2782 // requirement comes from the functional requirement to allow a context to
28 // resolve application call site information for a log.Caller stored in the
83 // resolve application call site information for a Caller stored in the
2984 // context. To do this we must be able to predict the number of logging
3085 // functions on the stack when bindValues is called.
3186 //
32 // Three implementation details provide the needed stack depth consistency.
33 // The first two of these details also result in better amortized performance,
34 // and thus make sense even without the requirements regarding stack depth.
35 // The third detail, however, is subtle and tied to the implementation of the
36 // Go compiler.
87 // Two implementation details provide the needed stack depth consistency.
3788 //
38 // 1. NewContext avoids introducing an additional layer when asked to
39 // wrap another Context.
40 // 2. With avoids introducing an additional layer by returning a newly
41 // constructed Context with a merged keyvals rather than simply
42 // wrapping the existing Context.
43 // 3. All of Context's methods take pointer receivers even though they
44 // do not mutate the Context.
45 //
46 // Before explaining the last detail, first some background. The Go compiler
47 // generates wrapper methods to implement the auto dereferencing behavior when
48 // calling a value method through a pointer variable. These wrapper methods
49 // are also used when calling a value method through an interface variable
50 // because interfaces store a pointer to the underlying concrete value.
51 // Calling a pointer receiver through an interface does not require generating
52 // an additional function.
53 //
54 // If Context had value methods then calling Context.Log through a variable
55 // with type Logger would have an extra stack frame compared to calling
56 // Context.Log through a variable with type Context. Using pointer receivers
57 // avoids this problem.
58
59 // A Context wraps a Logger and holds keyvals that it includes in all log
60 // events. When logging, a Context replaces all value elements (odd indexes)
61 // containing a Valuer with their generated value for each call to its Log
62 // method.
63 type Context struct {
89 // 1. newContext avoids introducing an additional layer when asked to
90 // wrap another context.
91 // 2. With and WithPrefix avoid introducing an additional layer by
92 // returning a newly constructed context with a merged keyvals rather
93 // than simply wrapping the existing context.
94 type context struct {
6495 logger Logger
6596 keyvals []interface{}
6697 hasValuer bool
6798 }
6899
100 func newContext(logger Logger) *context {
101 if c, ok := logger.(*context); ok {
102 return c
103 }
104 return &context{logger: logger}
105 }
106
69107 // Log replaces all value elements (odd indexes) containing a Valuer in the
70108 // stored context with their generated value, appends keyvals, and passes the
71109 // result to the wrapped Logger.
72 func (l *Context) Log(keyvals ...interface{}) error {
110 func (l *context) Log(keyvals ...interface{}) error {
73111 kvs := append(l.keyvals, keyvals...)
74112 if len(kvs)%2 != 0 {
75113 kvs = append(kvs, ErrMissingValue)
85123 return l.logger.Log(kvs...)
86124 }
87125
88 // With returns a new Context with keyvals appended to those of the receiver.
89 func (l *Context) With(keyvals ...interface{}) *Context {
90 if len(keyvals) == 0 {
91 return l
92 }
93 kvs := append(l.keyvals, keyvals...)
94 if len(kvs)%2 != 0 {
95 kvs = append(kvs, ErrMissingValue)
96 }
97 return &Context{
98 logger: l.logger,
99 // Limiting the capacity of the stored keyvals ensures that a new
100 // backing array is created if the slice must grow in Log or With.
101 // Using the extra capacity without copying risks a data race that
102 // would violate the Logger interface contract.
103 keyvals: kvs[:len(kvs):len(kvs)],
104 hasValuer: l.hasValuer || containsValuer(keyvals),
105 }
106 }
107
108 // WithPrefix returns a new Context with keyvals prepended to those of the
109 // receiver.
110 func (l *Context) WithPrefix(keyvals ...interface{}) *Context {
111 if len(keyvals) == 0 {
112 return l
113 }
114 // Limiting the capacity of the stored keyvals ensures that a new
115 // backing array is created if the slice must grow in Log or With.
116 // Using the extra capacity without copying risks a data race that
117 // would violate the Logger interface contract.
118 n := len(l.keyvals) + len(keyvals)
119 if len(keyvals)%2 != 0 {
120 n++
121 }
122 kvs := make([]interface{}, 0, n)
123 kvs = append(kvs, keyvals...)
124 if len(kvs)%2 != 0 {
125 kvs = append(kvs, ErrMissingValue)
126 }
127 kvs = append(kvs, l.keyvals...)
128 return &Context{
129 logger: l.logger,
130 keyvals: kvs,
131 hasValuer: l.hasValuer || containsValuer(keyvals),
132 }
133 }
134
135126 // LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
136127 // f is a function with the appropriate signature, LoggerFunc(f) is a Logger
137128 // object that calls f.
1515 logger := log.NewLogfmtLogger(buf)
1616
1717 kvs := []interface{}{"a", 123}
18 lc := log.NewContext(logger).With(kvs...)
18 lc := log.With(logger, kvs...)
1919 kvs[1] = 0 // With should copy its key values
2020
21 lc = lc.With("b", "c") // With should stack
21 lc = log.With(lc, "b", "c") // With should stack
2222 if err := lc.Log("msg", "message"); err != nil {
2323 t.Fatal(err)
2424 }
2727 }
2828
2929 buf.Reset()
30 lc = lc.WithPrefix("p", "first")
30 lc = log.WithPrefix(lc, "p", "first")
3131 if err := lc.Log("msg", "message"); err != nil {
3232 t.Fatal(err)
3333 }
4444 return nil
4545 }))
4646
47 lc := log.NewContext(logger)
48
49 lc.Log("k")
50 if want, have := 2, len(output); want != have {
51 t.Errorf("want len(output) == %v, have %v", want, have)
52 }
53 if want, have := log.ErrMissingValue, output[1]; want != have {
54 t.Errorf("want %#v, have %#v", want, have)
55 }
56
57 lc.With("k1").WithPrefix("k0").Log("k2")
47 log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2")
5848 if want, have := 6, len(output); want != have {
5949 t.Errorf("want len(output) == %v, have %v", want, have)
6050 }
6555 }
6656 }
6757
68 // Test that Context.Log has a consistent function stack depth when binding
69 // log.Valuers, regardless of how many times Context.With has been called or
70 // whether Context.Log is called via an interface typed variable or a concrete
71 // typed variable.
58 // Test that context.Log has a consistent function stack depth when binding
59 // Valuers, regardless of how many times With has been called.
7260 func TestContextStackDepth(t *testing.T) {
7361 t.Parallel()
7462 fn := fmt.Sprintf("%n", stack.Caller(0))
9078 return nil
9179 })
9280
93 concrete := log.NewContext(logger).With("stack", stackValuer)
94 var iface log.Logger = concrete
81 logger = log.With(logger, "stack", stackValuer)
9582
9683 // Call through interface to get baseline.
97 iface.Log("k", "v")
84 logger.Log("k", "v")
9885 want := output[1].(int)
9986
10087 for len(output) < 10 {
101 concrete.Log("k", "v")
88 logger.Log("k", "v")
10289 if have := output[1]; have != want {
10390 t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
10491 }
10592
106 iface.Log("k", "v")
107 if have := output[1]; have != want {
108 t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
109 }
110
111 wrapped := log.NewContext(concrete)
93 wrapped := log.With(logger)
11294 wrapped.Log("k", "v")
11395 if have := output[1]; have != want {
11496 t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want)
11597 }
11698
117 concrete = concrete.With("k", "v")
118 iface = concrete
99 logger = log.With(logger, "k", "v")
119100 }
120101 }
121102
139120
140121 // With must be careful about handling slices that can grow without
141122 // copying the underlying array, so give it a challenge.
142 l := log.NewContext(logger).With(make([]interface{}, 0, 2)...)
123 l := log.With(logger, make([]interface{}, 0, 2)...)
143124
144125 // Start logging concurrently. Each goroutine logs its id so the logger
145126 // can bucket the event counts.
174155
175156 func BenchmarkOneWith(b *testing.B) {
176157 logger := log.NewNopLogger()
177 lc := log.NewContext(logger).With("k", "v")
158 lc := log.With(logger, "k", "v")
178159 b.ReportAllocs()
179160 b.ResetTimer()
180161 for i := 0; i < b.N; i++ {
184165
185166 func BenchmarkTwoWith(b *testing.B) {
186167 logger := log.NewNopLogger()
187 lc := log.NewContext(logger).With("k", "v")
168 lc := log.With(logger, "k", "v")
188169 for i := 1; i < 2; i++ {
189 lc = lc.With("k", "v")
170 lc = log.With(lc, "k", "v")
190171 }
191172 b.ReportAllocs()
192173 b.ResetTimer()
197178
198179 func BenchmarkTenWith(b *testing.B) {
199180 logger := log.NewNopLogger()
200 lc := log.NewContext(logger).With("k", "v")
181 lc := log.With(logger, "k", "v")
201182 for i := 1; i < 10; i++ {
202 lc = lc.With("k", "v")
183 lc = log.With(lc, "k", "v")
203184 }
204185 b.ReportAllocs()
205186 b.ResetTimer()
1111 if err := logger.Log("abc", 123); err != nil {
1212 t.Error(err)
1313 }
14 if err := log.NewContext(logger).With("def", "ghi").Log(); err != nil {
14 if err := log.With(logger, "def", "ghi").Log(); err != nil {
1515 t.Error(err)
1616 }
1717 }
5555
5656 // copied from log/benchmark_test.go
5757 func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) {
58 lc := log.NewContext(logger).With("common_key", "common_value")
58 lc := log.With(logger, "common_key", "common_value")
5959 b.ReportAllocs()
6060 b.ResetTimer()
6161 for i := 0; i < b.N; i++ {
6565
6666 var (
6767 baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") }
68 withMessage = func(logger log.Logger) { log.NewContext(logger).With("a", "b").Log("c", "d") }
68 withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") }
6969 )
7070
7171 // copied from log/concurrency_test.go
55 "github.com/go-stack/stack"
66 )
77
8 // A Valuer generates a log value. When passed to Context.With in a value
9 // element (odd indexes), it represents a dynamic value which is re-evaluated
10 // with each log event.
8 // A Valuer generates a log value. When passed to With or WithPrefix in a
9 // value element (odd indexes), it represents a dynamic value which is re-
10 // evaluated with each log event.
1111 type Valuer func() interface{}
1212
1313 // bindValues replaces all value elements (odd indexes) containing a Valuer
3838 return func() interface{} { return t() }
3939 }
4040
41 var (
42 // DefaultTimestamp is a Valuer that returns the current wallclock time,
43 // respecting time zones, when bound.
44 DefaultTimestamp Valuer = func() interface{} { return time.Now().Format(time.RFC3339Nano) }
45
46 // DefaultTimestampUTC is a Valuer that returns the current time in UTC
47 // when bound.
48 DefaultTimestampUTC Valuer = func() interface{} { return time.Now().UTC().Format(time.RFC3339Nano) }
49 )
50
5141 // Caller returns a Valuer that returns a file and line from a specified depth
5242 // in the callstack. Users will probably want to use DefaultCaller.
5343 func Caller(depth int) Valuer {
5545 }
5646
5747 var (
48 // DefaultTimestamp is a Valuer that returns the current wallclock time,
49 // respecting time zones, when bound.
50 DefaultTimestamp = Valuer(func() interface{} {
51 return time.Now().Format(time.RFC3339Nano)
52 })
53
54 // DefaultTimestampUTC is a Valuer that returns the current time in UTC
55 // when bound.
56 DefaultTimestampUTC = Valuer(func() interface{} {
57 return time.Now().UTC().Format(time.RFC3339Nano)
58 })
59
5860 // DefaultCaller is a Valuer that returns the file and line where the Log
5961 // method was invoked. It can only be used with log.With.
6062 DefaultCaller = Caller(3)
2323 return now
2424 }
2525
26 lc := log.NewContext(logger).With("ts", log.Timestamp(mocktime), "caller", log.DefaultCaller)
26 lc := log.With(logger, "ts", log.Timestamp(mocktime), "caller", log.DefaultCaller)
2727
2828 lc.Log("foo", "bar")
2929 timestamp, ok := output[1].(time.Time)
6767 return now
6868 }
6969
70 logger = log.NewContext(logger).With("ts", log.Timestamp(mocktime))
70 logger = log.With(logger, "ts", log.Timestamp(mocktime))
7171
7272 logger.Log()
7373 timestamp, ok := output[1].(time.Time)
9191
9292 func BenchmarkValueBindingTimestamp(b *testing.B) {
9393 logger := log.NewNopLogger()
94 lc := log.NewContext(logger).With("ts", log.DefaultTimestamp)
94 lc := log.With(logger, "ts", log.DefaultTimestamp)
9595 b.ReportAllocs()
9696 b.ResetTimer()
9797 for i := 0; i < b.N; i++ {
101101
102102 func BenchmarkValueBindingCaller(b *testing.B) {
103103 logger := log.NewNopLogger()
104 lc := log.NewContext(logger).With("caller", log.DefaultCaller)
104 lc := log.With(logger, "caller", log.DefaultCaller)
105105 b.ReportAllocs()
106106 b.ResetTimer()
107107 for i := 0; i < b.N; i++ {
4545 subscriber := NewSubscriber(
4646 client,
4747 factory,
48 log.NewContext(logger).With("component", "subscriber"),
48 log.With(logger, "component", "subscriber"),
4949 r.Name,
5050 r.Tags,
5151 true,
6363 }
6464
6565 // Build a registrar for r.
66 registrar := NewRegistrar(client, r, log.NewContext(logger).With("component", "registrar"))
66 registrar := NewRegistrar(client, r, log.With(logger, "component", "registrar"))
6767 registrar.Register()
6868 defer registrar.Deregister()
6969
2020 return &Registrar{
2121 client: client,
2222 registration: r,
23 logger: log.NewContext(logger).With("service", r.Name, "tags", fmt.Sprint(r.Tags), "address", r.Address),
23 logger: log.With(logger, "service", r.Name, "tags", fmt.Sprint(r.Tags), "address", r.Address),
2424 }
2525 }
2626
3535 s := &Subscriber{
3636 cache: cache.New(factory, logger),
3737 client: client,
38 logger: log.NewContext(logger).With("service", service, "tags", fmt.Sprint(tags)),
38 logger: log.With(logger, "service", service, "tags", fmt.Sprint(tags)),
3939 service: service,
4040 tags: tags,
4141 passingOnly: passingOnly,
4848 registrar := NewRegistrar(client, Service{
4949 Key: key,
5050 Value: value,
51 }, log.NewContext(log.NewLogfmtLogger(os.Stderr)).With("component", "registrar"))
51 }, log.With(log.NewLogfmtLogger(os.Stderr), "component", "registrar"))
5252
5353 // Register our instance.
5454 registrar.Register()
7070 client,
7171 prefix,
7272 func(string) (endpoint.Endpoint, io.Closer, error) { return endpoint.Nop, nil, nil },
73 log.NewContext(log.NewLogfmtLogger(os.Stderr)).With("component", "subscriber"),
73 log.With(log.NewLogfmtLogger(os.Stderr), "component", "subscriber"),
7474 )
7575 if err != nil {
7676 t.Fatalf("NewSubscriber: %v", err)
6262 return &Registrar{
6363 client: client,
6464 service: service,
65 logger: log.NewContext(logger).With(
66 "key", service.Key,
67 "value", service.Value,
68 ),
65 logger: log.With(logger, "key", service.Key, "value", service.Value),
6966 }
7067 }
7168
2323 return &Registrar{
2424 client: client,
2525 service: service,
26 logger: log.NewContext(logger).With(
26 logger: log.With(logger,
2727 "service", service.Name,
2828 "path", service.Path,
2929 "data", string(service.Data),