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 }