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 }