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

     1  package log
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/dustin/go-humanize"
    12  	"github.com/pkg/errors"
    13  	"go.uber.org/zap"
    14  	"go.uber.org/zap/zapcore"
    15  
    16  	"github.com/wfusion/gofusion/common/constant"
    17  	"github.com/wfusion/gofusion/common/di"
    18  	"github.com/wfusion/gofusion/common/env"
    19  	"github.com/wfusion/gofusion/common/infra/rotatelog"
    20  	"github.com/wfusion/gofusion/common/utils"
    21  	"github.com/wfusion/gofusion/config"
    22  )
    23  
    24  func Construct(ctx context.Context, confs map[string]*Conf, opts ...utils.OptionExtender) func() {
    25  	opt := utils.ApplyOptions[config.InitOption](opts...)
    26  	optU := utils.ApplyOptions[useOption](opts...)
    27  	if opt.AppName == "" {
    28  		opt.AppName = optU.appName
    29  	}
    30  	for name, conf := range confs {
    31  		addInstance(ctx, name, conf, opt)
    32  	}
    33  
    34  	if opt.AppName == "" && appInstances[opt.AppName] != nil {
    35  		if conf, ok := appInstances[opt.AppName][config.DefaultInstanceKey]; !ok || conf == nil {
    36  			panic(ErrDefaultLoggerNotFound)
    37  		}
    38  	}
    39  
    40  	return func() {
    41  		rwlock.Lock()
    42  		defer rwlock.Unlock()
    43  		if appInstances != nil {
    44  			for _, instance := range appInstances[opt.AppName] {
    45  				instance.flush()
    46  			}
    47  			delete(appInstances, opt.AppName)
    48  		}
    49  
    50  		// there maybe some locally logging, avoid some NPE crash as possible as we can do
    51  		colorful := false
    52  		if opt.AppName == "" {
    53  			if confs != nil && confs[config.DefaultInstanceKey] != nil {
    54  				colorful = confs[config.DefaultInstanceKey].ConsoleOutputOption.Colorful
    55  			}
    56  			globalLogger = defaultLogger(colorful)
    57  		}
    58  	}
    59  }
    60  
    61  func addInstance(ctx context.Context, name string, conf *Conf, opt *config.InitOption) {
    62  	if !conf.EnableFileOutput && !conf.EnableConsoleOutput {
    63  		panic(ErrUnknownOutput)
    64  	}
    65  
    66  	var cores []zapcore.Core
    67  	if conf.EnableConsoleOutput {
    68  		cfg := getEncoderConfig(conf)
    69  		if conf.ConsoleOutputOption.Colorful {
    70  			cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
    71  		}
    72  		encoder := getEncoder(conf.ConsoleOutputOption.Layout, cfg)
    73  		writer := zapcore.Lock(os.Stdout)
    74  		logLevel := newZapLogLevel(opt.AppName, name, "enable_console_output", "log_level")
    75  		cores = append(cores, zapcore.NewCore(encoder, writer, logLevel))
    76  	}
    77  	if conf.EnableFileOutput {
    78  		var (
    79  			ext     = ".log"
    80  			logName string
    81  		)
    82  
    83  		cfg := getEncoderConfig(conf)
    84  		encoder := getEncoder(conf.FileOutputOption.Layout, cfg)
    85  		utils.IfAny(
    86  			func() bool { logName = conf.FileOutputOption.Name; return utils.IsStrNotBlank(logName) },
    87  			func() bool { logName = config.Use(opt.AppName).AppName() + ext; return utils.IsStrNotBlank(logName) },
    88  			func() bool {
    89  				logName = filepath.Base(env.WorkDir) + ext
    90  				return logName != constant.PathSeparator+".log"
    91  			},
    92  			func() bool {
    93  				sum := md5.Sum([]byte(env.WorkDir))
    94  				logName = string(sum[:]) + ext
    95  				return true
    96  			},
    97  		)
    98  
    99  		rotationSize, err := humanize.ParseBytes(conf.FileOutputOption.RotationSize)
   100  		if err != nil {
   101  			panic(errors.Errorf("log component parse ratation size %s failed for name %s: %s",
   102  				conf.FileOutputOption.RotationSize, name, err))
   103  		}
   104  
   105  		maxAge, err := time.ParseDuration(conf.FileOutputOption.RotationMaxAge)
   106  		if err != nil {
   107  			panic(errors.Errorf("log component parse ratation time %s failed for name %s: %s",
   108  				conf.FileOutputOption.RotationMaxAge, name, err))
   109  		}
   110  
   111  		writer := zapcore.AddSync(&rotatelog.Logger{
   112  			Filename:   path.Join(filepath.Clean(conf.FileOutputOption.Path), logName),
   113  			MaxSize:    int64(rotationSize),
   114  			MaxBackups: conf.FileOutputOption.RotationCount,
   115  			MaxAge:     maxAge,
   116  			Compress:   conf.FileOutputOption.Compress,
   117  		})
   118  		logLevel := newZapLogLevel(opt.AppName, name, "enable_file_output", "log_level")
   119  		cores = append(cores, zapcore.NewCore(encoder, writer, logLevel))
   120  	}
   121  
   122  	zopts := []zap.Option{
   123  		zap.Hooks(),
   124  		zap.AddCaller(),
   125  		zap.AddStacktrace(newZapLogLevel(opt.AppName, name, "enable_file_output", "stacktrace_level")),
   126  	}
   127  	if config.Use(opt.AppName).Debug() {
   128  		zopts = append(zopts, zap.Development())
   129  	}
   130  
   131  	zapLogger := zap.
   132  		New(zapcore.NewTee(cores...), zopts...).
   133  		Named(config.Use(opt.AppName).AppName())
   134  
   135  	fusLogger := &logger{name: name, logger: zapLogger, sugaredLogger: zapLogger.Sugar()}
   136  
   137  	rwlock.Lock()
   138  	defer rwlock.Unlock()
   139  	if appInstances == nil {
   140  		appInstances = make(map[string]map[string]*logger)
   141  	}
   142  	if appInstances[opt.AppName] == nil {
   143  		appInstances[opt.AppName] = make(map[string]*logger)
   144  	}
   145  	if _, ok := appInstances[opt.AppName][name]; ok {
   146  		panic(ErrDuplicatedName)
   147  	}
   148  	appInstances[opt.AppName][name] = fusLogger
   149  
   150  	if opt.AppName == "" && name == config.DefaultInstanceKey {
   151  		globalLogger = fusLogger
   152  	}
   153  
   154  	// ioc
   155  	if opt.DI != nil {
   156  		opt.DI.MustProvide(
   157  			func() Loggable { return Use(name, AppName(opt.AppName)) },
   158  			di.Name(name),
   159  		)
   160  	}
   161  }
   162  
   163  func init() {
   164  	config.AddComponent(config.ComponentLog, Construct, config.WithFlag(&flagString))
   165  }