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  }