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  }