├── README.md ├── conn.go ├── conn_test.go ├── console.go ├── console_test.go ├── file.go ├── file_test.go ├── images └── output1.png ├── log.go └── log_test.go /README.md: -------------------------------------------------------------------------------- 1 | # logger 2 | convenient log package 3 | 4 | # 1. 使用说明 5 | ```go 6 | import "github.com/wonderivan/logger" 7 | 8 | // 配置logger,如果不配置时默认为控制台输出,等级为DEBG 9 | logger.SetLogger(`{"Console": {"level": "DEBG"}`) 10 | // 配置说明见下文 11 | 12 | // 设置完成后,即可在控制台和日志文件app.log中看到如下输出 13 | logger.Trace("this is Trace") 14 | logger.Debug("this is Debug") 15 | logger.Info("this is Info") 16 | logger.Warn("this is Warn") 17 | logger.Error("this is Error") 18 | logger.Crit("this is Critical") 19 | logger.Alert("this is Alert") 20 | logger.Emer("this is Emergency") 21 | ``` 22 | 输出结果: 23 | 24 | ![](images/output1.png) 25 | 26 | # 2. 日志等级 27 | 28 | 当前日志输出等级共8种,从0-7对应的等级由高到底,当配置为某个输出等级时,只有大于等于该等级的日志才会输出。不同的输出适配器支持不同的日志等级配置: 29 | 30 | | 等级 | 配置 | 释义 | 控制台颜色 | 31 | | ---- | ---- | ------------------------------------------------ | ---------- | 32 | | 0 | EMER | 系统级紧急,比如磁盘出错,内存异常,网络不可用等 | 红色底 | 33 | | 1 | ALRT | 系统级警告,比如数据库访问异常,配置文件出错等 | 紫色 | 34 | | 2 | CRIT | 系统级危险,比如权限出错,访问异常等 | 蓝色 | 35 | | 3 | EROR | 用户级错误 | 红色 | 36 | | 4 | WARN | 用户级警告 | 黄色 | 37 | | 5 | INFO | 用户级重要 | 天蓝色 | 38 | | 6 | DEBG | 用户级调试 | 绿色 | 39 | | 7 | TRAC | 用户级基本输出 | 绿色 | 40 | 41 | 42 | # 3. 配置说明 43 | logger当前支持控制台、文件、网络3种方式适配器输出,可以通过各自的参数进行设置,该logger支持多个方式同时输出,如果未配置某项适配器时,则不初始化也不会输出到该适配器。 44 | 45 | 通过调用logger.SetLogger(config string)方法设置参数,config支持json配置,也支持指定内容为json配置的文件路径,例如: 46 | ```go 47 | // 通过配置参数直接配置 48 | logger.SetLogger(`{"Console": {"level": "DEBG"}}`) 49 | // 通过配置文件配置 50 | logger.SetLogger("/home/log.json") 51 | 52 | ``` 53 | 54 | ```json 55 | { 56 | "TimeFormat":"2006-01-02 15:04:05", // 输出日志开头时间格式 57 | "Console": { // 控制台日志配置 58 | "level": "TRAC", // 控制台日志输出等级 59 | "color": true // 控制台日志颜色开关 60 | }, 61 | "File": { // 文件日志配置 62 | "filename": "app.log", // 初始日志文件名 63 | "level": "TRAC", // 日志文件日志输出等级 64 | "daily": true, // 跨天后是否创建新日志文件,当append=true时有效 65 | "maxlines": 1000000, // 日志文件最大行数,当append=true时有效 66 | "maxsize": 1, // 日志文件最大大小,当append=true时有效 67 | "maxdays": -1, // 日志文件有效期 68 | "append": true, // 是否支持日志追加 69 | "permit": "0660" // 新创建的日志文件权限属性 70 | }, 71 | "Conn": { // 网络日志配置 72 | "net":"tcp", // 日志传输模式 73 | "addr":"10.1.55.10:1024", // 日志接收服务器 74 | "level": "Warn", // 网络日志输出等级 75 | "reconnect":true, // 网络断开后是否重连 76 | "reconnectOnMsg":false, // 发送完每条消息后是否断开网络 77 | } 78 | } 79 | ``` 80 | 81 | - 时间格式 82 | 83 | | 时间类型 | 时间格式 | 84 | | ------------ | ----------------------------------------- | 85 | | ANSIC | "Mon Jan _2 15:04:05 2006" | 86 | | UnixDate | "Mon Jan _2 15:04:05 MST 2006" | 87 | | RubyDate | "Mon Jan 02 15:04:05 -0700 2006" | 88 | | RFC822 | "02 Jan 06 15:04 MST" | 89 | | RFC822Z | "02 Jan 06 15:04 -0700" | 90 | | RFC850 | "Monday, 02-Jan-06 15:04:05 MST" | 91 | | RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" | 92 | | RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" | 93 | | RFC3339 | "2006-01-02T15:04:05Z07:00" | 94 | | RFC3339Nano | "2006-01-02T15:04:05.999999999Z07:00" | 95 | | Kitchen | "3:04PM" | 96 | | Stamp | "Jan _2 15:04:05" | 97 | | StampMilli | "Jan _2 15:04:05.000" | 98 | | StampMicro | "Jan _2 15:04:05.000000" | 99 | | StampNano | "Jan _2 15:04:05.000000000" | 100 | | RFC3339Nano1 | "2006-01-02 15:04:05.999999999 -0700 MST" | 101 | | DEFAULT | "2006-01-02 15:04:05" | 102 | 103 | - 时间格式打印: 104 | ``` 105 | ========RFC1123Z time format======== 106 | Thu, 02 Aug 2018 18:48:04 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123Z 107 | ========Stamp time format======== 108 | Aug 2 18:48:04 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Stamp 109 | ========StampMilli time format======== 110 | Aug 2 18:48:04.489 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMilli 111 | ========StampNano time format======== 112 | Aug 2 18:48:04.490002155 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampNano 113 | ========RubyDate time format======== 114 | Thu Aug 02 18:48:04 +0800 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RubyDate 115 | ========RFC822 time format======== 116 | 02 Aug 18 18:48 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822 117 | ========RFC822Z time format======== 118 | 02 Aug 18 18:48 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822Z 119 | ========RFC1123 time format======== 120 | Thu, 02 Aug 2018 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123 121 | ========RFC3339 time format======== 122 | 2018-08-02T18:48:04+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339 123 | ========RFC3339Nano time format======== 124 | 2018-08-02T18:48:04.490377325+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339Nano 125 | ========ANSIC time format======== 126 | Thu Aug 2 18:48:04 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug ANSIC 127 | ========UnixDate time format======== 128 | Thu Aug 2 18:48:04 CST 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug UnixDate 129 | ========RFC850 time format======== 130 | Thursday, 02-Aug-18 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC850 131 | ========Kitchen time format======== 132 | 6:48PM [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Kitchen 133 | ========StampMicro time format======== 134 | Aug 2 18:48:04.490662 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMicro 135 | ``` 136 | 137 | # 4. 其他 138 | 139 | 1. logger默认是控制台输出,输出等级为DEBG,默认是支持颜色区分的。 140 | 2. 日志文件append为true时,当写入的日志文件发生跨天(daily为true)或超过最大限制时,会创建一个新文件,原有文件格式被重命名为: ****.xxxx-xx-xx.xxx.xxx 格式,例如:当向app.log写入日志时,触发了创建新文件操作,则将app.log重命名为 app.2018-01-01.001.log, 如果此时app.2018-01-01.001.log已经存在,则将刚才的app.log重命名为 app.2018-01-01.002.log,以此类推。 141 | 3. logger package默认初始化了全局的defaultLogger,直接调用logger包的Debug方法时,会默认调用defaultLogger.Debug,所以普通调用时,仅需要import logger即可使用。 142 | 4. 网络配置中的reconnectOnMsg为每条消息都重连一次网络日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源。但强烈不建议普通使用时设置为true,这将会导致调用方反复的网络重连,极大增加资源消耗和延迟。 143 | 5. conn网络输出适配器经过ELK集成环境的测试验证,通过该方式发送的日志,能够正常通过Elecsearch和Kibana检索和分析 -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type connLogger struct { 15 | sync.Mutex 16 | innerWriter io.WriteCloser 17 | ReconnectOnMsg bool `json:"reconnectOnMsg"` 18 | Reconnect bool `json:"reconnect"` 19 | Net string `json:"net"` 20 | Addr string `json:"addr"` 21 | Level string `json:"level"` 22 | LogLevel int 23 | illNetFlag bool //网络异常标记 24 | } 25 | 26 | func (c *connLogger) Init(jsonConfig string) error { 27 | if len(jsonConfig) == 0 { 28 | return nil 29 | } 30 | fmt.Printf("consoleWriter Init:%s\n", jsonConfig) 31 | err := json.Unmarshal([]byte(jsonConfig), c) 32 | if err != nil { 33 | return err 34 | } 35 | if l, ok := LevelMap[c.Level]; ok { 36 | c.LogLevel = l 37 | } 38 | if c.innerWriter != nil { 39 | c.innerWriter.Close() 40 | c.innerWriter = nil 41 | } 42 | return nil 43 | } 44 | 45 | func (c *connLogger) LogWrite(when time.Time, msgText interface{}, level int) (err error) { 46 | if level > c.LogLevel { 47 | return nil 48 | } 49 | 50 | msg, ok := msgText.(*loginfo) 51 | if !ok { 52 | return 53 | } 54 | 55 | if c.needToConnectOnMsg() { 56 | err = c.connect() 57 | if err != nil { 58 | return 59 | } 60 | //重连成功 61 | c.illNetFlag = false 62 | } 63 | 64 | //每条消息都重连一次日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源 65 | if c.ReconnectOnMsg { // 频繁日志发送切勿开启 66 | defer c.innerWriter.Close() 67 | } 68 | 69 | //网络异常时,消息发出 70 | if !c.illNetFlag { 71 | err = c.println(when, msg) 72 | //网络异常,通知处理网络的go程自动重连 73 | if err != nil { 74 | c.illNetFlag = true 75 | } 76 | } 77 | 78 | return 79 | } 80 | 81 | func (c *connLogger) Destroy() { 82 | if c.innerWriter != nil { 83 | c.innerWriter.Close() 84 | } 85 | } 86 | 87 | func (c *connLogger) connect() error { 88 | if c.innerWriter != nil { 89 | c.innerWriter.Close() 90 | c.innerWriter = nil 91 | } 92 | addrs := strings.Split(c.Addr, ";") 93 | for _, addr := range addrs { 94 | conn, err := net.Dial(c.Net, addr) 95 | if err != nil { 96 | fmt.Fprintf(os.Stderr, "net.Dial error:%v\n", err) 97 | continue 98 | //return err 99 | } 100 | 101 | if tcpConn, ok := conn.(*net.TCPConn); ok { 102 | tcpConn.SetKeepAlive(true) 103 | } 104 | c.innerWriter = conn 105 | return nil 106 | } 107 | return fmt.Errorf("hava no valid logs service addr:%v", c.Addr) 108 | } 109 | 110 | func (c *connLogger) needToConnectOnMsg() bool { 111 | if c.Reconnect { 112 | c.Reconnect = false 113 | return true 114 | } 115 | 116 | if c.innerWriter == nil { 117 | return true 118 | } 119 | 120 | if c.illNetFlag { 121 | return true 122 | } 123 | return c.ReconnectOnMsg 124 | } 125 | 126 | func (c *connLogger) println(when time.Time, msg *loginfo) error { 127 | c.Lock() 128 | defer c.Unlock() 129 | ss, err := json.Marshal(msg) 130 | if err != nil { 131 | return err 132 | } 133 | _, err = c.innerWriter.Write(append(ss, '\n')) 134 | 135 | //返回err,解决日志系统网络异常后的自动重连 136 | return err 137 | } 138 | 139 | func init() { 140 | Register(AdapterConn, &connLogger{LogLevel: LevelTrace}) 141 | } 142 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConn(t *testing.T) { 8 | log := NewLogger() 9 | log.SetLogger("conn", `{"net":"tcp","addr":"10.1.55.10:1024"}`) 10 | log.Info("this is informational to net") 11 | } 12 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type brush func(string) string 13 | 14 | func newBrush(color string) brush { 15 | pre := "\033[" 16 | reset := "\033[0m" 17 | return func(text string) string { 18 | return pre + color + "m" + text + reset 19 | } 20 | } 21 | 22 | //鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用, 23 | var colors = []brush{ 24 | newBrush("1;41"), // Emergency 红色底 25 | newBrush("1;35"), // Alert 紫色 26 | newBrush("1;34"), // Critical 蓝色 27 | newBrush("1;31"), // Error 红色 28 | newBrush("1;33"), // Warn 黄色 29 | newBrush("1;36"), // Informational 天蓝色 30 | newBrush("1;32"), // Debug 绿色 31 | newBrush("1;32"), // Trace 绿色 32 | } 33 | 34 | type consoleLogger struct { 35 | sync.Mutex 36 | Level string `json:"level"` 37 | Colorful bool `json:"color"` 38 | LogLevel int 39 | } 40 | 41 | func (c *consoleLogger) Init(jsonConfig string) error { 42 | if len(jsonConfig) == 0 { 43 | return nil 44 | } 45 | if jsonConfig != "{}" { 46 | fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig) 47 | } 48 | 49 | err := json.Unmarshal([]byte(jsonConfig), c) 50 | if runtime.GOOS == "windows" { 51 | c.Colorful = false 52 | } 53 | 54 | if l, ok := LevelMap[c.Level]; ok { 55 | c.LogLevel = l 56 | return nil 57 | } 58 | 59 | return err 60 | } 61 | 62 | func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error { 63 | if level > c.LogLevel { 64 | return nil 65 | } 66 | msg, ok := msgText.(string) 67 | if !ok { 68 | return nil 69 | } 70 | if c.Colorful { 71 | msg = colors[level](msg) 72 | } 73 | c.printlnConsole(when, msg) 74 | return nil 75 | } 76 | 77 | func (c *consoleLogger) Destroy() { 78 | 79 | } 80 | 81 | func (c *consoleLogger) printlnConsole(when time.Time, msg string) { 82 | c.Lock() 83 | defer c.Unlock() 84 | os.Stdout.Write(append([]byte(msg), '\n')) 85 | } 86 | 87 | func init() { 88 | Register(AdapterConsole, &consoleLogger{ 89 | LogLevel: LevelDebug, 90 | Colorful: runtime.GOOS != "windows", 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /console_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Try each log level in decreasing order of priority. 8 | func testConsoleCalls(bl *LocalLogger) { 9 | bl.Emer("emergency") 10 | bl.Alert("alert") 11 | bl.Crit("critical") 12 | bl.Error("error") 13 | bl.Warn("warning") 14 | bl.Debug("notice") 15 | bl.Info("informational") 16 | bl.Trace("trace") 17 | } 18 | 19 | func TestConsole(t *testing.T) { 20 | log1 := NewLogger() 21 | log1.SetLogger("console", "") 22 | testConsoleCalls(log1) 23 | 24 | log2 := NewLogger() 25 | log2.SetLogger("console", `{"level":"EROR"}`) 26 | testConsoleCalls(log2) 27 | } 28 | 29 | // Test console without color 30 | func TestNoColorConsole(t *testing.T) { 31 | log := NewLogger() 32 | log.SetLogger("console", `{"color":false}`) 33 | testConsoleCalls(log) 34 | } 35 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | type fileLogger struct { 18 | sync.RWMutex 19 | fileWriter *os.File 20 | 21 | Filename string `json:"filename"` 22 | Append bool `json:"append"` 23 | MaxLines int `json:"maxlines"` 24 | MaxSize int `json:"maxsize"` 25 | Daily bool `json:"daily"` 26 | MaxDays int64 `json:"maxdays"` 27 | Level string `json:"level"` 28 | PermitMask string `json:"permit"` 29 | 30 | LogLevel int 31 | maxSizeCurSize int 32 | maxLinesCurLines int 33 | dailyOpenDate int 34 | dailyOpenTime time.Time 35 | fileNameOnly, suffix string 36 | } 37 | 38 | // Init file logger with json config. 39 | // jsonConfig like: 40 | // { 41 | // "filename":"log/app.log", 42 | // "maxlines":10000, 43 | // "maxsize":1024, 44 | // "daily":true, 45 | // "maxdays":15, 46 | // "rotate":true, 47 | // "permit":"0600" 48 | // } 49 | func (f *fileLogger) Init(jsonConfig string) error { 50 | fmt.Printf("fileLogger Init:%s\n", jsonConfig) 51 | if len(jsonConfig) == 0 { 52 | return nil 53 | } 54 | err := json.Unmarshal([]byte(jsonConfig), f) 55 | if err != nil { 56 | return err 57 | } 58 | if len(f.Filename) == 0 { 59 | return errors.New("jsonconfig must have filename") 60 | } 61 | f.suffix = filepath.Ext(f.Filename) 62 | f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix) 63 | f.MaxSize *= 1024 * 1024 // 将单位转换成MB 64 | if f.suffix == "" { 65 | f.suffix = ".log" 66 | } 67 | if l, ok := LevelMap[f.Level]; ok { 68 | f.LogLevel = l 69 | } 70 | err = f.newFile() 71 | return err 72 | } 73 | 74 | func (f *fileLogger) needCreateFresh(size int, day int) bool { 75 | return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) || 76 | (f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) || 77 | (f.Daily && day != f.dailyOpenDate) 78 | 79 | } 80 | 81 | // WriteMsg write logger message into file. 82 | func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error { 83 | msg, ok := msgText.(string) 84 | if !ok { 85 | return nil 86 | } 87 | if level > f.LogLevel { 88 | return nil 89 | } 90 | 91 | day := when.Day() 92 | msg += "\n" 93 | if f.Append { 94 | f.RLock() 95 | if f.needCreateFresh(len(msg), day) { 96 | f.RUnlock() 97 | f.Lock() 98 | if f.needCreateFresh(len(msg), day) { 99 | if err := f.createFreshFile(when); err != nil { 100 | fmt.Fprintf(os.Stderr, "createFreshFile(%q): %s\n", f.Filename, err) 101 | } 102 | } 103 | f.Unlock() 104 | } else { 105 | f.RUnlock() 106 | } 107 | } 108 | 109 | f.Lock() 110 | _, err := f.fileWriter.Write([]byte(msg)) 111 | if err == nil { 112 | f.maxLinesCurLines++ 113 | f.maxSizeCurSize += len(msg) 114 | } 115 | f.Unlock() 116 | return err 117 | } 118 | 119 | func (f *fileLogger) createLogFile() (*os.File, error) { 120 | // Open the log file 121 | perm, err := strconv.ParseInt(f.PermitMask, 8, 64) 122 | if err != nil { 123 | return nil, err 124 | } 125 | fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) 126 | if err == nil { 127 | // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask 128 | os.Chmod(f.Filename, os.FileMode(perm)) 129 | } 130 | return fd, err 131 | } 132 | 133 | func (f *fileLogger) newFile() error { 134 | file, err := f.createLogFile() 135 | if err != nil { 136 | return err 137 | } 138 | if f.fileWriter != nil { 139 | f.fileWriter.Close() 140 | } 141 | f.fileWriter = file 142 | 143 | fInfo, err := file.Stat() 144 | if err != nil { 145 | return fmt.Errorf("get stat err: %s", err) 146 | } 147 | f.maxSizeCurSize = int(fInfo.Size()) 148 | f.dailyOpenTime = time.Now() 149 | f.dailyOpenDate = f.dailyOpenTime.Day() 150 | f.maxLinesCurLines = 0 151 | if f.maxSizeCurSize > 0 { 152 | count, err := f.lines() 153 | if err != nil { 154 | return err 155 | } 156 | f.maxLinesCurLines = count 157 | } 158 | return nil 159 | } 160 | 161 | func (f *fileLogger) lines() (int, error) { 162 | fd, err := os.Open(f.Filename) 163 | if err != nil { 164 | return 0, err 165 | } 166 | defer fd.Close() 167 | 168 | buf := make([]byte, 32768) // 32k 169 | count := 0 170 | lineSep := []byte{'\n'} 171 | 172 | for { 173 | c, err := fd.Read(buf) 174 | if err != nil && err != io.EOF { 175 | return count, err 176 | } 177 | 178 | count += bytes.Count(buf[:c], lineSep) 179 | 180 | if err == io.EOF { 181 | break 182 | } 183 | } 184 | 185 | return count, nil 186 | } 187 | 188 | // new file name like xx.2013-01-01.001.log 189 | func (f *fileLogger) createFreshFile(logTime time.Time) error { 190 | // file exists 191 | // Find the next available number 192 | num := 1 193 | fName := "" 194 | rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | _, err = os.Lstat(f.Filename) 200 | if err != nil { 201 | // 初始日志文件不存在,无需创建新文件 202 | goto RESTART_LOGGER 203 | } 204 | // 日期变了, 说明跨天,重命名时需要保存为昨天的日期 205 | if f.dailyOpenDate != logTime.Day() { 206 | for ; err == nil && num <= 999; num++ { 207 | fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix) 208 | _, err = os.Lstat(fName) 209 | } 210 | } else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可 211 | for ; err == nil && num <= 999; num++ { 212 | fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix) 213 | _, err = os.Lstat(fName) 214 | } 215 | } 216 | 217 | if err == nil { 218 | return fmt.Errorf("Cannot find free log number to rename %s", f.Filename) 219 | } 220 | f.fileWriter.Close() 221 | 222 | // 当创建新文件标记为true时 223 | // 当日志文件超过最大限制行 224 | // 当日志文件超过最大限制字节 225 | // 当日志文件隔天更新标记为true时 226 | // 将旧文件重命名,然后创建新文件 227 | err = os.Rename(f.Filename, fName) 228 | if err != nil { 229 | fmt.Fprintf(os.Stderr, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error()) 230 | goto RESTART_LOGGER 231 | } 232 | 233 | err = os.Chmod(fName, os.FileMode(rotatePerm)) 234 | 235 | RESTART_LOGGER: 236 | 237 | startLoggerErr := f.newFile() 238 | go f.deleteOldLog() 239 | 240 | if startLoggerErr != nil { 241 | return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr) 242 | } 243 | if err != nil { 244 | return fmt.Errorf("Rotate: %s", err) 245 | } 246 | return nil 247 | } 248 | 249 | func (f *fileLogger) deleteOldLog() { 250 | dir := filepath.Dir(f.Filename) 251 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { 252 | defer func() { 253 | if r := recover(); r != nil { 254 | fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r) 255 | } 256 | }() 257 | 258 | if info == nil { 259 | return 260 | } 261 | 262 | if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) { 263 | if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) && 264 | strings.HasSuffix(filepath.Base(path), f.suffix) { 265 | os.Remove(path) 266 | } 267 | } 268 | return 269 | }) 270 | } 271 | 272 | func (f *fileLogger) Destroy() { 273 | f.fileWriter.Close() 274 | } 275 | 276 | func init() { 277 | Register(AdapterFile, &fileLogger{ 278 | Daily: true, 279 | MaxDays: 7, 280 | Append: true, 281 | LogLevel: LevelDebug, 282 | PermitMask: "0777", 283 | MaxLines: 10, 284 | MaxSize: 10 * 1024 * 1024, 285 | }) 286 | } 287 | -------------------------------------------------------------------------------- /file_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestFilePermit(t *testing.T) { 13 | log := NewLogger() 14 | log.SetLogger(AdapterFile, `{"filename":"test.log", 15 | "rotateperm": "0666", 16 | "maxlines":100000, 17 | "maxsize":1, 18 | "append":true} `) 19 | 20 | log.Trace("trace") 21 | log.Debug("debug") 22 | log.Info("info") 23 | log.Debug("notice") 24 | log.Warn("warning") 25 | log.Error("error") 26 | log.Alert("alert") 27 | log.Crit("critical") 28 | log.Emer("emergency") 29 | 30 | file, err := os.Stat("test.log") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if file.Mode() != 0666 { 35 | t.Fatal("unexpected log file permission") 36 | } 37 | os.Remove("test.log") 38 | } 39 | 40 | func TestFileLine(t *testing.T) { 41 | log := NewLogger() 42 | log.SetLogger("file", `{"filename":"test2.log"}`) 43 | log.Debug("debug") 44 | log.Info("info") 45 | log.Debug("debug") 46 | log.Warn("warning") 47 | log.Error("error") 48 | log.Alert("alert") 49 | log.Crit("critical") 50 | log.Emer("emergency") 51 | f, err := os.Open("test.log") 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | b := bufio.NewReader(f) 56 | lineNum := 0 57 | for { 58 | line, _, err := b.ReadLine() 59 | if err != nil { 60 | break 61 | } 62 | if len(line) > 0 { 63 | lineNum++ 64 | } 65 | } 66 | var expected = LevelTrace + 1 67 | if lineNum != expected { 68 | t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines") 69 | } 70 | os.Remove("test2.log") 71 | } 72 | 73 | func TestFileSize(t *testing.T) { 74 | log := NewLogger() 75 | log.SetLogger(AdapterFile, `{"filename":"test.log", 76 | "rotateperm": "0666", 77 | "maxlines":100000, 78 | "maxsize":1, 79 | "append":true} `) 80 | for i := 0; i < 3000; i++ { 81 | log.Trace("trace") 82 | log.Debug("debug") 83 | log.Info("info") 84 | log.Debug("notice") 85 | log.Warn("warning") 86 | log.Error("error") 87 | log.Alert("alert") 88 | log.Crit("critical") 89 | log.Emer("emergency") 90 | time.Sleep(time.Millisecond * 10) 91 | } 92 | // 手动删 93 | } 94 | 95 | func TestFileByMaxLine(t *testing.T) { 96 | log := NewLogger() 97 | log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`) 98 | log.Debug("debug") 99 | log.Info("info") 100 | log.Warn("warning") 101 | log.Error("error") 102 | log.Alert("alert") 103 | log.Crit("critical") 104 | log.Emer("emergency") 105 | rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log" 106 | b, err := exists(rotateName) 107 | if !b || err != nil { 108 | os.Remove("test3.log") 109 | t.Fatal("rotate not generated") 110 | } 111 | os.Remove(rotateName) 112 | os.Remove("test3.log") 113 | } 114 | 115 | func TestFileByTime(t *testing.T) { 116 | fn1 := "rotate_day.log" 117 | fn2 := "rotate_day" + fmt.Sprintf(".%s.%03d", time.Now().Add(-24*time.Hour).Format("2006-01-02"), 1) + ".log" 118 | fw := &fileLogger{ 119 | Daily: true, 120 | MaxDays: 7, 121 | Append: true, 122 | LogLevel: LevelTrace, 123 | PermitMask: "0660", 124 | } 125 | fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) 126 | fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) 127 | fw.dailyOpenDate = fw.dailyOpenTime.Day() 128 | fw.LogWrite(time.Now(), "this is a msg for test", LevelTrace) 129 | 130 | for _, file := range []string{fn1, fn2} { 131 | _, err := os.Stat(file) 132 | if err != nil { 133 | t.FailNow() 134 | } 135 | os.Remove(file) 136 | } 137 | fw.Destroy() 138 | } 139 | 140 | func exists(path string) (bool, error) { 141 | _, err := os.Stat(path) 142 | if err == nil { 143 | return true, nil 144 | } 145 | if os.IsNotExist(err) { 146 | return false, nil 147 | } 148 | return false, err 149 | } 150 | 151 | func BenchmarkFile(b *testing.B) { 152 | log := NewLogger() 153 | log.SetLogger("file", `{"filename":"test4.log"}`) 154 | for i := 0; i < b.N; i++ { 155 | log.Debug("debug") 156 | } 157 | os.Remove("test4.log") 158 | } 159 | 160 | func BenchmarkFileCallDepth(b *testing.B) { 161 | log := NewLogger() 162 | log.SetLogger("file", `{"filename":"test4.log"}`) 163 | for i := 0; i < b.N; i++ { 164 | log.Debug("debug") 165 | } 166 | os.Remove("test4.log") 167 | } 168 | 169 | func BenchmarkFileOnGoroutine(b *testing.B) { 170 | log := NewLogger() 171 | log.SetLogger("file", `{"filename":"test4.log"}`) 172 | for i := 0; i < b.N; i++ { 173 | go log.Debug("debug") 174 | } 175 | os.Remove("test4.log") 176 | } 177 | -------------------------------------------------------------------------------- /images/output1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aryming/logger/11d39e135b9d8f7994e0b70b26df77bf207d75aa/images/output1.png -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "runtime" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // 默认日志输出 15 | var defaultLogger *LocalLogger 16 | 17 | // 日志等级,从0-7,日优先级由高到低 18 | const ( 19 | LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等 20 | LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等 21 | LevelCritical // 系统级危险,比如权限出错,访问异常等 22 | LevelError // 用户级错误 23 | LevelWarning // 用户级警告 24 | LevelInformational // 用户级信息 25 | LevelDebug // 用户级调试 26 | LevelTrace // 用户级基本输出 27 | ) 28 | 29 | // 日志等级和描述映射关系 30 | var LevelMap = map[string]int{ 31 | "EMER": LevelEmergency, 32 | "ALRT": LevelAlert, 33 | "CRIT": LevelCritical, 34 | "EROR": LevelError, 35 | "WARN": LevelWarning, 36 | "INFO": LevelInformational, 37 | "DEBG": LevelDebug, 38 | "TRAC": LevelTrace, 39 | } 40 | 41 | // 注册实现的适配器, 当前支持控制台,文件和网络输出 42 | var adapters = make(map[string]Logger) 43 | 44 | // 日志记录等级字段 45 | var levelPrefix = [LevelTrace + 1]string{ 46 | "EMER", 47 | "ALRT", 48 | "CRIT", 49 | "EROR", 50 | "WARN", 51 | "INFO", 52 | "DEBG", 53 | "TRAC", 54 | } 55 | 56 | const ( 57 | logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式 58 | AdapterConsole = "console" // 控制台输出配置项 59 | AdapterFile = "file" // 文件输出配置项 60 | AdapterConn = "conn" // 网络输出配置项 61 | ) 62 | 63 | // log provider interface 64 | type Logger interface { 65 | Init(config string) error 66 | LogWrite(when time.Time, msg interface{}, level int) error 67 | Destroy() 68 | } 69 | 70 | // 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法 71 | func Register(name string, log Logger) { 72 | if log == nil { 73 | panic("logs: Register provide is nil") 74 | } 75 | if _, ok := adapters[name]; ok { 76 | panic("logs: Register called twice for provider " + name) 77 | } 78 | adapters[name] = log 79 | } 80 | 81 | type loginfo struct { 82 | Time string 83 | Level string 84 | Path string 85 | Name string 86 | Content string 87 | } 88 | 89 | type nameLogger struct { 90 | Logger 91 | name string 92 | config string 93 | } 94 | 95 | type LocalLogger struct { 96 | lock sync.Mutex 97 | init bool 98 | outputs []*nameLogger 99 | appName string 100 | callDepth int 101 | timeFormat string 102 | usePath string 103 | } 104 | 105 | func NewLogger(depth ...int) *LocalLogger { 106 | dep := append(depth, 2)[0] 107 | l := new(LocalLogger) 108 | // appName用于记录网络传输时标记的程序发送方, 109 | // 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方 110 | appSn := os.Getenv("APPSN") 111 | if appSn == "" { 112 | appSn = "NONE" 113 | } 114 | l.appName = "[" + appSn + "]" 115 | l.callDepth = dep 116 | l.SetLogger(AdapterConsole) 117 | l.timeFormat = logTimeDefaultFormat 118 | return l 119 | } 120 | 121 | //配置文件 122 | type logConfig struct { 123 | TimeFormat string `json:"TimeFormat"` 124 | Console *consoleLogger `json:"Console,omitempty"` 125 | File *fileLogger `json:"File,omitempty"` 126 | Conn *connLogger `json:"Conn,omitempty"` 127 | } 128 | 129 | func init() { 130 | defaultLogger = NewLogger(3) 131 | } 132 | 133 | func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error { 134 | this.lock.Lock() 135 | defer this.lock.Unlock() 136 | 137 | if !this.init { 138 | this.outputs = []*nameLogger{} 139 | this.init = true 140 | } 141 | 142 | config := append(configs, "{}")[0] 143 | var num int = -1 144 | var i int 145 | var l *nameLogger 146 | for i, l = range this.outputs { 147 | if l.name == adapterName { 148 | if l.config == config { 149 | //配置没有变动,不重新设置 150 | return fmt.Errorf("you have set same config for this adaptername %s", adapterName) 151 | } 152 | l.Logger.Destroy() 153 | num = i 154 | break 155 | } 156 | } 157 | logger, ok := adapters[adapterName] 158 | if !ok { 159 | return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName) 160 | } 161 | 162 | err := logger.Init(config) 163 | if err != nil { 164 | fmt.Fprintf(os.Stderr, "logger Init <%s> err:%v, %s output ignore!\n", 165 | adapterName, err, adapterName) 166 | return err 167 | } 168 | if num >= 0 { 169 | this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config} 170 | return nil 171 | } 172 | this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config}) 173 | return nil 174 | } 175 | 176 | func (this *LocalLogger) DelLogger(adapterName string) error { 177 | this.lock.Lock() 178 | defer this.lock.Unlock() 179 | outputs := []*nameLogger{} 180 | for _, lg := range this.outputs { 181 | if lg.name == adapterName { 182 | lg.Destroy() 183 | } else { 184 | outputs = append(outputs, lg) 185 | } 186 | } 187 | if len(outputs) == len(this.outputs) { 188 | return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName) 189 | } 190 | this.outputs = outputs 191 | return nil 192 | } 193 | 194 | // 设置日志起始路径 195 | func (this *LocalLogger) SetLogPathTrim(trimPath string) { 196 | this.usePath = trimPath 197 | } 198 | 199 | func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) { 200 | for _, l := range this.outputs { 201 | if l.name == AdapterConn { 202 | //网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索 203 | err := l.LogWrite(when, msg, level) 204 | if err != nil { 205 | fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) 206 | } 207 | continue 208 | } 209 | 210 | msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content 211 | err := l.LogWrite(when, msgStr, level) 212 | if err != nil { 213 | fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) 214 | } 215 | } 216 | } 217 | 218 | func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error { 219 | if !this.init { 220 | this.SetLogger(AdapterConsole) 221 | } 222 | msgSt := new(loginfo) 223 | src := "" 224 | if len(v) > 0 { 225 | msg = fmt.Sprintf(msg, v...) 226 | } 227 | when := time.Now() 228 | _, file, lineno, ok := runtime.Caller(this.callDepth) 229 | var strim string = "src/" 230 | if this.usePath != "" { 231 | strim = this.usePath 232 | } 233 | if ok { 234 | 235 | src = strings.Replace( 236 | fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1) 237 | } 238 | 239 | msgSt.Level = levelPrefix[logLevel] 240 | msgSt.Path = src 241 | msgSt.Content = msg 242 | msgSt.Name = this.appName 243 | msgSt.Time = when.Format(this.timeFormat) 244 | this.writeToLoggers(when, msgSt, logLevel) 245 | 246 | return nil 247 | } 248 | 249 | func (this *LocalLogger) Fatal(format string, args ...interface{}) { 250 | this.Emer("###Exec Panic:"+format, args...) 251 | os.Exit(1) 252 | } 253 | 254 | func (this *LocalLogger) Panic(format string, args ...interface{}) { 255 | this.Emer("###Exec Panic:"+format, args...) 256 | panic(fmt.Sprintf(format, args...)) 257 | } 258 | 259 | // Emer Log EMERGENCY level message. 260 | func (this *LocalLogger) Emer(format string, v ...interface{}) { 261 | this.writeMsg(LevelEmergency, format, v...) 262 | } 263 | 264 | // Alert Log ALERT level message. 265 | func (this *LocalLogger) Alert(format string, v ...interface{}) { 266 | this.writeMsg(LevelAlert, format, v...) 267 | } 268 | 269 | // Crit Log CRITICAL level message. 270 | func (this *LocalLogger) Crit(format string, v ...interface{}) { 271 | this.writeMsg(LevelCritical, format, v...) 272 | } 273 | 274 | // Error Log ERROR level message. 275 | func (this *LocalLogger) Error(format string, v ...interface{}) { 276 | this.writeMsg(LevelError, format, v...) 277 | } 278 | 279 | // Warn Log WARNING level message. 280 | func (this *LocalLogger) Warn(format string, v ...interface{}) { 281 | this.writeMsg(LevelWarning, format, v...) 282 | } 283 | 284 | // Info Log INFO level message. 285 | func (this *LocalLogger) Info(format string, v ...interface{}) { 286 | this.writeMsg(LevelInformational, format, v...) 287 | } 288 | 289 | // Debug Log DEBUG level message. 290 | func (this *LocalLogger) Debug(format string, v ...interface{}) { 291 | this.writeMsg(LevelDebug, format, v...) 292 | } 293 | 294 | // Trace Log TRAC level message. 295 | func (this *LocalLogger) Trace(format string, v ...interface{}) { 296 | this.writeMsg(LevelTrace, format, v...) 297 | } 298 | 299 | func (this *LocalLogger) Close() { 300 | 301 | for _, l := range this.outputs { 302 | l.Destroy() 303 | } 304 | this.outputs = nil 305 | 306 | } 307 | 308 | func (this *LocalLogger) Reset() { 309 | for _, l := range this.outputs { 310 | l.Destroy() 311 | } 312 | this.outputs = nil 313 | } 314 | 315 | func (this *LocalLogger) SetCallDepth(depth int) { 316 | this.callDepth = depth 317 | } 318 | 319 | // GetlocalLogger returns the defaultLogger 320 | func GetlocalLogger() *LocalLogger { 321 | return defaultLogger 322 | } 323 | 324 | // Reset will remove all the adapter 325 | func Reset() { 326 | defaultLogger.Reset() 327 | } 328 | 329 | func SetLogPathTrim(trimPath string) { 330 | defaultLogger.SetLogPathTrim(trimPath) 331 | } 332 | 333 | // param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台 334 | func SetLogger(param ...string) error { 335 | if 0 == len(param) { 336 | //默认只输出到控制台 337 | defaultLogger.SetLogger(AdapterConsole) 338 | return nil 339 | } 340 | 341 | c := param[0] 342 | conf := new(logConfig) 343 | err := json.Unmarshal([]byte(c), conf) 344 | if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出 345 | // Open the configuration file 346 | fd, err := os.Open(c) 347 | if err != nil { 348 | fmt.Fprintf(os.Stderr, "Could not open %s for configure: %s\n", c, err) 349 | os.Exit(1) 350 | return err 351 | } 352 | 353 | contents, err := ioutil.ReadAll(fd) 354 | if err != nil { 355 | fmt.Fprintf(os.Stderr, "Could not read %s: %s\n", c, err) 356 | os.Exit(1) 357 | return err 358 | } 359 | err = json.Unmarshal(contents, conf) 360 | if err != nil { 361 | fmt.Fprintf(os.Stderr, "Could not Unmarshal %s: %s\n", contents, err) 362 | os.Exit(1) 363 | return err 364 | } 365 | } 366 | if conf.TimeFormat != "" { 367 | defaultLogger.timeFormat = conf.TimeFormat 368 | } 369 | if conf.Console != nil { 370 | console, _ := json.Marshal(conf.Console) 371 | defaultLogger.SetLogger(AdapterConsole, string(console)) 372 | } 373 | if conf.File != nil { 374 | file, _ := json.Marshal(conf.File) 375 | defaultLogger.SetLogger(AdapterFile, string(file)) 376 | } 377 | if conf.Conn != nil { 378 | conn, _ := json.Marshal(conf.Conn) 379 | defaultLogger.SetLogger(AdapterConn, string(conn)) 380 | } 381 | return nil 382 | } 383 | 384 | // Painc logs a message at emergency level and panic. 385 | func Painc(f interface{}, v ...interface{}) { 386 | defaultLogger.Panic(formatLog(f, v...)) 387 | } 388 | 389 | // Fatal logs a message at emergency level and exit. 390 | func Fatal(f interface{}, v ...interface{}) { 391 | defaultLogger.Fatal(formatLog(f, v...)) 392 | } 393 | 394 | // Emer logs a message at emergency level. 395 | func Emer(f interface{}, v ...interface{}) { 396 | defaultLogger.Emer(formatLog(f, v...)) 397 | } 398 | 399 | // Alert logs a message at alert level. 400 | func Alert(f interface{}, v ...interface{}) { 401 | defaultLogger.Alert(formatLog(f, v...)) 402 | } 403 | 404 | // Crit logs a message at critical level. 405 | func Crit(f interface{}, v ...interface{}) { 406 | defaultLogger.Crit(formatLog(f, v...)) 407 | } 408 | 409 | // Error logs a message at error level. 410 | func Error(f interface{}, v ...interface{}) { 411 | defaultLogger.Error(formatLog(f, v...)) 412 | } 413 | 414 | // Warn logs a message at warning level. 415 | func Warn(f interface{}, v ...interface{}) { 416 | defaultLogger.Warn(formatLog(f, v...)) 417 | } 418 | 419 | // Info logs a message at info level. 420 | func Info(f interface{}, v ...interface{}) { 421 | defaultLogger.Info(formatLog(f, v...)) 422 | } 423 | 424 | // Notice logs a message at debug level. 425 | func Debug(f interface{}, v ...interface{}) { 426 | defaultLogger.Debug(formatLog(f, v...)) 427 | } 428 | 429 | // Trace logs a message at trace level. 430 | func Trace(f interface{}, v ...interface{}) { 431 | defaultLogger.Trace(formatLog(f, v...)) 432 | } 433 | 434 | func formatLog(f interface{}, v ...interface{}) string { 435 | var msg string 436 | switch f.(type) { 437 | case string: 438 | msg = f.(string) 439 | if len(v) == 0 { 440 | return msg 441 | } 442 | if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") { 443 | //format string 444 | } else { 445 | //do not contain format char 446 | msg += strings.Repeat(" %v", len(v)) 447 | } 448 | default: 449 | msg = fmt.Sprint(f) 450 | if len(v) == 0 { 451 | return msg 452 | } 453 | msg += strings.Repeat(" %v", len(v)) 454 | } 455 | return fmt.Sprintf(msg, v...) 456 | } 457 | 458 | func stringTrim(s string, cut string) string { 459 | ss := strings.SplitN(s, cut, 2) 460 | if 1 == len(ss) { 461 | return ss[0] 462 | } 463 | return ss[1] 464 | } 465 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var p = `{ 10 | "Console": { 11 | "level": "DEBG", 12 | "color": true 13 | }, 14 | "File": { 15 | "filename": "app.log", 16 | "level": "EROR", 17 | "daily": true, 18 | "maxlines": 1000000, 19 | "maxsize": 256, 20 | "maxdays": -1, 21 | "append": true, 22 | "permit": "0660" 23 | } 24 | }` 25 | 26 | func TestLogOut(t *testing.T) { 27 | SetLogger(p) 28 | Trace("this is Trace") 29 | Debug("this is Debug") 30 | Info("this is Info") 31 | Warn("this is Warn") 32 | Error("this is Error") 33 | Crit("this is Critical") 34 | Alert("this is Alert") 35 | Emer("this is Emergency") 36 | } 37 | 38 | func TestLogConfigReload(t *testing.T) { 39 | go func() { 40 | for { 41 | for level, _ := range LevelMap { 42 | SetLogger(fmt.Sprintf(`{ 43 | "Console": { 44 | "level": "%s", 45 | "color": true 46 | }, 47 | "File": { 48 | "filename": "app.log", 49 | "level": "%s", 50 | "daily": true, 51 | "maxlines": 1000000, 52 | "maxsize": 1, 53 | "maxdays": -1, 54 | "append": true, 55 | "permit": "0660" 56 | }}`, level, level)) 57 | time.Sleep(time.Second * 3) 58 | } 59 | } 60 | }() 61 | 62 | for { 63 | Trace("this is Trace") 64 | Debug("this is Debug") 65 | Info("this is Info") 66 | Warn("this is Warn") 67 | Error("this is Error") 68 | Crit("this is Critical") 69 | Alert("this is Alert") 70 | Emer("this is Emergency") 71 | fmt.Println() 72 | 73 | time.Sleep(time.Millisecond) 74 | } 75 | 76 | } 77 | 78 | func TestLogTimeFormat(t *testing.T) { 79 | 80 | var formats = map[string]string{"ANSIC": "Mon Jan _2 15:04:05 2006", 81 | "UnixDate": "Mon Jan _2 15:04:05 MST 2006", 82 | "RubyDate": "Mon Jan 02 15:04:05 -0700 2006", 83 | "RFC822": "02 Jan 06 15:04 MST", 84 | "RFC822Z": "02 Jan 06 15:04 -0700", 85 | "RFC850": "Monday, 02-Jan-06 15:04:05 MST", 86 | "RFC1123": "Mon, 02 Jan 2006 15:04:05 MST", 87 | "RFC1123Z": "Mon, 02 Jan 2006 15:04:05 -0700", 88 | "RFC3339": "2006-01-02T15:04:05Z07:00", 89 | "RFC3339Nano": "2006-01-02T15:04:05.999999999Z07:00", 90 | "Kitchen": "3:04PM", 91 | "Stamp": "Jan _2 15:04:05", 92 | "StampMilli": "Jan _2 15:04:05.000", 93 | "StampMicro": "Jan _2 15:04:05.000000", 94 | "StampNano": "Jan _2 15:04:05.000000000", 95 | } 96 | for timeType, format := range formats { 97 | SetLogger(fmt.Sprintf(`{ 98 | "TimeFormat":"%s", 99 | "Console": { 100 | "level": "TRAC", 101 | "color": true 102 | }, 103 | "File": { 104 | "filename": "app.log", 105 | "level": "TRAC", 106 | "daily": true, 107 | "maxlines": 1000000, 108 | "maxsize": 1, 109 | "maxdays": -1, 110 | "append": true, 111 | "permit": "0660" 112 | }}`, format)) 113 | fmt.Printf("========%s time format========\n", timeType) 114 | Trace("Trace", timeType) 115 | Debug("Debug", timeType) 116 | Info("Info", timeType) 117 | Warn("Warn", timeType) 118 | Error("Error", timeType) 119 | Crit("Critical", timeType) 120 | Alert("Alert", timeType) 121 | Emer("Emergency", timeType) 122 | } 123 | 124 | } 125 | --------------------------------------------------------------------------------