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