1 // Package log implements a standard convention for structured logging.
2 // Log entries are formatted as K=V pairs.
3 // By default, output is written to stdout; this can be changed with SetOutput.
17 "github.com/blockchain/errors"
20 const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
26 logWriterMu sync.Mutex // protects the following
27 logWriter io.Writer = os.Stdout
28 procPrefix []byte // process-global prefix; see SetPrefix vs AddPrefixkv
30 // pairDelims contains a list of characters that may be used as delimeters
31 // between key-value pairs in a log entry. Keys and values will be quoted or
32 // otherwise formatted to ensure that key-value extraction is unambiguous.
34 // The list of pair delimiters follows Splunk conventions, described here:
35 // http://answers.splunk.com/answers/143368/default-delimiters-for-key-value-extraction.html
36 pairDelims = " ,;|&\t\n\r"
37 illegalKeyChars = pairDelims + `="`
39 // context key for log line prefixes
43 // Conventional key names for log entries
45 KeyCaller = "at" // location of caller
46 KeyTime = "t" // time of call
48 KeyMessage = "message" // produced by Message
49 KeyError = "error" // produced by Error
50 KeyStack = "stack" // used by Printkv to print stack on subsequent lines
52 keyLogError = "log-error" // for errors produced by the log package itself
55 // SetOutput sets the log output to w.
56 // If SetOutput hasn't been called,
57 // the default behavior is to write to stdout.
58 func SetOutput(w io.Writer) {
64 func appendPrefix(b []byte, keyval ...interface{}) []byte {
65 // Invariant: len(keyval) is always even.
66 if len(keyval)%2 != 0 {
67 panic(fmt.Sprintf("odd-length prefix args: %v", keyval))
69 for i := 0; i < len(keyval); i += 2 {
70 k := formatKey(keyval[i])
71 v := formatValue(keyval[i+1])
80 // SetPrefix sets the global output prefix.
81 func SetPrefix(keyval ...interface{}) {
82 b := appendPrefix(nil, keyval...)
88 // AddPrefixkv appends keyval to any prefix stored in ctx,
89 // and returns a new context with the longer prefix.
90 func AddPrefixkv(ctx context.Context, keyval ...interface{}) context.Context {
91 p := appendPrefix(prefix(ctx), keyval...)
92 // Note: subsequent calls will append to p, so set cap(p) here.
93 // See TestAddPrefixkvAppendTwice.
94 p = p[0:len(p):len(p)]
95 return context.WithValue(ctx, prefixKey, p)
98 func prefix(ctx context.Context) []byte {
99 b, _ := ctx.Value(prefixKey).([]byte)
103 // Printkv prints a structured log entry to stdout. Log fields are
104 // specified as a variadic sequence of alternating keys and values.
106 // Duplicate keys will be preserved.
108 // Two fields are automatically added to the log entry: t=[time]
109 // and at=[file:line] indicating the location of the caller.
110 // Use SkipFunc to prevent helper functions from showing up in the
111 // at=[file:line] field.
113 // Printkv will also print the stack trace, if any, on separate lines
114 // following the message. The stack is obtained from the following,
115 // in order of preference:
116 // - a KeyStack value with type []byte or []errors.StackFrame
117 // - a KeyError value with type error, using the result of errors.Stack
118 func Printkv(ctx context.Context, keyvals ...interface{}) {
119 // Invariant: len(keyvals) is always even.
120 if len(keyvals)%2 != 0 {
121 keyvals = append(keyvals, "", keyLogError, "odd number of log params")
124 t := time.Now().UTC()
126 // Prepend the log entry with auto-generated fields.
130 KeyTime, formatValue(t.Format(rfc3339NanoFixed)),
133 var stack interface{}
134 for i := 0; i < len(keyvals); i += 2 {
137 if k == KeyStack && isStackVal(v) {
142 if e, ok := v.(error); ok && stack == nil {
143 stack = errors.Stack(errors.Wrap(e)) // wrap to ensure callstack
146 out += " " + formatKey(k) + "=" + formatValue(v)
150 logWriter.Write(procPrefix)
151 logWriter.Write(prefix(ctx))
152 logWriter.Write([]byte(out)) // ignore errors
153 logWriter.Write([]byte{'\n'})
154 writeRawStack(logWriter, stack)
158 // Fatalkv is equivalent to Printkv() followed by a call to os.Exit(1).
159 func Fatalkv(ctx context.Context, keyvals ...interface{}) {
160 Printkv(ctx, keyvals...)
164 func writeRawStack(w io.Writer, v interface{}) {
165 switch v := v.(type) {
169 w.Write([]byte{'\n'})
171 case []errors.StackFrame:
172 for _, s := range v {
173 io.WriteString(w, s.String())
174 w.Write([]byte{'\n'})
179 func isStackVal(v interface{}) bool {
183 case []errors.StackFrame:
189 // Printf prints a log entry containing a message assigned to the
190 // "message" key. Arguments are handled as in fmt.Printf.
191 func Printf(ctx context.Context, format string, a ...interface{}) {
192 Printkv(ctx, KeyMessage, fmt.Sprintf(format, a...))
195 // Error prints a log entry containing an error message assigned to the
197 // Optionally, an error message prefix can be included. Prefix arguments are
198 // handled as in fmt.Print.
199 func Error(ctx context.Context, err error, a ...interface{}) {
200 if len(a) > 0 && len(errors.Stack(err)) > 0 {
201 err = errors.Wrap(err, a...) // keep err's stack
202 } else if len(a) > 0 {
203 err = fmt.Errorf("%s: %s", fmt.Sprint(a...), err) // don't add a stack here
205 Printkv(ctx, KeyError, err)
208 // formatKey ensures that the stringified key is valid for use in a
209 // Splunk-style K=V format. It stubs out delimeter and quoter characters in
210 // the key string with hyphens.
211 func formatKey(k interface{}) string {
217 for _, c := range illegalKeyChars {
218 s = strings.Replace(s, string(c), "-", -1)
224 // formatValue ensures that the stringified value is valid for use in a
225 // Splunk-style K=V format. It quotes the string value if delimeter or quoter
226 // characters are present in the value string.
227 func formatValue(v interface{}) string {
229 if strings.ContainsAny(s, pairDelims) {
230 return strconv.Quote(s)
235 // RecoverAndLogError must be used inside a defer.
236 func RecoverAndLogError(ctx context.Context) {
237 if err := recover(); err != nil {
238 const size = 64 << 10
239 buf := make([]byte, size)
240 buf = buf[:runtime.Stack(buf, false)]