//  Copyright(C) 2021. Huawei Technologies Co.,Ltd.  All rights reserved.

// Package hwlog provides the capability of processing Huawei log rules.
package hwlog

import (
"fmt"
"github.com/fsnotify/fsnotify"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"path"
"regexp"
)

const (
// DefaultFileMaxSize the default maximum size of a single log file is 20 MB
DefaultFileMaxSize = 20
// DefaultMinSaveAge the minimum storage duration of backup logs is 7 days
DefaultMinSaveAge = 7
// DefaultMaxBackups the default number of backup log
DefaultMaxBackups = 30
// LogFileMode log file mode
LogFileMode os.FileMode = 0640
// BackupLogFileMode backup log file mode
BackupLogFileMode os.FileMode = 0400
// LogDirMode log dir mode
LogDirMode = 0750
backUpLogRegex = `^.+-[0-9]{4}-[0-9]{2}-[0-9T]{5}-[0-9]{2}-[0-9]{2}\.[0-9]{2,4}`
bitsize = 64
stackDeep = 3
pathLen = 2
minLogLevel = -1
maxLogLevel = 5
)

// LogConfig log module config
type LogConfig struct {
// log file path
LogFileName string
// only write to std out, default value: false
OnlyToStdout bool
// log level, -1-debug, 0-info, 1-warning, 2-error, 3-dpanic, 4-panic, 5-fatal, default value: 0
LogLevel int
// log file mode, default value: 0640
LogMode os.FileMode
// backup log file mode, default value: 0400
BackupLogMode os.FileMode
// size of a single log file (MB), default value: 20MB
FileMaxSize int
// maximum number of backup log files, default value: 30
MaxBackups int
// maximum number of days for backup log files, default value: 7
MaxAge int
// whether backup files need to be compressed, default value: false
IsCompress bool
}

var reg = regexp.MustCompile(backUpLogRegex)

type validateFunc func(config *LogConfig) error

// Init initialize and return the logger
func Init(config *LogConfig, stopCh <-chan struct{}) (*zap.Logger, error) {
if err := validateLogConfigFiled(config); err != nil {
return nil, err
}
zapLogger := create(*config)
if zapLogger == nil {
return nil, fmt.Errorf("create logger error")
}
msg := fmt.Sprintf("%s's logger init success.", path.Base(config.LogFileName))
zapLogger.Info(msg)
// skip change file mode and fs notify
if config.OnlyToStdout {
return zapLogger, nil
}
if err := os.Chmod(config.LogFileName, config.LogMode); err != nil {
zapLogger.Error("config log path error")
return zapLogger, fmt.Errorf("set log file mode failed")
}
go workerWatcher(zapLogger, *config, stopCh)
return zapLogger, nil
}

func create(config LogConfig) *zap.Logger {
logEncoder := getEncoder()
var writeSyncer zapcore.WriteSyncer
if config.OnlyToStdout {
writeSyncer = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout))
} else {
logWriter := getLogWriter(config)
writeSyncer = zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), logWriter)
}
core := zapcore.NewCore(logEncoder, writeSyncer, zapcore.Level(config.LogLevel))
return zap.New(core, zap.AddCaller())
}

// getEncoder get zap encoder
func getEncoder() zapcore.Encoder {
encoderConfig := zapcore.EncoderConfig{
// Keys can be anything except the empty string.
TimeKey: "T",
LevelKey: "L",
NameKey: "N",
CallerKey: "C",
MessageKey: "M",
StacktraceKey: "S",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
}
return zapcore.NewConsoleEncoder(encoderConfig)
}

// getLogWriter get zap log writer
func getLogWriter(config LogConfig) zapcore.WriteSyncer {
lumberjackLogger := &lumberjack.Logger{
Filename: config.LogFileName,
MaxSize: config.FileMaxSize, // megabytes
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge, // days
Compress: config.IsCompress,
}
return zapcore.AddSync(lumberjackLogger)
}

func checkDir(fileDir string) error {
if !isExist(fileDir) {
if err := os.MkdirAll(fileDir, LogDirMode); err != nil {
return fmt.Errorf("create dirs failed")
}
return nil
}
if err := os.Chmod(fileDir, LogDirMode); err != nil {
return fmt.Errorf("change log dir mode failed")
}
return nil
}

func createFile(filePath string) error {
fileName := path.Base(filePath)
if !isExist(filePath) {
f, err := os.Create(filePath)
defer f.Close()
if err != nil {
return fmt.Errorf("create file(%s) failed", fileName)
}
}
return nil
}

func checkAndCreateLogFile(filePath string) error {
if !isFile(filePath) {
return fmt.Errorf("config path is not file")
}
fileDir := path.Dir(filePath)
if err := checkDir(fileDir); err != nil {
return err
}
if err := createFile(filePath); err != nil {
return err
}
return nil
}

func isDir(path string) bool {
if !isExist(path) {
return path[len(path)-1:] == "/"
}
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}

func isFile(path string) bool {
return !isDir(path)
}

func isExist(filePath string) bool {
if _, err := os.Stat(filePath); err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}

func validateLogConfigFileMaxSize(config *LogConfig) error {
if config.FileMaxSize == 0 {
config.FileMaxSize = DefaultFileMaxSize
return nil
}
if config.FileMaxSize < 0 || config.FileMaxSize > DefaultFileMaxSize {
return fmt.Errorf("the size of a single log file range is (0, 20] MB")
}

return nil
}

func validateLogConfigBackups(config *LogConfig) error {
if config.MaxBackups <= 0 || config.MaxBackups > DefaultMaxBackups {
return fmt.Errorf("the number of backup log file range is (0, 30]")
}
return nil
}

func validateLogConfigMaxAge(config *LogConfig) error {
if config.MaxAge < DefaultMinSaveAge {
return fmt.Errorf("the maxage should be greater than 7 days")
}
return nil
}

func validateLogLevel(config *LogConfig) error {
if config.LogLevel < minLogLevel || config.LogLevel > maxLogLevel {
return fmt.Errorf("the log level range should be [-1, 5]")
}
return nil
}

func validateLogConfigFileMode(config *LogConfig) error {
if config.LogMode == 0 {
config.LogMode = LogFileMode
}
if config.BackupLogMode == 0 {
config.BackupLogMode = BackupLogFileMode
}
return nil
}

func getValidateFuncList() []validateFunc {
var funcList []validateFunc
funcList = append(funcList, validateLogConfigFileMaxSize, validateLogConfigBackups,
validateLogConfigMaxAge, validateLogConfigFileMode, validateLogLevel)
return funcList
}

func validateLogConfigFiled(config *LogConfig) error {
if config.OnlyToStdout {
return nil
}
if !path.IsAbs(config.LogFileName) {
return fmt.Errorf("config log path is not absolute path")
}

if err := checkAndCreateLogFile(config.LogFileName); err != nil {
return err
}
validateFuncList := getValidateFuncList()
for _, vaFunc := range validateFuncList {
if err := vaFunc(config); err != nil {
return err
}
}

return nil
}

func workerWatcher(l *zap.Logger, config LogConfig, stopCh <-chan struct{}) {
if l == nil {
fmt.Println("workerWatcher logger is nil")
return
}
if stopCh == nil {
l.Error("stop channel is nil")
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
l.Error("NewWatcher failed", zap.String("err", err.Error()))
return
}
defer watcher.Close()
logPath := path.Dir(config.LogFileName)
if err = watcher.Add(logPath); err != nil {
l.Error("watcher add log path failed")
return
}
for {
select {
case _, ok := <-stopCh:
if !ok {
l.Error("recv stop signal")
return
}
case event, ok := <-watcher.Events:
if !ok {
l.Error("watcher event failed, exit")
return
}
if event.Op&fsnotify.Create == 0 {
break
}
changeFileMode(l, event, config.LogFileName)
case errWatcher, ok := <-watcher.Errors:
if !ok {
l.Error("watcher error failed, exit")
return
}
l.Error("watcher error", zap.String("err", errWatcher.Error()))
return
}
}
}

func changeFileMode(l *zap.Logger, event fsnotify.Event, logFileFullPath string) {
if l == nil {
fmt.Println("changeFileMode logger is nil")
return
}
var logMode = LogFileMode
logPath := path.Dir(logFileFullPath)
changedFileName := path.Base(event.Name)
if isTargetLog(changedFileName) {
logMode = BackupLogFileMode
}
changedLogFilePath := path.Join(logPath, changedFileName)
if !isExist(changedLogFilePath) {
return
}
if errChmod := os.Chmod(changedLogFilePath, logMode); errChmod != nil {
l.Error("set file mode failed", zap.String("filename", changedFileName))
}
}
func isTargetLog(fileName string) bool {
return reg.MatchString(fileName)
}

hwlog--logger.go的更多相关文章

  1. ABP源码分析八:Logger集成

    ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方 ...

  2. org.apache.log4j.Logger详解

    org.apache.log4j.Logger 详解 1. 概述 1.1. 背景 在应用程序中添加日志记录总的来说基于三个目的 :监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工 ...

  3. Java程序日志:java.util.logging.Logger类

    一.Logger 的级别 比log4j的级别详细,全部定义在java.util.logging.Level里面.各级别按降序排列如下:SEVERE(最高值)WARNINGINFOCONFIGFINEF ...

  4. [LeetCode] Logger Rate Limiter 记录速率限制器

    Design a logger system that receive stream of messages along with its timestamps, each message shoul ...

  5. .Net Core Logger 实现log写入本地文件系统

    .net core 自带一个基础的logger框架Microsoft.Extensions.Logging. 微软默认实现了Microsoft.Extensions.Logging.Console.d ...

  6. Android源码——Logger日志系统

    Android的Logger日志系统是基于内核中的Logger日志驱动程序实现的. 日志保存在内核空间中 缓冲区保存日志   分类方法:日志的类型  +   日志的输出量   日志类型:   main ...

  7. java.lang.NoClassDefFoundError: Lorg/slf4j/Logger;

    如果你出现类似如下错误 1. Install tomcat7 in my home directory and set up `CATALINA_HOME` environment variable ...

  8. LeetCode 359 Logger Rate Limiter

    Problem: Design a logger system that receive stream of messages along with its timestamps, each mess ...

  9. 你的日志组件记录够清晰嘛?--自己开发日志组件 Logger

    现在现成的日志组件实在是太多太多,为什么我还需要自己实现呢????? 需求来源于java的log4j, [07-31 16:40:00:557:WARN : com.game.engine.threa ...

  10. log4j2 不使用配置文件,动态生成logger对象

    大家平时使用Log4j一般都是在classpath下放置一个log4j的配置文件,比如log4j.xml,里面配置好Appenders和Loggers,但是前一阵想做某需求的时候,想要的效果是每一个任 ...

随机推荐

  1. 第七十九篇:数组方法(forEach,some,every,reduce)

    好家伙,来复习几个数组方法, 1.forEach循环与some循环 代码如下: <script> const arr =['奔驰','宝马','GTR','奥迪'] //forEach循环 ...

  2. 【Traefik二次开发】服务 Service 开发

    Service 定义 https://doc.traefik.io/traefik/routing/services/ The Services are responsible for configu ...

  3. Homework7

    问:了解java的反射机制. 答:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法.而对于任意一个对象,都能够调用它的任意一个方法.这种动态获取的信息以及动态调用对象的 ...

  4. 使用Pipeline抽象业务生命周期流程

    上篇关于流程引擎的文章还是快两年以前的<微服务业务生命周期流程管控引擎>,这中间各种低代码平台层出不穷,虽然有些仅仅是OA+表单的再度包装,但有些的确是在逻辑和操作单元层面进行了真正的高度 ...

  5. Java SE 2、抽象类

    抽象类 用abstract关键字来修饰一个类时,这个类就是抽象类 ​ 访问修饰符 abstract 类名 { ​ } 用abstract关键字来修饰一个方法时,这个方法就是抽象方法 ​ 访问修饰符 a ...

  6. kibana配置文件kibana.yml参数详解

    server.port: 默认值: 5601 Kibana 由后端服务器提供服务,该配置指定使用的端口号. server.host: 默认值: "localhost" 指定后端服务 ...

  7. 配置Kubelet的垃圾回收

    文章转载自:https://www.kuboard.cn/learning/k8s-advanced/gc.html Kubelet的垃圾回收功能可以清理不再使用的容器和镜像,kubelet对容器进行 ...

  8. 运用Filebeat module分析nginx日志

    在同一台主机上事先安装好filebeat,elasticsearch和kibana filebeat配置 安装完Filebeat后,可以看到在Filebeat的安装目录下有一个叫做filebeat.y ...

  9. Logstash:Logstash-to-Logstash 通信

    文章转载自:https://elasticstack.blog.csdn.net/article/details/117253545 在有些时候,我们甚至可以建立 Logstash-to-Logsta ...

  10. mvn clean package 、mvn clean install、mvn clean deploy的区别与联系

    使用的时候首选:mvn clean package mvn clean package依次执行了clean.resources.compile.testResources.testCompile.te ...