trace.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package context
  2. import (
  3. "runtime"
  4. "time"
  5. "github.com/docker/distribution/uuid"
  6. )
  7. // WithTrace allocates a traced timing span in a new context. This allows a
  8. // caller to track the time between calling WithTrace and the returned done
  9. // function. When the done function is called, a log message is emitted with a
  10. // "trace.duration" field, corresponding to the elapsed time and a
  11. // "trace.func" field, corresponding to the function that called WithTrace.
  12. //
  13. // The logging keys "trace.id" and "trace.parent.id" are provided to implement
  14. // dapper-like tracing. This function should be complemented with a WithSpan
  15. // method that could be used for tracing distributed RPC calls.
  16. //
  17. // The main benefit of this function is to post-process log messages or
  18. // intercept them in a hook to provide timing data. Trace ids and parent ids
  19. // can also be linked to provide call tracing, if so required.
  20. //
  21. // Here is an example of the usage:
  22. //
  23. // func timedOperation(ctx Context) {
  24. // ctx, done := WithTrace(ctx)
  25. // defer done("this will be the log message")
  26. // // ... function body ...
  27. // }
  28. //
  29. // If the function ran for roughly 1s, such a usage would emit a log message
  30. // as follows:
  31. //
  32. // INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/docker/distribution/context.traceOperation trace.id=<id> ...
  33. //
  34. // Notice that the function name is automatically resolved, along with the
  35. // package and a trace id is emitted that can be linked with parent ids.
  36. func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
  37. if ctx == nil {
  38. ctx = Background()
  39. }
  40. pc, file, line, _ := runtime.Caller(1)
  41. f := runtime.FuncForPC(pc)
  42. ctx = &traced{
  43. Context: ctx,
  44. id: uuid.Generate().String(),
  45. start: time.Now(),
  46. parent: GetStringValue(ctx, "trace.id"),
  47. fnname: f.Name(),
  48. file: file,
  49. line: line,
  50. }
  51. return ctx, func(format string, a ...interface{}) {
  52. GetLogger(ctx,
  53. "trace.duration",
  54. "trace.id",
  55. "trace.parent.id",
  56. "trace.func",
  57. "trace.file",
  58. "trace.line").
  59. Debugf(format, a...)
  60. }
  61. }
  62. // traced represents a context that is traced for function call timing. It
  63. // also provides fast lookup for the various attributes that are available on
  64. // the trace.
  65. type traced struct {
  66. Context
  67. id string
  68. parent string
  69. start time.Time
  70. fnname string
  71. file string
  72. line int
  73. }
  74. func (ts *traced) Value(key interface{}) interface{} {
  75. switch key {
  76. case "trace.start":
  77. return ts.start
  78. case "trace.duration":
  79. return time.Since(ts.start)
  80. case "trace.id":
  81. return ts.id
  82. case "trace.parent.id":
  83. if ts.parent == "" {
  84. return nil // must return nil to signal no parent.
  85. }
  86. return ts.parent
  87. case "trace.func":
  88. return ts.fnname
  89. case "trace.file":
  90. return ts.file
  91. case "trace.line":
  92. return ts.line
  93. }
  94. return ts.Context.Value(key)
  95. }