//  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)
}

最新文章

  1. react native 刷新机制----通知
  2. adb shell出错“error: unknown host service”
  3. winrt 页面进入动画
  4. hibernate 组件(Component)映射
  5. Mousetrap - Keyboard shortcuts in Javascript
  6. java优化占用内存的方法(一)
  7. 24种设计模式--建造者模式【Builder Pattern】
  8. 乐在其中设计模式(C#) - 桥接模式(Bridge Pattern)
  9. angular中的cookie读写
  10. 通过ssh远程ipython notebook登录使用服务器
  11. java 对象转型
  12. 用过的关于css的知识
  13. NowCoder牛客练习赛7-A.骰子的游戏 B.购物-优先队列
  14. CentOS 7 安装Maven
  15. GA:利用GA对一元函数进行优化过程,求x∈(0,10)中y的最大值——Jason niu
  16. 一套能体现 RBAC 的表结构设计
  17. Jenkins CLI 命令详解
  18. TRIO-basic变量的状态位
  19. win怎么设置最快捷的下滑关机
  20. SpringMVC 请求响应流程

热门文章

  1. HC32L110(四) HC32L110的startup启动文件和ld连接脚本
  2. UE4自动打包工具编写
  3. Java中一些必须要知道的东西
  4. Django 之必知必会三板斧
  5. 【学习笔记】循环神经网络(RNN)
  6. Django 聚合分组F与Q查询及choices
  7. 使用调度器apscheduler实现py文件不停连接MySQL数据库
  8. 引入Wukong让你的系统瞬间具备IOC能力
  9. 手把手教你使用LabVIEW OpenCV DNN实现手写数字识别(含源码)
  10. 聊聊Linux中CPU上下文切换