//  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. K8S name_namespace

    Name 由于K8S内部,使用"资源"来定义每一种逻辑概念(功能),故没种"资源",都应该有自己的"名称" "资源"有 ...

  2. Python 第三次实验

    一如既往地简单,不到半个小时即可完成 [1] (程序设计)输入一个正整数,输出它的因子分解式.如输入132,则输出132=122311 n=int(input()) print(1,end='') f ...

  3. 【面试题】JS改变this指向的三种方法

    一.this指向 点击打开视频讲解更加详细 this随处可见,一般谁调用,this就指向谁.this在不同环境下,不同作用下,表现的也不同. 以下几种情况,this都是指向window 1.全局作用下 ...

  4. 尝试阅读理解一份linux shell脚本

    以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/p/16721350.html 从头一二去阅读语法和命令说明 ...

  5. MinIO管理员完整指南

    官方文档地址:http://docs.minio.org.cn/docs/master/minio-admin-complete-guide MinIO Client(mc)提供了" adm ...

  6. MinIO 快速入门指南

    官方文档地址:http://docs.minio.org.cn/docs/ MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储. 它是与 Amazon S3 云存 ...

  7. Kibana可视化数据(Visualize)

    在侧边导航栏点击 Visualize 开始视化您的数据. Visualize 工具能让您通过多种方式浏览您的数据.例如:我们使用饼图这个重要的可视化控件来查看银行账户样本数据中的账户余额.点击屏幕中间 ...

  8. 组件化开发2-安装cocoaPods

    第一步:安装ruby 不能一上来就换ruby源.虽然mac自带了ruby,但是版本一般都偏低,如果不进行更新会导致版本依赖问题. 这里使用rvm来管理ruby,它允许共存多个ruby.RVM:Ruby ...

  9. value中放vue的参数

    <input type="text" v-model="userInfo.name"/>

  10. CentOS 7 下安装 MySQL 8.x

    CentOS 7 下安装 MySQL 8.x 作者:Grey 原文地址: 博客园:CentOS 7 下安装 MySQL 8.x CSDN:CentOS 7 下安装 MySQL 8.x 环境 CentO ...