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