mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.
**Type alias migration to `any`:**
* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)
**Generated code and templates:**
* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
**Container and utility updates:**
* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)
These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
412 lines
11 KiB
Go
412 lines
11 KiB
Go
// 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"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/gogf/gf/v2/debug/gdebug"
|
|
"github.com/gogf/gf/v2/internal/consts"
|
|
"github.com/gogf/gf/v2/internal/errors"
|
|
"github.com/gogf/gf/v2/internal/intlog"
|
|
"github.com/gogf/gf/v2/os/gctx"
|
|
"github.com/gogf/gf/v2/os/gfile"
|
|
"github.com/gogf/gf/v2/os/gfpool"
|
|
"github.com/gogf/gf/v2/os/gmlock"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/text/gregex"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
// Logger is the struct for logging management.
|
|
type Logger struct {
|
|
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`
|
|
defaultTimeFormat = "2006-01-02T15:04:05.000Z07:00"
|
|
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 {
|
|
return &Logger{
|
|
config: DefaultConfig(),
|
|
}
|
|
}
|
|
|
|
// 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 a `shallow copy` of the current logger.
|
|
// Note that the attribute `config` of the cloned one is the shallow copy of current one.
|
|
func (l *Logger) Clone() *Logger {
|
|
return &Logger{
|
|
config: l.config,
|
|
parent: l,
|
|
}
|
|
}
|
|
|
|
// 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, stack string, values ...any) {
|
|
// Lazy initialize for rotation feature.
|
|
// It uses atomic reading operation to enhance the performance checking.
|
|
// It here uses CAP for performance and concurrent safety.
|
|
// It just initializes once for each logger.
|
|
if l.config.RotateSize > 0 || l.config.RotateExpire > 0 {
|
|
if !l.config.rotatedHandlerInitialized.Val() && l.config.rotatedHandlerInitialized.Cas(false, true) {
|
|
l.rotateChecksTimely(ctx)
|
|
intlog.Printf(ctx, "logger rotation initialized: every %s", l.config.RotateCheckInterval.String())
|
|
}
|
|
}
|
|
|
|
var (
|
|
now = time.Now()
|
|
input = &HandlerInput{
|
|
internalHandlerInfo: internalHandlerInfo{
|
|
index: -1,
|
|
},
|
|
Logger: l,
|
|
Buffer: bytes.NewBuffer(nil),
|
|
Time: now,
|
|
Color: defaultLevelColor[level],
|
|
Level: level,
|
|
Stack: stack,
|
|
Values: values,
|
|
}
|
|
)
|
|
|
|
// Logging handlers.
|
|
if len(l.config.Handlers) > 0 {
|
|
input.handlers = append(input.handlers, l.config.Handlers...)
|
|
} else if defaultHandler != nil {
|
|
input.handlers = []Handler{defaultHandler}
|
|
}
|
|
input.handlers = append(input.handlers, doFinalPrint)
|
|
|
|
// Time.
|
|
timeFormat := ""
|
|
if l.config.TimeFormat != "" {
|
|
timeFormat = l.config.TimeFormat
|
|
} else {
|
|
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.GetLevelPrefix(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{consts.StackFilterKeyForGoFrame},
|
|
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.TraceId = traceId.String()
|
|
}
|
|
// Context values.
|
|
if len(l.config.CtxKeys) > 0 {
|
|
for _, ctxKey := range l.config.CtxKeys {
|
|
var ctxValue any
|
|
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 l.config.Flags&F_ASYNC > 0 {
|
|
input.IsAsync = true
|
|
err := asyncPool.Add(ctx, func(ctx context.Context) {
|
|
input.Next(ctx)
|
|
})
|
|
if err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
} else {
|
|
input.Next(ctx)
|
|
}
|
|
}
|
|
|
|
// doFinalPrint outputs the logging content according configuration.
|
|
func (l *Logger) doFinalPrint(ctx context.Context, input *HandlerInput) *bytes.Buffer {
|
|
var buffer *bytes.Buffer
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// Used custom writer.
|
|
if l.config.Writer != nil {
|
|
// 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.Errorf(ctx, `%+v`, 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 (
|
|
err error
|
|
buffer = input.getRealBuffer(!l.config.StdoutColorDisabled)
|
|
)
|
|
// This will lose color in Windows os system. DO NOT USE.
|
|
// 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.Errorf(ctx, `%+v`, 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 && gfile.Size(logFilePath) > l.config.RotateSize {
|
|
if runtime.GOOS == "windows" {
|
|
file := l.createFpInPool(ctx, logFilePath)
|
|
if file == nil {
|
|
intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath)
|
|
return buffer
|
|
}
|
|
|
|
if _, err := file.Write(buffer.Bytes()); err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
|
|
if err := file.Close(true); err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
l.rotateFileBySize(ctx, t)
|
|
|
|
return buffer
|
|
}
|
|
|
|
l.rotateFileBySize(ctx, t)
|
|
}
|
|
// Logging content outputting to disk file.
|
|
if file := l.createFpInPool(ctx, logFilePath); file == nil {
|
|
intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath)
|
|
} else {
|
|
if _, err := file.Write(buffer.Bytes()); err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
}
|
|
return buffer
|
|
}
|
|
|
|
// createFpInPool retrieves and returns a file pointer from file pool.
|
|
func (l *Logger) createFpInPool(ctx context.Context, path string) *gfpool.File {
|
|
file, err := gfpool.Open(
|
|
path,
|
|
defaultFileFlags,
|
|
defaultFilePerm,
|
|
defaultFileExpire,
|
|
)
|
|
if err != nil {
|
|
// panic(err)
|
|
intlog.Errorf(ctx, `%+v`, err)
|
|
}
|
|
return file
|
|
}
|
|
|
|
// getFpFromPool retrieves and returns a file pointer from file pool.
|
|
func (l *Logger) getFpFromPool(ctx context.Context, path string) *gfpool.File {
|
|
file := gfpool.Get(
|
|
path,
|
|
defaultFileFlags,
|
|
defaultFilePerm,
|
|
defaultFileExpire,
|
|
)
|
|
if file == nil {
|
|
intlog.Errorf(ctx, `can not find the file, path:%s`, path)
|
|
}
|
|
return file
|
|
}
|
|
|
|
// printStd prints content `s` without stack.
|
|
func (l *Logger) printStd(ctx context.Context, level int, values ...any) {
|
|
// nil logger, print nothing
|
|
if l == nil {
|
|
return
|
|
}
|
|
l.print(ctx, level, "", values...)
|
|
}
|
|
|
|
// printErr prints content `s` with stack check.
|
|
func (l *Logger) printErr(ctx context.Context, level int, values ...any) {
|
|
// nil logger, print nothing
|
|
if l == nil {
|
|
return
|
|
}
|
|
var stack string
|
|
if l.config.StStatus == 1 {
|
|
stack = l.GetStack()
|
|
}
|
|
// In matter of sequence, do not use stderr here, but use the same stdout.
|
|
l.print(ctx, level, stack, values...)
|
|
}
|
|
|
|
// format formats `values` using fmt.Sprintf.
|
|
func (l *Logger) format(format string, values ...any) string {
|
|
return fmt.Sprintf(format, values...)
|
|
}
|
|
|
|
// PrintStack prints the caller stack,
|
|
// the optional parameter `skip` specify the skipped stack offset from the end point.
|
|
func (l *Logger) PrintStack(ctx context.Context, skip ...int) {
|
|
if s := l.GetStack(skip...); s != "" {
|
|
l.Print(ctx, "Stack:\n"+s)
|
|
} else {
|
|
l.Print(ctx)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
// Whether filter framework error stacks.
|
|
if errors.IsStackModeBrief() {
|
|
filters = append(filters, consts.StackFilterKeyForGoFrame)
|
|
}
|
|
return gdebug.StackWithFilters(filters, stackSkip)
|
|
}
|