github.com/wfusion/gofusion@v1.1.14/log/logger.go (about)

     1  package log
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"sync"
     8  	"syscall"
     9  
    10  	"github.com/pkg/errors"
    11  	"go.uber.org/zap"
    12  	"go.uber.org/zap/zapcore"
    13  
    14  	"github.com/wfusion/gofusion/common/infra/watermill"
    15  	"github.com/wfusion/gofusion/common/utils"
    16  	"github.com/wfusion/gofusion/config"
    17  	"github.com/wfusion/gofusion/log/encoder"
    18  
    19  	fusCtx "github.com/wfusion/gofusion/context"
    20  )
    21  
    22  var (
    23  	globalLogger = defaultLogger(false)
    24  
    25  	rwlock       = new(sync.RWMutex)
    26  	appInstances map[string]map[string]*logger
    27  )
    28  
    29  type logger struct {
    30  	name          string
    31  	logger        *zap.Logger
    32  	sugaredLogger *zap.SugaredLogger
    33  }
    34  
    35  type useOption struct {
    36  	appName string
    37  }
    38  
    39  func AppName(name string) utils.OptionFunc[useOption] {
    40  	return func(o *useOption) {
    41  		o.appName = name
    42  	}
    43  }
    44  
    45  func Use(name string, opts ...utils.OptionExtender) Loggable {
    46  	opt := utils.ApplyOptions[useOption](opts...)
    47  
    48  	rwlock.RLock()
    49  	defer rwlock.RUnlock()
    50  	instances, ok := appInstances[opt.appName]
    51  	if !ok {
    52  		globalLogger.Debug(context.Background(), "%v [Gofusion] %s instance not found for app: %s",
    53  			syscall.Getpid(), config.ComponentLog, opt.appName, Fields{"component": "log"})
    54  		return globalLogger
    55  	}
    56  	instance, ok := instances[name]
    57  	if !ok {
    58  		instance, ok = instances[config.DefaultInstanceKey]
    59  		if ok {
    60  			instance.Debug(context.Background(), "%v [Gofusion] %s instance not found for name: %s",
    61  				syscall.Getpid(), config.ComponentLog, name, Fields{"component": "log"})
    62  			return instance
    63  		}
    64  		globalLogger.Debug(context.Background(), "%v [Gofusion] %s instance not found for name: %s",
    65  			syscall.Getpid(), config.ComponentLog, name, Fields{"component": "log"})
    66  		return globalLogger
    67  	}
    68  
    69  	return instance
    70  }
    71  
    72  func defaultLogger(colorful bool) Loggable {
    73  	devCfg := zap.NewDevelopmentConfig()
    74  	devCfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
    75  	if colorful {
    76  		devCfg.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
    77  	}
    78  	devCfg.EncoderConfig.EncodeCaller = encoder.SkipCallerEncoder(encoder.SkipCallers, true)
    79  	zapLogger, _ := devCfg.Build(
    80  		zap.AddStacktrace(zap.PanicLevel),
    81  		zap.AddCaller(),
    82  		zap.Hooks(),
    83  	)
    84  	return &logger{
    85  		logger:        zapLogger,
    86  		sugaredLogger: zapLogger.Sugar(),
    87  	}
    88  }
    89  func (l *logger) Debug(ctx context.Context, format string, args ...any) {
    90  	lg, msg, fields := l.sweeten(ctx, format, args...)
    91  	if logger, ok := lg.(*logger); ok {
    92  		logger.logger.Debug(msg, fields...)
    93  	} else {
    94  		lg.Debug(ctx, msg, args...)
    95  	}
    96  }
    97  func (l *logger) Info(ctx context.Context, format string, args ...any) {
    98  	lg, msg, fields := l.sweeten(ctx, format, args...)
    99  	if logger, ok := lg.(*logger); ok {
   100  		logger.logger.Info(msg, fields...)
   101  	} else {
   102  		lg.Info(ctx, msg, args...)
   103  	}
   104  }
   105  func (l *logger) Warn(ctx context.Context, format string, args ...any) {
   106  	lg, msg, fields := l.sweeten(ctx, format, args...)
   107  	if logger, ok := lg.(*logger); ok {
   108  		logger.logger.Warn(msg, fields...)
   109  	} else {
   110  		lg.Warn(ctx, msg, args...)
   111  	}
   112  }
   113  func (l *logger) Error(ctx context.Context, format string, args ...any) {
   114  	lg, msg, fields := l.sweeten(ctx, format, args...)
   115  	if logger, ok := lg.(*logger); ok {
   116  		logger.logger.Error(msg, fields...)
   117  	} else {
   118  		lg.Error(ctx, msg, args...)
   119  	}
   120  }
   121  func (l *logger) Panic(ctx context.Context, format string, args ...any) {
   122  	lg, msg, fields := l.sweeten(ctx, format, args...)
   123  	if logger, ok := lg.(*logger); ok {
   124  		logger.logger.Panic(msg, fields...)
   125  	} else {
   126  		lg.Panic(ctx, msg, args...)
   127  	}
   128  }
   129  func (l *logger) Fatal(ctx context.Context, format string, args ...any) {
   130  	lg, msg, fields := l.sweeten(ctx, format, args...)
   131  	if logger, ok := lg.(*logger); ok {
   132  		logger.logger.Fatal(msg, fields...)
   133  	} else {
   134  		lg.Fatal(ctx, msg, args...)
   135  	}
   136  }
   137  func (l *logger) Level(ctx context.Context) Level {
   138  	ctxLogger := GetCtxLogger(ctx, l)
   139  	lg, ok := ctxLogger.(*logger)
   140  	if !ok {
   141  		return InvalidLevel
   142  	}
   143  	switch lg.logger.Level() {
   144  	case zap.DebugLevel:
   145  		return DebugLevel
   146  	case zap.InfoLevel:
   147  		return InfoLevel
   148  	case zap.WarnLevel:
   149  		return WarnLevel
   150  	case zap.ErrorLevel:
   151  		return ErrorLevel
   152  	case zap.PanicLevel:
   153  		return PanicLevel
   154  	case zap.FatalLevel:
   155  		return FatalLevel
   156  	default:
   157  		return InfoLevel
   158  	}
   159  }
   160  func (l *logger) flush() {
   161  	ignore := func(err error) bool {
   162  		// ENOTTY:
   163  		//     ignore sync /dev/stdout: inappropriate ioctl for device errors,
   164  		//     which happens when redirect stderr to stdout
   165  		// EINVAL:
   166  		//     ignore sync /dev/stdout: invalid argument
   167  		for _, target := range []error{syscall.EINVAL, syscall.ENOTTY} {
   168  			if errors.Is(err, target) {
   169  				return true
   170  			}
   171  		}
   172  		return false
   173  	}
   174  
   175  	pid := syscall.Getpid()
   176  	if _, err := utils.Catch(l.logger.Sync); err != nil && !ignore(err) {
   177  		log.Printf("%v [Gofusion] %s flush %s logger error: %s", pid, config.ComponentLog, l.name, err)
   178  	}
   179  	if _, err := utils.Catch(l.sugaredLogger.Sync); err != nil && !ignore(err) {
   180  		log.Printf("%v [Gofusion] %s flush %s sugared logger error: %s",
   181  			pid, config.ComponentLog, l.name, err)
   182  	}
   183  }
   184  
   185  func (l *logger) sweeten(ctx context.Context, format string, raw ...any) (
   186  	log Loggable, msg string, fields []zap.Field) {
   187  	args := make([]any, 0, len(raw))
   188  	fields = getContextZapFields(ctx)
   189  	for _, arg := range raw {
   190  		if f, ok := arg.(Fields); ok {
   191  			fields = append(fields, convertFieldsToZapFields(f)...)
   192  			continue
   193  		}
   194  		args = append(args, arg)
   195  	}
   196  
   197  	msg = fmt.Sprintf(format, args...)
   198  	if userID := fusCtx.GetUserID(ctx); utils.IsStrNotBlank(userID) {
   199  		fields = append(fields, zap.String("user_id", userID))
   200  	}
   201  	if traceID := fusCtx.GetTraceID(ctx); utils.IsStrNotBlank(traceID) {
   202  		fields = append(fields, zap.String("trace_id", traceID))
   203  	}
   204  	if taskID := fusCtx.GetCronTaskID(ctx); utils.IsStrNotBlank(taskID) {
   205  		fields = append(fields, zap.String("cron_task_id", taskID))
   206  	}
   207  	if taskName := fusCtx.GetCronTaskName(ctx); utils.IsStrNotBlank(taskName) {
   208  		fields = append(fields, zap.String("cron_task_name", taskName))
   209  	}
   210  	if id := utils.GetCtxAny[string](ctx, watermill.ContextKeyMessageUUID); utils.IsStrNotBlank(id) {
   211  		fields = append(fields, zap.String("message_uuid", id))
   212  	}
   213  	if id := utils.GetCtxAny[string](ctx, watermill.ContextKeyRawMessageID); utils.IsStrNotBlank(id) {
   214  		fields = append(fields, zap.String("message_raw_id", id))
   215  	}
   216  
   217  	log = GetCtxLogger(ctx, l)
   218  	return
   219  }