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