// 日志库,参考: // https://blog.csdn.net/keenw/article/details/125325512 // https://blog.csdn.net/weixin_52690231/article/details/124879087 // https://blog.csdn.net/qq_34272964/article/details/127046386 package flog import ( "fmt" gls2 "github.com/v2pro/plz/gls" . "gls/lib/gls-server-core/core/core_conf" "os" "runtime/debug" "strings" "time" "github.com/IBM/sarama" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type LogKafka struct { Topic string Producer sarama.SyncProducer Partition int32 } func (lk *LogKafka) Write(p []byte) (n int, err error) { // 构建消息 msg := &sarama.ProducerMessage{ Topic: lk.Topic, Value: sarama.ByteEncoder(p), Partition: lk.Partition, } // 发现消息 _, _, err = lk.Producer.SendMessage(msg) if err != nil { return } return } var Logger *zap.Logger var Log *zap.SugaredLogger var callerPathSliceIndex = 0 func NewLogConfig() *LogConfig { cfg := &LogConfig{Stdout: "stdout"} cfg.LogFileConfig.Enable = false cfg.LogFileConfig.Filename = "" cfg.LogFileConfig.MaxSize = 1 cfg.LogFileConfig.MaxBackups = 5 cfg.LogFileConfig.MaxAge = 30 cfg.LogKafkaConfig.Enable = false cfg.LogKafkaConfig.Topic = "Log" cfg.LogKafkaConfig.Partition = 1 return cfg } func InitLogger() { InitLoggerWithConfig(LogConfig{Stdout: "stdout"}) } func InitLoggerErr() { InitLoggerWithConfig(LogConfig{Stdout: "stderr"}) } func InitLoggerWithConfig(cfg LogConfig) { cwd, _ := os.Getwd() callerPathSliceIndex = strings.LastIndex(cwd, "/src/") if callerPathSliceIndex < 0 { callerPathSliceIndex = strings.LastIndex(cwd, "\\src\\") } if callerPathSliceIndex < 0 { callerPathSliceIndex = strings.LastIndex(cwd, "\\lib\\") } if callerPathSliceIndex < 0 { callerPathSliceIndex = len(cwd) } isDebugMode := false if cwd[0] != '/' { isDebugMode = true // win 调试,必须有控制台 if cfg.Stdout != "stdout" && cfg.Stdout != "stderr" { cfg.Stdout = "stdout" } } // writeSyncer := getLogWriter(cfg) // encoder := getEncoder() // core := zapcore.NewCore(encoder, writeSyncer, lv) // logger := zap.New(core, zap.AddCaller()) var ( err error allCore []zapcore.Core core zapcore.Core ) lv := getLogLevel(cfg.Level) // 日志是输出终端 if isDebugMode { encoderCfg := zap.NewDevelopmentEncoderConfig() encoderCfg.EncodeTime = getTimeEncoder // encoderCfg.EncodeLevel = zapcore.CapitalLevelEncoder encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder encoderCfg.EncodeCaller = getCallerEncoder encoderCfg.ConsoleSeparator = " " consoleEncoder := zapcore.NewConsoleEncoder(encoderCfg) // consoleEncoder := getEncoder() var writeSyncer zapcore.WriteSyncer if cfg.Stdout == "stderr" { writeSyncer = zapcore.Lock(os.Stderr) } else { writeSyncer = zapcore.Lock(os.Stdout) } allCore = append(allCore, zapcore.NewCore(consoleEncoder, writeSyncer, lv)) } else { encoder := getEncoder() var StdoutWriter zapcore.WriteSyncer if len(cfg.Stdout) > 0 { cWriter, close, err := zap.Open(cfg.Stdout) if err != nil { close() StdoutWriter = os.Stdout } else { StdoutWriter = cWriter } } writeSyncer := zapcore.AddSync(StdoutWriter) allCore = append(allCore, zapcore.NewCore(encoder, writeSyncer, lv)) } if cfg.LogFileConfig.Enable { encoder := getEncoder() lumberJackLogger := &lumberjack.Logger{ Filename: cfg.LogFileConfig.Filename, MaxSize: cfg.LogFileConfig.MaxSize, MaxBackups: cfg.LogFileConfig.MaxBackups, MaxAge: cfg.LogFileConfig.MaxAge, Compress: cfg.LogFileConfig.Compress, } writeSyncer := zapcore.AddSync(lumberJackLogger) lv := getLogLevel(cfg.LogFileConfig.Level) allCore = append(allCore, zapcore.NewCore(encoder, writeSyncer, lv)) } if cfg.LogKafkaConfig.Enable { // 日志输出kafka // kafka配置 config := sarama.NewConfig() // 设置日志输入到Kafka的配置 config.Producer.RequiredAcks = sarama.WaitForAll // 等待服务器所有副本都保存成功后的响应 //config.Producer.Partitioner = sarama.NewRandomPartitioner // 随机的分区类型 config.Producer.Return.Successes = true // 是否等待成功后的响应,只有上面的RequiredAcks设置不是NoReponse这里才有用. config.Producer.Return.Errors = true // 是否等待失败后的响应,只有上面的RequireAcks设置不是NoReponse这里才有用. // kafka连接 var kl LogKafka kl.Topic = cfg.LogKafkaConfig.Topic // Topic(话题):Kafka中用于区分不同类别信息的类别名称。由producer指定 kl.Partition = cfg.LogKafkaConfig.Partition // Partition(分区):Topic物理上的分组,一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset) kl.Producer, err = sarama.NewSyncProducer(cfg.LogKafkaConfig.Addrs, config) if err != nil { panic(fmt.Sprintf("connect kafka failed: %+v\n", err)) } encoder := getEncoder() writeSyncer := zapcore.AddSync(&kl) allCore = append(allCore, zapcore.NewCore(encoder, writeSyncer, getLogLevel(cfg.LogKafkaConfig.Level))) } core = zapcore.NewTee(allCore...) Logger = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)) Log = Logger.Sugar() zap.ReplaceGlobals(Logger) Log.Infof(cwd) } func getLogLevel(level string) zapcore.Level { lv, err := zapcore.ParseLevel(level) if err != nil { lv = zapcore.DebugLevel } return lv } func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = getTimeEncoder encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder encoderConfig.EncodeCaller = getCallerEncoder return zapcore.NewConsoleEncoder(encoderConfig) } func getTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05.000")) } func getCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { // TODO: consider using a byte-oriented API to save an allocation. var strCaller string if !caller.Defined { strCaller = "undefined" } else if caller.File[0] == '/' { strCaller = fmt.Sprintf("gid: %d %s:%d", goId(), caller.File, caller.Line) } else { strCaller = fmt.Sprintf("gid: %d .%s:%d", goId(), caller.File[callerPathSliceIndex:], caller.Line) } enc.AppendString(strCaller) } // goId 当前goruntinue的id func goId() int64 { return gls2.GoID() } func getLogWriter(cfg LogConfig) zapcore.WriteSyncer { var StdoutWriter zapcore.WriteSyncer if len(cfg.Stdout) > 0 { cWriter, close, err := zap.Open(cfg.Stdout) if err != nil { close() StdoutWriter = os.Stdout } else { StdoutWriter = cWriter } } if cfg.LogFileConfig.Enable { lumberJackLogger := &lumberjack.Logger{ Filename: cfg.LogFileConfig.Filename, MaxSize: cfg.LogFileConfig.MaxSize, MaxBackups: cfg.LogFileConfig.MaxBackups, MaxAge: cfg.LogFileConfig.MaxAge, Compress: cfg.LogFileConfig.Compress, } // return zapcore.AddSync(lumberJackLogger) if StdoutWriter != nil { return zapcore.NewMultiWriteSyncer(StdoutWriter, zapcore.AddSync(lumberJackLogger)) } return zapcore.NewMultiWriteSyncer(zapcore.AddSync(lumberJackLogger)) } else { return zapcore.NewMultiWriteSyncer(StdoutWriter) } } // TraceError prints the stack and error func GetStack() string { ss := strings.Split(string(debug.Stack()), "\n") s := strings.Join(append(ss[:1], ss[5:]...), "\n") return s } func GetCaller() string { ss := strings.Split(string(debug.Stack()), "\n") s := strings.Join(ss[5:7], " ") return strings.Replace(s, "\t", "", 1) } func TraceError(format string, args ...interface{}) { Log.Error(string(GetStack())) Log.Errorf(format, args...) }