1package log 2 3import ( 4 "bytes" 5 "io" 6 "log" 7 "regexp" 8 "strings" 9) 10 11// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's 12// designed to be passed to a Go kit logger as the writer, for cases where 13// it's necessary to redirect all Go kit log output to the stdlib logger. 14// 15// If you have any choice in the matter, you shouldn't use this. Prefer to 16// redirect the stdlib log to the Go kit logger via NewStdlibAdapter. 17type StdlibWriter struct{} 18 19// Write implements io.Writer. 20func (w StdlibWriter) Write(p []byte) (int, error) { 21 log.Print(strings.TrimSpace(string(p))) 22 return len(p), nil 23} 24 25// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib 26// logger's SetOutput. It will extract date/timestamps, filenames, and 27// messages, and place them under relevant keys. 28type StdlibAdapter struct { 29 Logger 30 timestampKey string 31 fileKey string 32 messageKey string 33 prefix string 34 joinPrefixToMsg bool 35} 36 37// StdlibAdapterOption sets a parameter for the StdlibAdapter. 38type StdlibAdapterOption func(*StdlibAdapter) 39 40// TimestampKey sets the key for the timestamp field. By default, it's "ts". 41func TimestampKey(key string) StdlibAdapterOption { 42 return func(a *StdlibAdapter) { a.timestampKey = key } 43} 44 45// FileKey sets the key for the file and line field. By default, it's "caller". 46func FileKey(key string) StdlibAdapterOption { 47 return func(a *StdlibAdapter) { a.fileKey = key } 48} 49 50// MessageKey sets the key for the actual log message. By default, it's "msg". 51func MessageKey(key string) StdlibAdapterOption { 52 return func(a *StdlibAdapter) { a.messageKey = key } 53} 54 55// Prefix configures the adapter to parse a prefix from stdlib log events. If 56// you provide a non-empty prefix to the stdlib logger, then your should provide 57// that same prefix to the adapter via this option. 58// 59// By default, the prefix isn't included in the msg key. Set joinPrefixToMsg to 60// true if you want to include the parsed prefix in the msg. 61func Prefix(prefix string, joinPrefixToMsg bool) StdlibAdapterOption { 62 return func(a *StdlibAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg } 63} 64 65// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed 66// logger. It's designed to be passed to log.SetOutput. 67func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { 68 a := StdlibAdapter{ 69 Logger: logger, 70 timestampKey: "ts", 71 fileKey: "caller", 72 messageKey: "msg", 73 } 74 for _, option := range options { 75 option(&a) 76 } 77 return a 78} 79 80func (a StdlibAdapter) Write(p []byte) (int, error) { 81 p = a.handlePrefix(p) 82 83 result := subexps(p) 84 keyvals := []interface{}{} 85 var timestamp string 86 if date, ok := result["date"]; ok && date != "" { 87 timestamp = date 88 } 89 if time, ok := result["time"]; ok && time != "" { 90 if timestamp != "" { 91 timestamp += " " 92 } 93 timestamp += time 94 } 95 if timestamp != "" { 96 keyvals = append(keyvals, a.timestampKey, timestamp) 97 } 98 if file, ok := result["file"]; ok && file != "" { 99 keyvals = append(keyvals, a.fileKey, file) 100 } 101 if msg, ok := result["msg"]; ok { 102 msg = a.handleMessagePrefix(msg) 103 keyvals = append(keyvals, a.messageKey, msg) 104 } 105 if err := a.Logger.Log(keyvals...); err != nil { 106 return 0, err 107 } 108 return len(p), nil 109} 110 111func (a StdlibAdapter) handlePrefix(p []byte) []byte { 112 if a.prefix != "" { 113 p = bytes.TrimPrefix(p, []byte(a.prefix)) 114 } 115 return p 116} 117 118func (a StdlibAdapter) handleMessagePrefix(msg string) string { 119 if a.prefix == "" { 120 return msg 121 } 122 123 msg = strings.TrimPrefix(msg, a.prefix) 124 if a.joinPrefixToMsg { 125 msg = a.prefix + msg 126 } 127 return msg 128} 129 130const ( 131 logRegexpDate = `(?P<date>[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` 132 logRegexpTime = `(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?)?[ ]?` 133 logRegexpFile = `(?P<file>.+?:[0-9]+)?` 134 logRegexpMsg = `(: )?(?P<msg>(?s:.*))` 135) 136 137var ( 138 logRegexp = regexp.MustCompile(logRegexpDate + logRegexpTime + logRegexpFile + logRegexpMsg) 139) 140 141func subexps(line []byte) map[string]string { 142 m := logRegexp.FindSubmatch(line) 143 if len(m) < len(logRegexp.SubexpNames()) { 144 return map[string]string{} 145 } 146 result := map[string]string{} 147 for i, name := range logRegexp.SubexpNames() { 148 result[name] = strings.TrimRight(string(m[i]), "\n") 149 } 150 return result 151} 152