//  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. Python代码用在这些地方,其实1行就够了!

    摘要:都说 Python 简单快捷,那本篇博客就为大家带来一些实用的 Python 技巧,而且仅需要 1 行代码,就可以解决一些小问题. 本文分享自华为云社区<你猜 1 行Python代码能干什 ...

  2. 字符类数据类型和oracle字符类型的区别

    为兼容Oracle的数据类型,KingbaseES扩展了Oracle的NUMBER.VARCHAR2.CHAR(n)和DATE类型.该措施使得移植Oracle的Create Table等DDL语句时, ...

  3. docker学习笔记-容器相关命令

    新建并启动容器 docker pull centos (先下载镜像,如果没有直接使用docker run 命令会根据本地情况进行下载) # docker run [可选参数] image # 参数说明 ...

  4. [报错]-NameError: name 'NAN' is not defined

    部分数据输出为NaN,处理这部分异常数据使用isnan()函数 from math import isnan isnan(z) 参考: https://www.cnblogs.com/itdyb/p/ ...

  5. Windows Server体验之安装

    微软在Windows Server家族中有一个新的家族,名字就是Windows Server.这个按半年频道更新的版本目前是1903和Windows 10的命名方式一样.这个产品就是以前的服务器核心安 ...

  6. 最强cron解析器

    背景 大家有没有这么一种困境 我现在需要去配置一个定时任务:"每天早上九点执行任务" 若你有一个好的定时任务平台,相信很容易就能配置完成.那若是没有定时任务平台呢?是不是就要自己写 ...

  7. 【项目实战】kaggle产品分类挑战

    多分类特征的学习 这里还是b站刘二大人的视频课代码,视频链接:https://www.bilibili.com/video/BV1Y7411d7Ys?p=9 相关注释已经标明了(就当是笔记),因此在这 ...

  8. Docker网络详解——原理篇

    安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络). none .host 网络模式 简介 Host 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP ...

  9. js从数组中找到某个对象

    handel(ide) { let that = this; console.log(that.goodCaseList); let detailData = that.goodCaseList.fi ...

  10. 1_requests基础用法

    requests模块的基本使用 什么是requests模块? Python中封装好的一个基于网络请求的模块 requests模块的作用? 用来模拟浏览器发请求 requests模块的环境安装: pip ...