//  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. EL&JSTL笔记------jsp

    今日内容 1. JSP: 1. 指令 2. 注释 3. 内置对象 2. MVC开发模式 3. EL表达式 4. JSTL标签 5. 三层架构 JSP: 1. 指令 * 作用:用于配置JSP页面,导入资 ...

  2. ubuntu 安装及配置Apache

    一.下载.启动apache2 sudo apt-get install apache2 sudo /etc/init.d/apache2 restart 二.修改配置文件 1.更改网站根目录 sudo ...

  3. synchronized锁详解

    synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...

  4. 第六章:Django 综合篇 - 1:配置 Django

    Django项目的设置文件位于项目同名目录下,名叫settings.py.这个模块,集合了整个项目方方面面的设置属性,是项目启动和提供服务的根本保证. 一.简述 settings.py文件本质上是一个 ...

  5. 史上最全的selenium三大等待介绍

    一.强制等待 1.设置完等待后不管有没有找到元素,都会执行等待,等待结束后才会执行下一步 2.实例 driver = webdriver.Chrome() driver.get("https ...

  6. loam详细代码解析与公式推导

    loam详细代码解析与公式推导(基础理论知识) 一.基础坐标变换 loam中欧拉角解算都采用R P Y 的解算方式,即先左乘R, 再左乘P, 最后左乘Y,用矩阵表示为: R = Ry * Rp * R ...

  7. PAT (Basic Level) Practice 1027 打印沙漏 分数 20

    本题要求你写个程序把给定的符号打印成沙漏的形状.例如给定17个"*",要求按下列格式打印 ***** *** * *** *****   所谓"沙漏形状",是指 ...

  8. 【Java8新特性】- 接口中默认方法修饰为普通方法

    Java8新特性 - 接口中默认方法修饰为普通方法 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学 ...

  9. Leetcode刷题笔记(双指针)

    1.何为双指针 双指针主要用来遍历数组,两个指针指向不同的元素,从而协同完成任务.我们也可以类比这个概念,推广到多个数组的多个指针. 若两个指针指向同一数组,遍历方向相同且不会相交,可以称之为滑动窗口 ...

  10. 关于csh-C-shell的记录

    csh,由柏克莱大学的 Bill Joy 设计的,语法有点类似C语言,所以才得名为 C shell ,简称为 csh Bill Joy 是一个风云人物,他创立了 BSD 操作系统,开发了 vi 编辑器 ...