github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/slogger/log.go (about)

     1  package slogger
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/mongodb/grip/level"
    10  	"github.com/mongodb/grip/message"
    11  )
    12  
    13  const maxStackFrames = 1024
    14  
    15  // Log is a representation of a logging event, which matches the
    16  // structure and interface of the original slogger Log
    17  // type. Additionally implements grip's "message.Composer" interface
    18  // for use with other logging mechanisms.
    19  //
    20  // Note that the String() method, which Sender's use to format the
    21  // output of the log lines includes timestamp and component
    22  // (name/prefix) information.
    23  type Log struct {
    24  	Prefix    string    `bson:"prefix,omitempty" json:"prefix,omitempty" yaml:"prefix,omitempty"`
    25  	Level     Level     `bson:"level" json:"level" yaml:"level"`
    26  	Filename  string    `bson:"filename" json:"filename" yaml:"filename"`
    27  	Line      int       `bson:"line" json:"line" yaml:"line"`
    28  	Timestamp time.Time `bson:"timestamp" json:"timestamp" yaml:"timestamp"`
    29  	Output    string    `bson:"message,omitempty" json:"message,omitempty" yaml:"message,omitempty"`
    30  	msg       message.Composer
    31  }
    32  
    33  // FormatLog provides compatibility with the original slogger
    34  // implementation.
    35  func FormatLog(log *Log) string {
    36  	return log.String()
    37  }
    38  
    39  // Message returns the formatted log message.
    40  func (l *Log) Message() string                        { return l.msg.String() }
    41  func (l *Log) Priority() level.Priority               { return l.Level.Priority() }
    42  func (l *Log) SetPriority(lvl level.Priority) error   { l.Level = convertFromPriority(lvl); return nil }
    43  func (l *Log) Loggable() bool                         { return l.msg.Loggable() }
    44  func (l *Log) Raw() interface{}                       { _ = l.String(); return l }
    45  func (l *Log) Annotate(k string, v interface{}) error { return l.msg.Annotate(k, v) }
    46  func (l *Log) String() string {
    47  	if l.Output == "" {
    48  		year, month, day := l.Timestamp.Date()
    49  		hour, min, sec := l.Timestamp.Clock()
    50  
    51  		l.Output = fmt.Sprintf("[%.4d/%.2d/%.2d %.2d:%.2d:%.2d] [%v.%v] [%v:%d] %v",
    52  			year, month, day,
    53  			hour, min, sec,
    54  			l.Prefix, l.Level.String(),
    55  			l.Filename, l.Line,
    56  			l.msg.String())
    57  	}
    58  
    59  	return l.Output
    60  }
    61  
    62  // NewLog takes a message.Composer object and returns a slogger.Log
    63  // instance (which also implements message.Composer). This method
    64  // records its callsite, so you ought to call this method directly.
    65  func NewLog(m message.Composer) *Log {
    66  	l := &Log{
    67  		Level:     convertFromPriority(m.Priority()),
    68  		Timestamp: time.Now(),
    69  		msg:       m,
    70  	}
    71  	l.appendCallerInfo(2)
    72  	return l
    73  }
    74  
    75  func newLog(m message.Composer) *Log {
    76  	l := &Log{
    77  		Level:     convertFromPriority(m.Priority()),
    78  		Timestamp: time.Now(),
    79  		msg:       m,
    80  	}
    81  	l.appendCallerInfo(3)
    82  	return l
    83  }
    84  
    85  // NewPrefixedLog allows you to construct a slogger.Log message from a
    86  // message composer, while specifying a prefix.
    87  func NewPrefixedLog(prefix string, m message.Composer) *Log {
    88  	l := NewLog(m)
    89  	l.Prefix = prefix
    90  	l.appendCallerInfo(2)
    91  	return l
    92  }
    93  
    94  func newPrefixedLog(prefix string, m message.Composer) *Log {
    95  	l := newLog(m)
    96  	l.Prefix = prefix
    97  	l.appendCallerInfo(3)
    98  	return l
    99  
   100  }
   101  
   102  func (l *Log) appendCallerInfo(skip int) {
   103  	_, file, line, ok := runtime.Caller(skip)
   104  	if ok {
   105  		l.Filename = stripDirectories(file, 1)
   106  		l.Line = line
   107  	}
   108  }
   109  
   110  // These functions are taken directly from the original slogger
   111  
   112  func stacktrace() []string {
   113  	ret := make([]string, 0, 2)
   114  
   115  	for skip := 2; skip < maxStackFrames; skip++ {
   116  		_, file, line, ok := runtime.Caller(skip)
   117  		if !ok {
   118  			break
   119  		}
   120  
   121  		ret = append(ret, fmt.Sprintf("at %s:%d", stripDirectories(file, 1), line))
   122  	}
   123  
   124  	return ret
   125  }
   126  
   127  func stripDirectories(filepath string, toKeep int) string {
   128  	var idxCutoff int
   129  	if idxCutoff = strings.LastIndex(filepath, "/"); idxCutoff == -1 {
   130  		return filepath
   131  	}
   132  
   133  outer:
   134  	for dirToKeep := 0; dirToKeep < toKeep; dirToKeep++ {
   135  		switch idx := strings.LastIndex(filepath[:idxCutoff], "/"); idx {
   136  		case -1:
   137  			break outer
   138  		default:
   139  			idxCutoff = idx
   140  		}
   141  	}
   142  
   143  	return filepath[idxCutoff+1:]
   144  }