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  }