go.undefinedlabs.com/scopeagent@v0.4.2/instrumentation/logging/logger.go (about) 1 package logging 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 stdlog "log" 8 "path/filepath" 9 "regexp" 10 "sync" 11 "time" 12 13 "github.com/opentracing/opentracing-go" 14 "github.com/opentracing/opentracing-go/log" 15 16 "go.undefinedlabs.com/scopeagent/tags" 17 ) 18 19 const ( 20 logRegexTemplate = `(?m)^%s(?:(?P<date>\d{4}\/\d{1,2}\/\d{1,2}) )?(?:(?P<time>\d{1,2}:\d{1,2}:\d{1,2}(?:.\d{1,6})?) )?(?:(?:(?P<file>[\w\-. \/\\:]+):(?P<line>\d+)): )?(.*)\n?$` 21 timeLayout = "2006/01/02T15:04:05.000000" 22 ) 23 24 type ( 25 otWriter struct { 26 logRecordsMutex sync.RWMutex 27 logRecords []opentracing.LogRecord 28 regex *regexp.Regexp 29 ctx context.Context 30 } 31 logItem struct { 32 time time.Time 33 file string 34 lineNumber string 35 message string 36 } 37 loggerPatchInfo struct { 38 current *otWriter 39 previous io.Writer 40 } 41 ) 42 43 var ( 44 patchedLoggersMutex sync.Mutex 45 patchedLoggers = map[io.Writer]loggerPatchInfo{} 46 stdLoggerWriter io.Writer 47 ) 48 49 // Patch the standard logger 50 func PatchStandardLogger() { 51 stdLoggerWriter := getStdLoggerWriter() 52 otWriter := &otWriter{regex: regexp.MustCompile(fmt.Sprintf(logRegexTemplate, stdlog.Prefix()))} 53 stdlog.SetOutput(io.MultiWriter(stdLoggerWriter, otWriter)) 54 recorders = append(recorders, otWriter) 55 } 56 57 // Unpatch the standard logger 58 func UnpatchStandardLogger() { 59 stdlog.SetOutput(stdLoggerWriter) 60 } 61 62 // Patch a logger 63 func PatchLogger(logger *stdlog.Logger) { 64 patchLogger(logger, nil) 65 } 66 67 // Unpatch a logger 68 func UnpatchLogger(logger *stdlog.Logger) { 69 unpatchLogger(logger) 70 } 71 72 // Create a new logger with a context 73 func WithContext(logger *stdlog.Logger, ctx context.Context) *stdlog.Logger { 74 rLogger := stdlog.New(getLoggerWriter(logger), logger.Prefix(), logger.Flags()) 75 patchLogger(rLogger, ctx) 76 return rLogger 77 } 78 79 // Write data to the channel and the base writer 80 func (w *otWriter) Write(p []byte) (n int, err error) { 81 w.process(p) 82 return len(p), nil 83 } 84 85 // Start recording opentracing.LogRecord from logger 86 func (w *otWriter) Reset() { 87 w.logRecordsMutex.Lock() 88 defer w.logRecordsMutex.Unlock() 89 w.logRecords = nil 90 } 91 92 // Stop recording opentracing.LogRecord and return all recorded items 93 func (w *otWriter) GetRecords() []opentracing.LogRecord { 94 w.logRecordsMutex.RLock() 95 defer w.Reset() 96 defer w.logRecordsMutex.RUnlock() 97 return w.logRecords 98 } 99 100 // Patch logger with optional context 101 func patchLogger(logger *stdlog.Logger, ctx context.Context) { 102 unpatchLogger(logger) 103 104 patchedLoggersMutex.Lock() 105 defer patchedLoggersMutex.Unlock() 106 107 otWriter := &otWriter{ 108 regex: regexp.MustCompile(fmt.Sprintf(logRegexTemplate, logger.Prefix())), 109 ctx: ctx, 110 } 111 112 currentWriter := getLoggerWriter(logger) 113 newWriter := io.MultiWriter(currentWriter, otWriter) 114 patchedLoggers[newWriter] = loggerPatchInfo{ 115 current: otWriter, 116 previous: currentWriter, 117 } 118 119 recorders = append(recorders, otWriter) 120 logger.SetOutput(newWriter) 121 } 122 123 // Unpatch logger 124 func unpatchLogger(logger *stdlog.Logger) { 125 patchedLoggersMutex.Lock() 126 defer patchedLoggersMutex.Unlock() 127 128 currentWriter := getLoggerWriter(logger) 129 130 if logInfo, ok := patchedLoggers[currentWriter]; ok { 131 logger.SetOutput(logInfo.previous) 132 delete(patchedLoggers, currentWriter) 133 } 134 } 135 136 // Process bytes and create new log items struct to store 137 func (w *otWriter) process(p []byte) { 138 if len(p) == 0 { 139 // Nothing to process 140 return 141 } 142 logBuffer := string(p) 143 matches := w.regex.FindAllStringSubmatch(logBuffer, -1) 144 if matches == nil || len(matches) == 0 { 145 return 146 } 147 var item *logItem 148 for _, match := range matches { 149 // In case a new log line we store the previous one and create a new log item 150 if match[1] != "" || match[2] != "" || match[3] != "" || match[4] != "" { 151 if item != nil { 152 w.storeLogRecord(item) 153 } 154 now := time.Now() 155 if match[1] != "" && match[2] != "" { 156 pTime, err := time.Parse(timeLayout, fmt.Sprintf("%sT%s", match[1], match[2])) 157 if err == nil { 158 now = pTime 159 } 160 } 161 item = &logItem{ 162 time: now, 163 file: match[3], 164 lineNumber: match[4], 165 } 166 } 167 if item != nil { 168 if item.message == "" { 169 item.message = match[5] 170 } else { 171 // Multiline log item support 172 item.message = item.message + "\n" + match[5] 173 } 174 } 175 } 176 if item != nil { 177 w.storeLogRecord(item) 178 } 179 } 180 181 // Stores a new log record from the logItem 182 func (w *otWriter) storeLogRecord(item *logItem) { 183 fields := []log.Field{ 184 log.String(tags.EventType, tags.LogEvent), 185 log.String(tags.LogEventLevel, tags.LogLevel_VERBOSE), 186 log.String("log.logger", "log.std"), 187 log.String(tags.EventMessage, item.message), 188 } 189 if item.file != "" && item.lineNumber != "" { 190 item.file = filepath.Clean(item.file) 191 fields = append(fields, log.String(tags.EventSource, fmt.Sprintf("%s:%s", item.file, item.lineNumber))) 192 } 193 194 // If context is found, we try to find the a span from the context and write the logs 195 if w.ctx != nil { 196 if span := opentracing.SpanFromContext(w.ctx); span != nil { 197 span.LogFields(fields...) 198 return 199 } 200 } 201 202 // If no context, we store the log records for future extraction 203 w.logRecordsMutex.Lock() 204 defer w.logRecordsMutex.Unlock() 205 w.logRecords = append(w.logRecords, opentracing.LogRecord{ 206 Timestamp: item.time, 207 Fields: fields, 208 }) 209 }