github.com/gogf/gf@v1.16.9/os/glog/glog_logger.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package glog 8 9 import ( 10 "bytes" 11 "context" 12 "fmt" 13 "io" 14 "os" 15 "strings" 16 "time" 17 18 "github.com/fatih/color" 19 "github.com/gogf/gf/container/gtype" 20 "github.com/gogf/gf/internal/intlog" 21 "github.com/gogf/gf/os/gctx" 22 "github.com/gogf/gf/os/gfpool" 23 "github.com/gogf/gf/os/gmlock" 24 "github.com/gogf/gf/os/gtimer" 25 "go.opentelemetry.io/otel/trace" 26 27 "github.com/gogf/gf/debug/gdebug" 28 29 "github.com/gogf/gf/os/gfile" 30 "github.com/gogf/gf/os/gtime" 31 "github.com/gogf/gf/text/gregex" 32 "github.com/gogf/gf/util/gconv" 33 ) 34 35 // Logger is the struct for logging management. 36 type Logger struct { 37 ctx context.Context // Context for logging. 38 init *gtype.Bool // Initialized. 39 parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function. 40 config Config // Logger configuration. 41 } 42 43 const ( 44 defaultFileFormat = `{Y-m-d}.log` 45 defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND 46 defaultFilePerm = os.FileMode(0666) 47 defaultFileExpire = time.Minute 48 pathFilterKey = "/os/glog/glog" 49 memoryLockPrefixForPrintingToFile = "glog.printToFile:" 50 ) 51 52 const ( 53 F_ASYNC = 1 << iota // Print logging content asynchronously。 54 F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23. 55 F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG. 56 F_TIME_DATE // Print the date in the local time zone: 2009-01-23. 57 F_TIME_TIME // Print the time in the local time zone: 01:23:23. 58 F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675. 59 F_CALLER_FN // Print Caller function name and package: main.main 60 F_TIME_STD = F_TIME_DATE | F_TIME_MILLI 61 ) 62 63 // New creates and returns a custom logger. 64 func New() *Logger { 65 logger := &Logger{ 66 init: gtype.NewBool(), 67 config: DefaultConfig(), 68 } 69 return logger 70 } 71 72 // NewWithWriter creates and returns a custom logger with io.Writer. 73 func NewWithWriter(writer io.Writer) *Logger { 74 l := New() 75 l.SetWriter(writer) 76 return l 77 } 78 79 // Clone returns a new logger, which is the clone the current logger. 80 // It's commonly used for chaining operations. 81 func (l *Logger) Clone() *Logger { 82 newLogger := New() 83 newLogger.ctx = l.ctx 84 newLogger.config = l.config 85 newLogger.parent = l 86 return newLogger 87 } 88 89 // getFilePath returns the logging file path. 90 // The logging file name must have extension name of "log". 91 func (l *Logger) getFilePath(now time.Time) string { 92 // Content containing "{}" in the file name is formatted using gtime. 93 file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string { 94 return gtime.New(now).Format(strings.Trim(s, "{}")) 95 }) 96 file = gfile.Join(l.config.Path, file) 97 return file 98 } 99 100 // print prints `s` to defined writer, logging file or passed `std`. 101 func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { 102 // Lazy initialize for rotation feature. 103 // It uses atomic reading operation to enhance the performance checking. 104 // It here uses CAP for performance and concurrent safety. 105 p := l 106 if p.parent != nil { 107 p = p.parent 108 } 109 // It just initializes once for each logger. 110 if p.config.RotateSize > 0 || p.config.RotateExpire > 0 { 111 if !p.init.Val() && p.init.Cas(false, true) { 112 gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely) 113 intlog.Printf(ctx, "logger rotation initialized: every %s", p.config.RotateCheckInterval.String()) 114 } 115 } 116 117 var ( 118 now = time.Now() 119 input = &HandlerInput{ 120 Logger: l, 121 Buffer: bytes.NewBuffer(nil), 122 Ctx: ctx, 123 Time: now, 124 Color: defaultLevelColor[level], 125 Level: level, 126 handlerIndex: -1, 127 } 128 ) 129 if l.config.HeaderPrint { 130 // Time. 131 timeFormat := "" 132 if l.config.Flags&F_TIME_DATE > 0 { 133 timeFormat += "2006-01-02" 134 } 135 if l.config.Flags&F_TIME_TIME > 0 { 136 if timeFormat != "" { 137 timeFormat += " " 138 } 139 timeFormat += "15:04:05" 140 } 141 if l.config.Flags&F_TIME_MILLI > 0 { 142 if timeFormat != "" { 143 timeFormat += " " 144 } 145 timeFormat += "15:04:05.000" 146 } 147 if len(timeFormat) > 0 { 148 input.TimeFormat = now.Format(timeFormat) 149 } 150 151 // Level string. 152 input.LevelFormat = l.getLevelPrefixWithBrackets(level) 153 154 // Caller path and Fn name. 155 if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 { 156 callerFnName, path, line := gdebug.CallerWithFilter([]string{pathFilterKey}, l.config.StSkip) 157 if l.config.Flags&F_CALLER_FN > 0 { 158 if len(callerFnName) > 2 { 159 input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName) 160 } 161 } 162 if line >= 0 && len(path) > 1 { 163 if l.config.Flags&F_FILE_LONG > 0 { 164 input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line) 165 } 166 if l.config.Flags&F_FILE_SHORT > 0 { 167 input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line) 168 } 169 } 170 } 171 // Prefix. 172 if len(l.config.Prefix) > 0 { 173 input.Prefix = l.config.Prefix 174 } 175 } 176 // Convert value to string. 177 if ctx != nil { 178 // Tracing values. 179 spanCtx := trace.SpanContextFromContext(ctx) 180 if traceId := spanCtx.TraceID(); traceId.IsValid() { 181 input.CtxStr = traceId.String() 182 } 183 // Context values. 184 if len(l.config.CtxKeys) > 0 { 185 for _, ctxKey := range l.config.CtxKeys { 186 var ctxValue interface{} 187 if ctxValue = ctx.Value(ctxKey); ctxValue == nil { 188 ctxValue = ctx.Value(gctx.StrKey(gconv.String(ctxKey))) 189 } 190 if ctxValue != nil { 191 if input.CtxStr != "" { 192 input.CtxStr += ", " 193 } 194 input.CtxStr += gconv.String(ctxValue) 195 } 196 } 197 } 198 if input.CtxStr != "" { 199 input.CtxStr = "{" + input.CtxStr + "}" 200 } 201 } 202 var tempStr string 203 for _, v := range values { 204 tempStr = gconv.String(v) 205 if len(input.Content) > 0 { 206 if input.Content[len(input.Content)-1] == '\n' { 207 // Remove one blank line(\n\n). 208 if tempStr[0] == '\n' { 209 input.Content += tempStr[1:] 210 } else { 211 input.Content += tempStr 212 } 213 } else { 214 input.Content += " " + tempStr 215 } 216 } else { 217 input.Content = tempStr 218 } 219 } 220 if l.config.Flags&F_ASYNC > 0 { 221 input.IsAsync = true 222 err := asyncPool.Add(func() { 223 input.Next() 224 }) 225 if err != nil { 226 intlog.Error(ctx, err) 227 } 228 } else { 229 input.Next() 230 } 231 } 232 233 // doDefaultPrint outputs the logging content according configuration. 234 func (l *Logger) doDefaultPrint(ctx context.Context, input *HandlerInput) *bytes.Buffer { 235 var buffer *bytes.Buffer 236 if l.config.Writer == nil { 237 // Allow output to stdout? 238 if l.config.StdoutPrint { 239 if buf := l.printToStdout(ctx, input); buf != nil { 240 buffer = buf 241 } 242 } 243 244 // Output content to disk file. 245 if l.config.Path != "" { 246 if buf := l.printToFile(ctx, input.Time, input); buf != nil { 247 buffer = buf 248 } 249 } 250 } else { 251 // Output to custom writer. 252 if buf := l.printToWriter(ctx, input); buf != nil { 253 buffer = buf 254 } 255 } 256 return buffer 257 } 258 259 // printToWriter writes buffer to writer. 260 func (l *Logger) printToWriter(ctx context.Context, input *HandlerInput) *bytes.Buffer { 261 if l.config.Writer != nil { 262 var ( 263 buffer = input.getRealBuffer(l.config.WriterColorEnable) 264 ) 265 if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil { 266 intlog.Error(ctx, err) 267 } 268 return buffer 269 } 270 return nil 271 } 272 273 // printToStdout outputs logging content to stdout. 274 func (l *Logger) printToStdout(ctx context.Context, input *HandlerInput) *bytes.Buffer { 275 if l.config.StdoutPrint { 276 var ( 277 buffer = input.getRealBuffer(true) 278 ) 279 // This will lose color in Windows os system. 280 // if _, err := os.Stdout.Write(input.getRealBuffer(true).Bytes()); err != nil { 281 282 // This will print color in Windows os system. 283 if _, err := fmt.Fprint(color.Output, buffer.String()); err != nil { 284 intlog.Error(ctx, err) 285 } 286 return buffer 287 } 288 return nil 289 } 290 291 // printToFile outputs logging content to disk file. 292 func (l *Logger) printToFile(ctx context.Context, t time.Time, in *HandlerInput) *bytes.Buffer { 293 var ( 294 buffer = in.getRealBuffer(l.config.WriterColorEnable) 295 logFilePath = l.getFilePath(t) 296 memoryLockKey = memoryLockPrefixForPrintingToFile + logFilePath 297 ) 298 gmlock.Lock(memoryLockKey) 299 defer gmlock.Unlock(memoryLockKey) 300 301 // Rotation file size checks. 302 if l.config.RotateSize > 0 { 303 if gfile.Size(logFilePath) > l.config.RotateSize { 304 l.rotateFileBySize(t) 305 } 306 } 307 // Logging content outputting to disk file. 308 if file := l.getFilePointer(ctx, logFilePath); file == nil { 309 intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath) 310 } else { 311 if _, err := file.Write(buffer.Bytes()); err != nil { 312 intlog.Error(ctx, err) 313 } 314 if err := file.Close(); err != nil { 315 intlog.Error(ctx, err) 316 } 317 } 318 return buffer 319 } 320 321 // getFilePointer retrieves and returns a file pointer from file pool. 322 func (l *Logger) getFilePointer(ctx context.Context, path string) *gfpool.File { 323 file, err := gfpool.Open( 324 path, 325 defaultFileFlags, 326 defaultFilePerm, 327 defaultFileExpire, 328 ) 329 if err != nil { 330 // panic(err) 331 intlog.Error(ctx, err) 332 } 333 return file 334 } 335 336 // getCtx returns the context which is set through chaining operations. 337 // It returns an empty context if no context set previously. 338 func (l *Logger) getCtx() context.Context { 339 if l.ctx != nil { 340 return l.ctx 341 } 342 return context.TODO() 343 } 344 345 // printStd prints content `s` without stack. 346 func (l *Logger) printStd(level int, value ...interface{}) { 347 l.print(l.getCtx(), level, value...) 348 } 349 350 // printStd prints content `s` with stack check. 351 func (l *Logger) printErr(level int, value ...interface{}) { 352 if l.config.StStatus == 1 { 353 if s := l.GetStack(); s != "" { 354 value = append(value, "\nStack:\n"+s) 355 } 356 } 357 // In matter of sequence, do not use stderr here, but use the same stdout. 358 l.print(l.getCtx(), level, value...) 359 } 360 361 // format formats `values` using fmt.Sprintf. 362 func (l *Logger) format(format string, value ...interface{}) string { 363 return fmt.Sprintf(format, value...) 364 } 365 366 // PrintStack prints the caller stack, 367 // the optional parameter `skip` specify the skipped stack offset from the end point. 368 func (l *Logger) PrintStack(skip ...int) { 369 if s := l.GetStack(skip...); s != "" { 370 l.Println("Stack:\n" + s) 371 } else { 372 l.Println() 373 } 374 } 375 376 // GetStack returns the caller stack content, 377 // the optional parameter `skip` specify the skipped stack offset from the end point. 378 func (l *Logger) GetStack(skip ...int) string { 379 stackSkip := l.config.StSkip 380 if len(skip) > 0 { 381 stackSkip += skip[0] 382 } 383 filters := []string{pathFilterKey} 384 if l.config.StFilter != "" { 385 filters = append(filters, l.config.StFilter) 386 } 387 return gdebug.StackWithFilters(filters, stackSkip) 388 } 389 390 // GetConfig returns the configuration of current Logger. 391 func (l *Logger) GetConfig() Config { 392 return l.config 393 }