123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
- //
- // This Source Code Form is subject to the terms of the MIT License.
- // If a copy of the MIT was not distributed with this file,
- // You can obtain one at https://github.com/gogf/gf.
- package glog
- import (
- "bytes"
- "context"
- "fmt"
- "io"
- "os"
- "strings"
- "time"
- "github.com/fatih/color"
- "github.com/gogf/gf/container/gtype"
- "github.com/gogf/gf/internal/intlog"
- "github.com/gogf/gf/os/gctx"
- "github.com/gogf/gf/os/gfpool"
- "github.com/gogf/gf/os/gmlock"
- "github.com/gogf/gf/os/gtimer"
- "go.opentelemetry.io/otel/trace"
- "github.com/gogf/gf/debug/gdebug"
- "github.com/gogf/gf/os/gfile"
- "github.com/gogf/gf/os/gtime"
- "github.com/gogf/gf/text/gregex"
- "github.com/gogf/gf/util/gconv"
- )
- // Logger is the struct for logging management.
- type Logger struct {
- ctx context.Context // Context for logging.
- init *gtype.Bool // Initialized.
- parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
- config Config // Logger configuration.
- }
- const (
- defaultFileFormat = `{Y-m-d}.log`
- defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
- defaultFilePerm = os.FileMode(0666)
- defaultFileExpire = time.Minute
- pathFilterKey = "/os/glog/glog"
- memoryLockPrefixForPrintingToFile = "glog.printToFile:"
- )
- const (
- F_ASYNC = 1 << iota // Print logging content asynchronously。
- F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
- F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
- F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
- F_TIME_TIME // Print the time in the local time zone: 01:23:23.
- F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
- F_CALLER_FN // Print Caller function name and package: main.main
- F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
- )
- // New creates and returns a custom logger.
- func New() *Logger {
- logger := &Logger{
- init: gtype.NewBool(),
- config: DefaultConfig(),
- }
- return logger
- }
- // NewWithWriter creates and returns a custom logger with io.Writer.
- func NewWithWriter(writer io.Writer) *Logger {
- l := New()
- l.SetWriter(writer)
- return l
- }
- // Clone returns a new logger, which is the clone the current logger.
- // It's commonly used for chaining operations.
- func (l *Logger) Clone() *Logger {
- newLogger := New()
- newLogger.ctx = l.ctx
- newLogger.config = l.config
- newLogger.parent = l
- return newLogger
- }
- // getFilePath returns the logging file path.
- // The logging file name must have extension name of "log".
- func (l *Logger) getFilePath(now time.Time) string {
- // Content containing "{}" in the file name is formatted using gtime.
- file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string {
- return gtime.New(now).Format(strings.Trim(s, "{}"))
- })
- file = gfile.Join(l.config.Path, file)
- return file
- }
- // print prints `s` to defined writer, logging file or passed `std`.
- func (l *Logger) print(ctx context.Context, level int, values ...interface{}) {
- // Lazy initialize for rotation feature.
- // It uses atomic reading operation to enhance the performance checking.
- // It here uses CAP for performance and concurrent safety.
- p := l
- if p.parent != nil {
- p = p.parent
- }
- // It just initializes once for each logger.
- if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
- if !p.init.Val() && p.init.Cas(false, true) {
- gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
- intlog.Printf(ctx, "logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
- }
- }
- var (
- now = time.Now()
- input = &HandlerInput{
- Logger: l,
- Buffer: bytes.NewBuffer(nil),
- Ctx: ctx,
- Time: now,
- Color: defaultLevelColor[level],
- Level: level,
- handlerIndex: -1,
- }
- )
- if l.config.HeaderPrint {
- // Time.
- timeFormat := ""
- if l.config.Flags&F_TIME_DATE > 0 {
- timeFormat += "2006-01-02"
- }
- if l.config.Flags&F_TIME_TIME > 0 {
- if timeFormat != "" {
- timeFormat += " "
- }
- timeFormat += "15:04:05"
- }
- if l.config.Flags&F_TIME_MILLI > 0 {
- if timeFormat != "" {
- timeFormat += " "
- }
- timeFormat += "15:04:05.000"
- }
- if len(timeFormat) > 0 {
- input.TimeFormat = now.Format(timeFormat)
- }
- // Level string.
- input.LevelFormat = l.getLevelPrefixWithBrackets(level)
- // Caller path and Fn name.
- if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
- callerFnName, path, line := gdebug.CallerWithFilter([]string{pathFilterKey}, l.config.StSkip)
- if l.config.Flags&F_CALLER_FN > 0 {
- if len(callerFnName) > 2 {
- input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName)
- }
- }
- if line >= 0 && len(path) > 1 {
- if l.config.Flags&F_FILE_LONG > 0 {
- input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line)
- }
- if l.config.Flags&F_FILE_SHORT > 0 {
- input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line)
- }
- }
- }
- // Prefix.
- if len(l.config.Prefix) > 0 {
- input.Prefix = l.config.Prefix
- }
- }
- // Convert value to string.
- if ctx != nil {
- // Tracing values.
- spanCtx := trace.SpanContextFromContext(ctx)
- if traceId := spanCtx.TraceID(); traceId.IsValid() {
- input.CtxStr = traceId.String()
- }
- // Context values.
- if len(l.config.CtxKeys) > 0 {
- for _, ctxKey := range l.config.CtxKeys {
- var ctxValue interface{}
- if ctxValue = ctx.Value(ctxKey); ctxValue == nil {
- ctxValue = ctx.Value(gctx.StrKey(gconv.String(ctxKey)))
- }
- if ctxValue != nil {
- if input.CtxStr != "" {
- input.CtxStr += ", "
- }
- input.CtxStr += gconv.String(ctxValue)
- }
- }
- }
- if input.CtxStr != "" {
- input.CtxStr = "{" + input.CtxStr + "}"
- }
- }
- var tempStr string
- for _, v := range values {
- tempStr = gconv.String(v)
- if len(input.Content) > 0 {
- if input.Content[len(input.Content)-1] == '\n' {
- // Remove one blank line(\n\n).
- if tempStr[0] == '\n' {
- input.Content += tempStr[1:]
- } else {
- input.Content += tempStr
- }
- } else {
- input.Content += " " + tempStr
- }
- } else {
- input.Content = tempStr
- }
- }
- if l.config.Flags&F_ASYNC > 0 {
- input.IsAsync = true
- err := asyncPool.Add(func() {
- input.Next()
- })
- if err != nil {
- intlog.Error(ctx, err)
- }
- } else {
- input.Next()
- }
- }
- // doDefaultPrint outputs the logging content according configuration.
- func (l *Logger) doDefaultPrint(ctx context.Context, input *HandlerInput) *bytes.Buffer {
- var buffer *bytes.Buffer
- if l.config.Writer == nil {
- // Allow output to stdout?
- if l.config.StdoutPrint {
- if buf := l.printToStdout(ctx, input); buf != nil {
- buffer = buf
- }
- }
- // Output content to disk file.
- if l.config.Path != "" {
- if buf := l.printToFile(ctx, input.Time, input); buf != nil {
- buffer = buf
- }
- }
- } else {
- // Output to custom writer.
- if buf := l.printToWriter(ctx, input); buf != nil {
- buffer = buf
- }
- }
- return buffer
- }
- // printToWriter writes buffer to writer.
- func (l *Logger) printToWriter(ctx context.Context, input *HandlerInput) *bytes.Buffer {
- if l.config.Writer != nil {
- var (
- buffer = input.getRealBuffer(l.config.WriterColorEnable)
- )
- if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
- intlog.Error(ctx, err)
- }
- return buffer
- }
- return nil
- }
- // printToStdout outputs logging content to stdout.
- func (l *Logger) printToStdout(ctx context.Context, input *HandlerInput) *bytes.Buffer {
- if l.config.StdoutPrint {
- var (
- buffer = input.getRealBuffer(true)
- )
- // This will lose color in Windows os system.
- // if _, err := os.Stdout.Write(input.getRealBuffer(true).Bytes()); err != nil {
- // This will print color in Windows os system.
- if _, err := fmt.Fprint(color.Output, buffer.String()); err != nil {
- intlog.Error(ctx, err)
- }
- return buffer
- }
- return nil
- }
- // printToFile outputs logging content to disk file.
- func (l *Logger) printToFile(ctx context.Context, t time.Time, in *HandlerInput) *bytes.Buffer {
- var (
- buffer = in.getRealBuffer(l.config.WriterColorEnable)
- logFilePath = l.getFilePath(t)
- memoryLockKey = memoryLockPrefixForPrintingToFile + logFilePath
- )
- gmlock.Lock(memoryLockKey)
- defer gmlock.Unlock(memoryLockKey)
- // Rotation file size checks.
- if l.config.RotateSize > 0 {
- if gfile.Size(logFilePath) > l.config.RotateSize {
- l.rotateFileBySize(t)
- }
- }
- // Logging content outputting to disk file.
- if file := l.getFilePointer(ctx, logFilePath); file == nil {
- intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath)
- } else {
- if _, err := file.Write(buffer.Bytes()); err != nil {
- intlog.Error(ctx, err)
- }
- if err := file.Close(); err != nil {
- intlog.Error(ctx, err)
- }
- }
- return buffer
- }
- // getFilePointer retrieves and returns a file pointer from file pool.
- func (l *Logger) getFilePointer(ctx context.Context, path string) *gfpool.File {
- file, err := gfpool.Open(
- path,
- defaultFileFlags,
- defaultFilePerm,
- defaultFileExpire,
- )
- if err != nil {
- // panic(err)
- intlog.Error(ctx, err)
- }
- return file
- }
- // getCtx returns the context which is set through chaining operations.
- // It returns an empty context if no context set previously.
- func (l *Logger) getCtx() context.Context {
- if l.ctx != nil {
- return l.ctx
- }
- return context.TODO()
- }
- // printStd prints content `s` without stack.
- func (l *Logger) printStd(level int, value ...interface{}) {
- l.print(l.getCtx(), level, value...)
- }
- // printStd prints content `s` with stack check.
- func (l *Logger) printErr(level int, value ...interface{}) {
- if l.config.StStatus == 1 {
- if s := l.GetStack(); s != "" {
- value = append(value, "\nStack:\n"+s)
- }
- }
- // In matter of sequence, do not use stderr here, but use the same stdout.
- l.print(l.getCtx(), level, value...)
- }
- // format formats `values` using fmt.Sprintf.
- func (l *Logger) format(format string, value ...interface{}) string {
- return fmt.Sprintf(format, value...)
- }
- // PrintStack prints the caller stack,
- // the optional parameter `skip` specify the skipped stack offset from the end point.
- func (l *Logger) PrintStack(skip ...int) {
- if s := l.GetStack(skip...); s != "" {
- l.Println("Stack:\n" + s)
- } else {
- l.Println()
- }
- }
- // GetStack returns the caller stack content,
- // the optional parameter `skip` specify the skipped stack offset from the end point.
- func (l *Logger) GetStack(skip ...int) string {
- stackSkip := l.config.StSkip
- if len(skip) > 0 {
- stackSkip += skip[0]
- }
- filters := []string{pathFilterKey}
- if l.config.StFilter != "" {
- filters = append(filters, l.config.StFilter)
- }
- return gdebug.StackWithFilters(filters, stackSkip)
- }
- // GetConfig returns the configuration of current Logger.
- func (l *Logger) GetConfig() Config {
- return l.config
- }
|