go-micro.dev/v5@v5.12.0/logger/default.go (about) 1 package logger 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "runtime" 8 "sort" 9 "strings" 10 "sync" 11 "time" 12 13 dlog "go-micro.dev/v5/debug/log" 14 ) 15 16 func init() { 17 lvl, err := GetLevel(os.Getenv("MICRO_LOG_LEVEL")) 18 if err != nil { 19 lvl = InfoLevel 20 } 21 22 DefaultLogger = NewLogger(WithLevel(lvl)) 23 } 24 25 type defaultLogger struct { 26 opts Options 27 sync.RWMutex 28 } 29 30 // Init (opts...) should only overwrite provided options. 31 func (l *defaultLogger) Init(opts ...Option) error { 32 for _, o := range opts { 33 o(&l.opts) 34 } 35 36 return nil 37 } 38 39 func (l *defaultLogger) String() string { 40 return "default" 41 } 42 43 func (l *defaultLogger) Fields(fields map[string]interface{}) Logger { 44 l.Lock() 45 nfields := make(map[string]interface{}, len(l.opts.Fields)) 46 47 for k, v := range l.opts.Fields { 48 nfields[k] = v 49 } 50 l.Unlock() 51 52 for k, v := range fields { 53 nfields[k] = v 54 } 55 56 return &defaultLogger{opts: Options{ 57 Level: l.opts.Level, 58 Fields: nfields, 59 Out: l.opts.Out, 60 CallerSkipCount: l.opts.CallerSkipCount, 61 Context: l.opts.Context, 62 }} 63 } 64 65 func copyFields(src map[string]interface{}) map[string]interface{} { 66 dst := make(map[string]interface{}, len(src)) 67 for k, v := range src { 68 dst[k] = v 69 } 70 71 return dst 72 } 73 74 // logCallerfilePath returns a package/file:line description of the caller, 75 // preserving only the leaf directory name and file name. 76 func logCallerfilePath(loggingFilePath string) string { 77 // To make sure we trim the path correctly on Windows too, we 78 // counter-intuitively need to use '/' and *not* os.PathSeparator here, 79 // because the path given originates from Go stdlib, specifically 80 // runtime.Caller() which (as of Mar/17) returns forward slashes even on 81 // Windows. 82 // 83 // See https://github.com/golang/go/issues/3335 84 // and https://github.com/golang/go/issues/18151 85 // 86 // for discussion on the issue on Go side. 87 idx := strings.LastIndexByte(loggingFilePath, '/') 88 if idx == -1 { 89 return loggingFilePath 90 } 91 92 idx = strings.LastIndexByte(loggingFilePath[:idx], '/') 93 94 if idx == -1 { 95 return loggingFilePath 96 } 97 98 return loggingFilePath[idx+1:] 99 } 100 101 func (l *defaultLogger) Log(level Level, v ...interface{}) { 102 // TODO decide does we need to write message if log level not used? 103 if !l.opts.Level.Enabled(level) { 104 return 105 } 106 107 l.RLock() 108 fields := copyFields(l.opts.Fields) 109 l.RUnlock() 110 111 fields["level"] = level.String() 112 113 if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { 114 fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) 115 } 116 117 rec := dlog.Record{ 118 Timestamp: time.Now(), 119 Message: fmt.Sprint(v...), 120 Metadata: make(map[string]string, len(fields)), 121 } 122 123 keys := make([]string, 0, len(fields)) 124 for k, v := range fields { 125 keys = append(keys, k) 126 rec.Metadata[k] = fmt.Sprintf("%v", v) 127 } 128 129 sort.Strings(keys) 130 131 metadata := "" 132 133 for _, k := range keys { 134 metadata += fmt.Sprintf(" %s=%v", k, fields[k]) 135 } 136 137 dlog.DefaultLog.Write(rec) 138 139 t := rec.Timestamp.Format("2006-01-02 15:04:05") 140 fmt.Printf("%s %s %v\n", t, metadata, rec.Message) 141 } 142 143 func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) { 144 // TODO decide does we need to write message if log level not used? 145 if !l.opts.Level.Enabled(level) { 146 return 147 } 148 149 l.RLock() 150 fields := copyFields(l.opts.Fields) 151 l.RUnlock() 152 153 fields["level"] = level.String() 154 155 if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok { 156 fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line) 157 } 158 159 rec := dlog.Record{ 160 Timestamp: time.Now(), 161 Message: fmt.Sprintf(format, v...), 162 Metadata: make(map[string]string, len(fields)), 163 } 164 165 keys := make([]string, 0, len(fields)) 166 for k, v := range fields { 167 keys = append(keys, k) 168 rec.Metadata[k] = fmt.Sprintf("%v", v) 169 } 170 171 sort.Strings(keys) 172 173 metadata := "" 174 175 for _, k := range keys { 176 metadata += fmt.Sprintf(" %s=%v", k, fields[k]) 177 } 178 179 dlog.DefaultLog.Write(rec) 180 181 t := rec.Timestamp.Format("2006-01-02 15:04:05") 182 fmt.Printf("%s %s %v\n", t, metadata, rec.Message) 183 } 184 185 func (l *defaultLogger) Options() Options { 186 // not guard against options Context values 187 l.RLock() 188 defer l.RUnlock() 189 190 opts := l.opts 191 opts.Fields = copyFields(l.opts.Fields) 192 193 return opts 194 } 195 196 // NewLogger builds a new logger based on options. 197 func NewLogger(opts ...Option) Logger { 198 // Default options 199 options := Options{ 200 Level: InfoLevel, 201 Fields: make(map[string]interface{}), 202 Out: os.Stderr, 203 CallerSkipCount: 2, 204 Context: context.Background(), 205 } 206 207 l := &defaultLogger{opts: options} 208 if err := l.Init(opts...); err != nil { 209 l.Log(FatalLevel, err) 210 } 211 212 return l 213 }