This commit is contained in:
cuisongliu
2019-04-19 09:59:46 +08:00
parent cdd91e0399
commit e973e30d36
10 changed files with 1154 additions and 24 deletions
+5
View File
@@ -133,6 +133,11 @@
"Comment": "v1.3.2",
"Rev": "9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9"
},
{
"ImportPath": "github.com/wonderivan/logger",
"Comment": "v1.0.0",
"Rev": "11d39e135b9d8f7994e0b70b26df77bf207d75aa"
},
{
"ImportPath": "golang.org/x/crypto/curve25519",
"Rev": "c2843e01d9a2bc60bb26ad24e09734fdc2d9ec58"
+7 -8
View File
@@ -15,7 +15,7 @@ kubernetes 1.14以下版本请移步老掉牙教程[sealos 1.x docs](https://git
# 快速使用
## 准备条件
* 装好docker并启动docker
* 把[离线安装包](http://store.lameleg.com) 下载好拷贝到所有节点的/root目录下, 不需要解压,如果有文件服务器更好,sealos支持从一个服务器上wget到所有节点上
* 把[离线安装包](http://store.lameleg.com) 下载好拷贝到执行节点的任意目录,不需要解压,sealos会自动检测各个节点是否有安装包,若不存在则会scp到该节点。如果有文件服务器更好,sealos也支持从一个服务器上wget到所有节点上。
## 安装
sealos已经放在离线包中,解压后在kube/bin目录下(可以解压一个,获取sealos bin文件)
@@ -23,12 +23,12 @@ sealos已经放在离线包中,解压后在kube/bin目录下(可以解压一
sealos init \
--master 192.168.0.2 \
--master 192.168.0.3 \
--master 192.168.0.4 \ # master地址列表
--node 192.168.0.5 \ # node地址列表
--user root \ # 服务用户名
--passwd your-server-password \ # 服务器密码,用于远程执行命令
--pkg kube1.14.1.tar.gz \ # 离线安装包名称
--version v1.14.1 # kubernetes 离线安装包版本,这渲染kubeadm配置时需要使用
--master 192.168.0.4 \ # master地址列表
--node 192.168.0.5 \ # node地址列表
--user root \ # 服务用户名
--passwd your-server-password \ # 服务器密码,用于远程执行命令
--pkg-url /root/kube1.14.1.tar.gz \ # 离线安装包位置,可支持http/https服务器(http://store.lameleg.com/kube1.14.1.tar.gz)存放和本地(/root/kube1.14.1.tar.gz)存放两种方式。若对应节点上文件不存在则会从执行机器上scp文件到对应节点。
--version v1.14.1 # kubernetes 离线安装包版本,这渲染kubeadm配置时需要使用
```
然后,就没有然后了
@@ -37,7 +37,6 @@ sealos init \
```
--kubeadm-config string kubeadm-config.yaml local # 自定义kubeadm配置文件,如有这个sealos就不去渲染kubeadm配置
--pkg-url string http://store.lameleg.com/kube1.14.1.tar.gz download offline pakage url # 支持从远程拉取离线包,省的每个机器拷贝,前提你得有个http服务器放离线包
--vip string virtual ip (default "10.103.97.2") # 代理master的虚拟IP,只要与你地址不冲突请不要改
```
+1 -1
View File
@@ -54,6 +54,6 @@ func init() {
initCmd.Flags().StringSliceVar(&masters, "master", []string{}, "kubernetes masters")
initCmd.Flags().StringSliceVar(&nodes, "node", []string{}, "kubernetes nodes")
initCmd.Flags().StringVar(&pkgURL, "pkg-url", "", "http://store.lameleg.com/kube1.14.1.tar.gz download offline pakage url")
initCmd.Flags().StringVar(&pkgURL, "pkg-url", "", "http://store.lameleg.com/kube1.14.1.tar.gz download offline pakage url, or file localtion ex. /root/kube1.14.1.tar.gz")
initCmd.Flags().StringVar(&install.Version, "version", "v1.14.1", "version is kubernetes version")
}
+15 -11
View File
@@ -23,7 +23,7 @@ func (s *SealosInstaller) SendPackage(url string) {
wgetCommand = fmt.Sprintf(" wget %s ", wgetParam)
}
remoteCmd := fmt.Sprintf("cd /root && %s %s && tar zxvf %s", wgetCommand, url, pkg)
localCmd := fmt.Sprintf("cd /root && tar zxvf %s ", pkg)
localCmd := fmt.Sprintf("cd /root && rm -rf kube && tar zxvf %s ", pkg)
kubeCmd := "cd /root/kube/shell && sh init.sh"
kubeLocal := fmt.Sprintf("/root/%s", pkg)
@@ -34,14 +34,16 @@ func (s *SealosInstaller) SendPackage(url string) {
go func(master string) {
defer wm.Done()
logger.Debug("please wait for tar zxvf exec")
if isHttp {
go WatchFileSize(master, kubeLocal, GetFileSize(url))
Cmd(master, remoteCmd)
if RemoteFilExist(master, kubeLocal) {
logger.Warn("host is ", master, ", SendPackage: file is exist")
} else {
if !RemoteFilExist(master, kubeLocal) {
if isHttp {
go WatchFileSize(master, kubeLocal, GetFileSize(url))
Cmd(master, remoteCmd)
} else {
Copy(master, url, kubeLocal)
Cmd(master, localCmd)
}
Cmd(master, localCmd)
}
Cmd(master, kubeCmd)
}(master)
@@ -51,14 +53,16 @@ func (s *SealosInstaller) SendPackage(url string) {
go func(node string) {
defer wn.Done()
logger.Debug("please wait for tar zxvf exec")
if isHttp {
go WatchFileSize(node, kubeLocal, GetFileSize(url))
Cmd(node, remoteCmd)
if RemoteFilExist(node, kubeLocal) {
logger.Warn("host is ", node, ", SendPackage: file is exist")
} else {
if !RemoteFilExist(node, kubeLocal) {
if isHttp {
go WatchFileSize(node, kubeLocal, GetFileSize(url))
Cmd(node, remoteCmd)
} else {
Copy(node, url, kubeLocal)
Cmd(node, localCmd)
}
Cmd(node, localCmd)
}
Cmd(node, kubeCmd)
}(node)
-4
View File
@@ -109,10 +109,6 @@ func RemoteFilExist(host, remoteFilePath string) bool {
//Copy is
func Copy(host, localFilePath, remoteFilePath string) {
if RemoteFilExist(host, remoteFilePath) {
logger.Warn("host is ", host, ", scpCopy: file is exist")
return
}
sftpClient, err := SftpConnect(User, Passwd, host)
if err != nil {
logger.Error("scpCopy:", err)
+143
View File
@@ -0,0 +1,143 @@
# logger
convenient log package
# 1. 使用说明
```go
import "github.com/wonderivan/logger"
// 配置logger,如果不配置时默认为控制台输出,等级为DEBG
logger.SetLogger(`{"Console": {"level": "DEBG"}`)
// 配置说明见下文
// 设置完成后,即可在控制台和日志文件app.log中看到如下输出
logger.Trace("this is Trace")
logger.Debug("this is Debug")
logger.Info("this is Info")
logger.Warn("this is Warn")
logger.Error("this is Error")
logger.Crit("this is Critical")
logger.Alert("this is Alert")
logger.Emer("this is Emergency")
```
输出结果:
![](images/output1.png)
# 2. 日志等级
当前日志输出等级共8种,从0-7对应的等级由高到底,当配置为某个输出等级时,只有大于等于该等级的日志才会输出。不同的输出适配器支持不同的日志等级配置:
| 等级 | 配置 | 释义 | 控制台颜色 |
| ---- | ---- | ------------------------------------------------ | ---------- |
| 0 | EMER | 系统级紧急,比如磁盘出错,内存异常,网络不可用等 | 红色底 |
| 1 | ALRT | 系统级警告,比如数据库访问异常,配置文件出错等 | 紫色 |
| 2 | CRIT | 系统级危险,比如权限出错,访问异常等 | 蓝色 |
| 3 | EROR | 用户级错误 | 红色 |
| 4 | WARN | 用户级警告 | 黄色 |
| 5 | INFO | 用户级重要 | 天蓝色 |
| 6 | DEBG | 用户级调试 | 绿色 |
| 7 | TRAC | 用户级基本输出 | 绿色 |
# 3. 配置说明
logger当前支持控制台、文件、网络3种方式适配器输出,可以通过各自的参数进行设置,该logger支持多个方式同时输出,如果未配置某项适配器时,则不初始化也不会输出到该适配器。
通过调用logger.SetLogger(config string)方法设置参数,config支持json配置,也支持指定内容为json配置的文件路径,例如:
```go
// 通过配置参数直接配置
logger.SetLogger(`{"Console": {"level": "DEBG"}}`)
// 通过配置文件配置
logger.SetLogger("/home/log.json")
```
```json
{
"TimeFormat":"2006-01-02 15:04:05", // 输出日志开头时间格式
"Console": { // 控制台日志配置
"level": "TRAC", // 控制台日志输出等级
"color": true // 控制台日志颜色开关
},
"File": { // 文件日志配置
"filename": "app.log", // 初始日志文件名
"level": "TRAC", // 日志文件日志输出等级
"daily": true, // 跨天后是否创建新日志文件,当append=true时有效
"maxlines": 1000000, // 日志文件最大行数,当append=true时有效
"maxsize": 1, // 日志文件最大大小,当append=true时有效
"maxdays": -1, // 日志文件有效期
"append": true, // 是否支持日志追加
"permit": "0660" // 新创建的日志文件权限属性
},
"Conn": { // 网络日志配置
"net":"tcp", // 日志传输模式
"addr":"10.1.55.10:1024", // 日志接收服务器
"level": "Warn", // 网络日志输出等级
"reconnect":true, // 网络断开后是否重连
"reconnectOnMsg":false, // 发送完每条消息后是否断开网络
}
}
```
- 时间格式
| 时间类型 | 时间格式 |
| ------------ | ----------------------------------------- |
| ANSIC | "Mon Jan _2 15:04:05 2006" |
| UnixDate | "Mon Jan _2 15:04:05 MST 2006" |
| RubyDate | "Mon Jan 02 15:04:05 -0700 2006" |
| RFC822 | "02 Jan 06 15:04 MST" |
| RFC822Z | "02 Jan 06 15:04 -0700" |
| RFC850 | "Monday, 02-Jan-06 15:04:05 MST" |
| RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" |
| RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" |
| RFC3339 | "2006-01-02T15:04:05Z07:00" |
| RFC3339Nano | "2006-01-02T15:04:05.999999999Z07:00" |
| Kitchen | "3:04PM" |
| Stamp | "Jan _2 15:04:05" |
| StampMilli | "Jan _2 15:04:05.000" |
| StampMicro | "Jan _2 15:04:05.000000" |
| StampNano | "Jan _2 15:04:05.000000000" |
| RFC3339Nano1 | "2006-01-02 15:04:05.999999999 -0700 MST" |
| DEFAULT | "2006-01-02 15:04:05" |
- 时间格式打印:
```
========RFC1123Z time format========
Thu, 02 Aug 2018 18:48:04 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123Z
========Stamp time format========
Aug 2 18:48:04 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Stamp
========StampMilli time format========
Aug 2 18:48:04.489 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMilli
========StampNano time format========
Aug 2 18:48:04.490002155 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampNano
========RubyDate time format========
Thu Aug 02 18:48:04 +0800 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RubyDate
========RFC822 time format========
02 Aug 18 18:48 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822
========RFC822Z time format========
02 Aug 18 18:48 +0800 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC822Z
========RFC1123 time format========
Thu, 02 Aug 2018 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC1123
========RFC3339 time format========
2018-08-02T18:48:04+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339
========RFC3339Nano time format========
2018-08-02T18:48:04.490377325+08:00 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC3339Nano
========ANSIC time format========
Thu Aug 2 18:48:04 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug ANSIC
========UnixDate time format========
Thu Aug 2 18:48:04 CST 2018 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug UnixDate
========RFC850 time format========
Thursday, 02-Aug-18 18:48:04 CST [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug RFC850
========Kitchen time format========
6:48PM [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug Kitchen
========StampMicro time format========
Aug 2 18:48:04.490662 [DEBG] [github.com/wonderivan/logger/log_test.go:115] Debug StampMicro
```
# 4. 其他
1. logger默认是控制台输出,输出等级为DEBG,默认是支持颜色区分的。
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,以此类推。
3. logger package默认初始化了全局的defaultLogger,直接调用logger包的Debug方法时,会默认调用defaultLogger.Debug,所以普通调用时,仅需要import logger即可使用。
4. 网络配置中的reconnectOnMsg为每条消息都重连一次网络日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源。但强烈不建议普通使用时设置为true,这将会导致调用方反复的网络重连,极大增加资源消耗和延迟。
5. conn网络输出适配器经过ELK集成环境的测试验证,通过该方式发送的日志,能够正常通过Elecsearch和Kibana检索和分析
Generated Vendored Executable
+141
View File
@@ -0,0 +1,141 @@
package logger
import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"strings"
"sync"
"time"
)
type connLogger struct {
sync.Mutex
innerWriter io.WriteCloser
ReconnectOnMsg bool `json:"reconnectOnMsg"`
Reconnect bool `json:"reconnect"`
Net string `json:"net"`
Addr string `json:"addr"`
Level string `json:"level"`
LogLevel int
illNetFlag bool //网络异常标记
}
func (c *connLogger) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
fmt.Printf("consoleWriter Init:%s\n", jsonConfig)
err := json.Unmarshal([]byte(jsonConfig), c)
if err != nil {
return err
}
if l, ok := LevelMap[c.Level]; ok {
c.LogLevel = l
}
if c.innerWriter != nil {
c.innerWriter.Close()
c.innerWriter = nil
}
return nil
}
func (c *connLogger) LogWrite(when time.Time, msgText interface{}, level int) (err error) {
if level > c.LogLevel {
return nil
}
msg, ok := msgText.(*loginfo)
if !ok {
return
}
if c.needToConnectOnMsg() {
err = c.connect()
if err != nil {
return
}
//重连成功
c.illNetFlag = false
}
//每条消息都重连一次日志中心,适用于写日志频率极低的情况下的服务调用,避免长时间连接,占用资源
if c.ReconnectOnMsg { // 频繁日志发送切勿开启
defer c.innerWriter.Close()
}
//网络异常时,消息发出
if !c.illNetFlag {
err = c.println(when, msg)
//网络异常,通知处理网络的go程自动重连
if err != nil {
c.illNetFlag = true
}
}
return
}
func (c *connLogger) Destroy() {
if c.innerWriter != nil {
c.innerWriter.Close()
}
}
func (c *connLogger) connect() error {
if c.innerWriter != nil {
c.innerWriter.Close()
c.innerWriter = nil
}
addrs := strings.Split(c.Addr, ";")
for _, addr := range addrs {
conn, err := net.Dial(c.Net, addr)
if err != nil {
fmt.Fprintf(os.Stderr, "net.Dial error:%v\n", err)
continue
//return err
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
}
c.innerWriter = conn
return nil
}
return fmt.Errorf("hava no valid logs service addr:%v", c.Addr)
}
func (c *connLogger) needToConnectOnMsg() bool {
if c.Reconnect {
c.Reconnect = false
return true
}
if c.innerWriter == nil {
return true
}
if c.illNetFlag {
return true
}
return c.ReconnectOnMsg
}
func (c *connLogger) println(when time.Time, msg *loginfo) error {
c.Lock()
defer c.Unlock()
ss, err := json.Marshal(msg)
if err != nil {
return err
}
_, err = c.innerWriter.Write(append(ss, '\n'))
//返回err,解决日志系统网络异常后的自动重连
return err
}
func init() {
Register(AdapterConn, &connLogger{LogLevel: LevelTrace})
}
+92
View File
@@ -0,0 +1,92 @@
package logger
import (
"encoding/json"
"fmt"
"os"
"runtime"
"sync"
"time"
)
type brush func(string) string
func newBrush(color string) brush {
pre := "\033["
reset := "\033[0m"
return func(text string) string {
return pre + color + "m" + text + reset
}
}
//鉴于终端的通常使用习惯,一般白色和黑色字体是不可行的,所以30,37不可用,
var colors = []brush{
newBrush("1;41"), // Emergency 红色底
newBrush("1;35"), // Alert 紫色
newBrush("1;34"), // Critical 蓝色
newBrush("1;31"), // Error 红色
newBrush("1;33"), // Warn 黄色
newBrush("1;36"), // Informational 天蓝色
newBrush("1;32"), // Debug 绿色
newBrush("1;32"), // Trace 绿色
}
type consoleLogger struct {
sync.Mutex
Level string `json:"level"`
Colorful bool `json:"color"`
LogLevel int
}
func (c *consoleLogger) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
if jsonConfig != "{}" {
fmt.Fprintf(os.Stdout, "consoleLogger Init:%s\n", jsonConfig)
}
err := json.Unmarshal([]byte(jsonConfig), c)
if runtime.GOOS == "windows" {
c.Colorful = false
}
if l, ok := LevelMap[c.Level]; ok {
c.LogLevel = l
return nil
}
return err
}
func (c *consoleLogger) LogWrite(when time.Time, msgText interface{}, level int) error {
if level > c.LogLevel {
return nil
}
msg, ok := msgText.(string)
if !ok {
return nil
}
if c.Colorful {
msg = colors[level](msg)
}
c.printlnConsole(when, msg)
return nil
}
func (c *consoleLogger) Destroy() {
}
func (c *consoleLogger) printlnConsole(when time.Time, msg string) {
c.Lock()
defer c.Unlock()
os.Stdout.Write(append([]byte(msg), '\n'))
}
func init() {
Register(AdapterConsole, &consoleLogger{
LogLevel: LevelDebug,
Colorful: runtime.GOOS != "windows",
})
}
Generated Vendored Executable
+286
View File
@@ -0,0 +1,286 @@
package logger
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
)
type fileLogger struct {
sync.RWMutex
fileWriter *os.File
Filename string `json:"filename"`
Append bool `json:"append"`
MaxLines int `json:"maxlines"`
MaxSize int `json:"maxsize"`
Daily bool `json:"daily"`
MaxDays int64 `json:"maxdays"`
Level string `json:"level"`
PermitMask string `json:"permit"`
LogLevel int
maxSizeCurSize int
maxLinesCurLines int
dailyOpenDate int
dailyOpenTime time.Time
fileNameOnly, suffix string
}
// Init file logger with json config.
// jsonConfig like:
// {
// "filename":"log/app.log",
// "maxlines":10000,
// "maxsize":1024,
// "daily":true,
// "maxdays":15,
// "rotate":true,
// "permit":"0600"
// }
func (f *fileLogger) Init(jsonConfig string) error {
fmt.Printf("fileLogger Init:%s\n", jsonConfig)
if len(jsonConfig) == 0 {
return nil
}
err := json.Unmarshal([]byte(jsonConfig), f)
if err != nil {
return err
}
if len(f.Filename) == 0 {
return errors.New("jsonconfig must have filename")
}
f.suffix = filepath.Ext(f.Filename)
f.fileNameOnly = strings.TrimSuffix(f.Filename, f.suffix)
f.MaxSize *= 1024 * 1024 // 将单位转换成MB
if f.suffix == "" {
f.suffix = ".log"
}
if l, ok := LevelMap[f.Level]; ok {
f.LogLevel = l
}
err = f.newFile()
return err
}
func (f *fileLogger) needCreateFresh(size int, day int) bool {
return (f.MaxLines > 0 && f.maxLinesCurLines >= f.MaxLines) ||
(f.MaxSize > 0 && f.maxSizeCurSize+size >= f.MaxSize) ||
(f.Daily && day != f.dailyOpenDate)
}
// WriteMsg write logger message into file.
func (f *fileLogger) LogWrite(when time.Time, msgText interface{}, level int) error {
msg, ok := msgText.(string)
if !ok {
return nil
}
if level > f.LogLevel {
return nil
}
day := when.Day()
msg += "\n"
if f.Append {
f.RLock()
if f.needCreateFresh(len(msg), day) {
f.RUnlock()
f.Lock()
if f.needCreateFresh(len(msg), day) {
if err := f.createFreshFile(when); err != nil {
fmt.Fprintf(os.Stderr, "createFreshFile(%q): %s\n", f.Filename, err)
}
}
f.Unlock()
} else {
f.RUnlock()
}
}
f.Lock()
_, err := f.fileWriter.Write([]byte(msg))
if err == nil {
f.maxLinesCurLines++
f.maxSizeCurSize += len(msg)
}
f.Unlock()
return err
}
func (f *fileLogger) createLogFile() (*os.File, error) {
// Open the log file
perm, err := strconv.ParseInt(f.PermitMask, 8, 64)
if err != nil {
return nil, err
}
fd, err := os.OpenFile(f.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
os.Chmod(f.Filename, os.FileMode(perm))
}
return fd, err
}
func (f *fileLogger) newFile() error {
file, err := f.createLogFile()
if err != nil {
return err
}
if f.fileWriter != nil {
f.fileWriter.Close()
}
f.fileWriter = file
fInfo, err := file.Stat()
if err != nil {
return fmt.Errorf("get stat err: %s", err)
}
f.maxSizeCurSize = int(fInfo.Size())
f.dailyOpenTime = time.Now()
f.dailyOpenDate = f.dailyOpenTime.Day()
f.maxLinesCurLines = 0
if f.maxSizeCurSize > 0 {
count, err := f.lines()
if err != nil {
return err
}
f.maxLinesCurLines = count
}
return nil
}
func (f *fileLogger) lines() (int, error) {
fd, err := os.Open(f.Filename)
if err != nil {
return 0, err
}
defer fd.Close()
buf := make([]byte, 32768) // 32k
count := 0
lineSep := []byte{'\n'}
for {
c, err := fd.Read(buf)
if err != nil && err != io.EOF {
return count, err
}
count += bytes.Count(buf[:c], lineSep)
if err == io.EOF {
break
}
}
return count, nil
}
// new file name like xx.2013-01-01.001.log
func (f *fileLogger) createFreshFile(logTime time.Time) error {
// file exists
// Find the next available number
num := 1
fName := ""
rotatePerm, err := strconv.ParseInt(f.PermitMask, 8, 64)
if err != nil {
return err
}
_, err = os.Lstat(f.Filename)
if err != nil {
// 初始日志文件不存在,无需创建新文件
goto RESTART_LOGGER
}
// 日期变了, 说明跨天,重命名时需要保存为昨天的日期
if f.dailyOpenDate != logTime.Day() {
for ; err == nil && num <= 999; num++ {
fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", f.dailyOpenTime.Format("2006-01-02"), num, f.suffix)
_, err = os.Lstat(fName)
}
} else { //如果仅仅是文件大小或行数达到了限制,仅仅变更后缀序号即可
for ; err == nil && num <= 999; num++ {
fName = f.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, f.suffix)
_, err = os.Lstat(fName)
}
}
if err == nil {
return fmt.Errorf("Cannot find free log number to rename %s", f.Filename)
}
f.fileWriter.Close()
// 当创建新文件标记为true时
// 当日志文件超过最大限制行
// 当日志文件超过最大限制字节
// 当日志文件隔天更新标记为true时
// 将旧文件重命名,然后创建新文件
err = os.Rename(f.Filename, fName)
if err != nil {
fmt.Fprintf(os.Stderr, "os.Rename %s to %s err:%s\n", f.Filename, fName, err.Error())
goto RESTART_LOGGER
}
err = os.Chmod(fName, os.FileMode(rotatePerm))
RESTART_LOGGER:
startLoggerErr := f.newFile()
go f.deleteOldLog()
if startLoggerErr != nil {
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
}
if err != nil {
return fmt.Errorf("Rotate: %s", err)
}
return nil
}
func (f *fileLogger) deleteOldLog() {
dir := filepath.Dir(f.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
}
}()
if info == nil {
return
}
if f.MaxDays != -1 && !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(f.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(f.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), f.suffix) {
os.Remove(path)
}
}
return
})
}
func (f *fileLogger) Destroy() {
f.fileWriter.Close()
}
func init() {
Register(AdapterFile, &fileLogger{
Daily: true,
MaxDays: 7,
Append: true,
LogLevel: LevelDebug,
PermitMask: "0777",
MaxLines: 10,
MaxSize: 10 * 1024 * 1024,
})
}
Generated Vendored Executable
+464
View File
@@ -0,0 +1,464 @@
package logger
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"runtime"
"strings"
"sync"
"time"
)
// 默认日志输出
var defaultLogger *LocalLogger
// 日志等级,从0-7,日优先级由高到低
const (
LevelEmergency = iota // 系统级紧急,比如磁盘出错,内存异常,网络不可用等
LevelAlert // 系统级警告,比如数据库访问异常,配置文件出错等
LevelCritical // 系统级危险,比如权限出错,访问异常等
LevelError // 用户级错误
LevelWarning // 用户级警告
LevelInformational // 用户级信息
LevelDebug // 用户级调试
LevelTrace // 用户级基本输出
)
// 日志等级和描述映射关系
var LevelMap = map[string]int{
"EMER": LevelEmergency,
"ALRT": LevelAlert,
"CRIT": LevelCritical,
"EROR": LevelError,
"WARN": LevelWarning,
"INFO": LevelInformational,
"DEBG": LevelDebug,
"TRAC": LevelTrace,
}
// 注册实现的适配器, 当前支持控制台,文件和网络输出
var adapters = make(map[string]Logger)
// 日志记录等级字段
var levelPrefix = [LevelTrace + 1]string{
"EMER",
"ALRT",
"CRIT",
"EROR",
"WARN",
"INFO",
"DEBG",
"TRAC",
}
const (
logTimeDefaultFormat = "2006-01-02 15:04:05" // 日志输出默认格式
AdapterConsole = "console" // 控制台输出配置项
AdapterFile = "file" // 文件输出配置项
AdapterConn = "conn" // 网络输出配置项
)
// log provider interface
type Logger interface {
Init(config string) error
LogWrite(when time.Time, msg interface{}, level int) error
Destroy()
}
// 日志输出适配器注册,log需要实现Init,LogWrite,Destroy方法
func Register(name string, log Logger) {
if log == nil {
panic("logs: Register provide is nil")
}
if _, ok := adapters[name]; ok {
panic("logs: Register called twice for provider " + name)
}
adapters[name] = log
}
type loginfo struct {
Time string
Level string
Path string
Name string
Content string
}
type nameLogger struct {
Logger
name string
config string
}
type LocalLogger struct {
lock sync.Mutex
init bool
outputs []*nameLogger
appName string
callDepth int
timeFormat string
usePath string
}
func NewLogger(depth ...int) *LocalLogger {
dep := append(depth, 2)[0]
l := new(LocalLogger)
// appName用于记录网络传输时标记的程序发送方,
// 通过环境变量APPSN进行设置,默认为NONE,此时无法通过网络日志检索区分不同服务发送方
appSn := os.Getenv("APPSN")
if appSn == "" {
appSn = "NONE"
}
l.appName = "[" + appSn + "]"
l.callDepth = dep
l.SetLogger(AdapterConsole)
l.timeFormat = logTimeDefaultFormat
return l
}
//配置文件
type logConfig struct {
TimeFormat string `json:"TimeFormat"`
Console *consoleLogger `json:"Console,omitempty"`
File *fileLogger `json:"File,omitempty"`
Conn *connLogger `json:"Conn,omitempty"`
}
func init() {
defaultLogger = NewLogger(3)
}
func (this *LocalLogger) SetLogger(adapterName string, configs ...string) error {
this.lock.Lock()
defer this.lock.Unlock()
if !this.init {
this.outputs = []*nameLogger{}
this.init = true
}
config := append(configs, "{}")[0]
var num int = -1
var i int
var l *nameLogger
for i, l = range this.outputs {
if l.name == adapterName {
if l.config == config {
//配置没有变动,不重新设置
return fmt.Errorf("you have set same config for this adaptername %s", adapterName)
}
l.Logger.Destroy()
num = i
break
}
}
logger, ok := adapters[adapterName]
if !ok {
return fmt.Errorf("unknown adaptername %s (forgotten Register?)", adapterName)
}
err := logger.Init(config)
if err != nil {
fmt.Fprintf(os.Stderr, "logger Init <%s> err:%v, %s output ignore!\n",
adapterName, err, adapterName)
return err
}
if num >= 0 {
this.outputs[i] = &nameLogger{name: adapterName, Logger: logger, config: config}
return nil
}
this.outputs = append(this.outputs, &nameLogger{name: adapterName, Logger: logger, config: config})
return nil
}
func (this *LocalLogger) DelLogger(adapterName string) error {
this.lock.Lock()
defer this.lock.Unlock()
outputs := []*nameLogger{}
for _, lg := range this.outputs {
if lg.name == adapterName {
lg.Destroy()
} else {
outputs = append(outputs, lg)
}
}
if len(outputs) == len(this.outputs) {
return fmt.Errorf("logs: unknown adaptername %s (forgotten Register?)", adapterName)
}
this.outputs = outputs
return nil
}
// 设置日志起始路径
func (this *LocalLogger) SetLogPathTrim(trimPath string) {
this.usePath = trimPath
}
func (this *LocalLogger) writeToLoggers(when time.Time, msg *loginfo, level int) {
for _, l := range this.outputs {
if l.name == AdapterConn {
//网络日志,使用json格式发送,此处使用结构体,用于类似ElasticSearch功能检索
err := l.LogWrite(when, msg, level)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
}
continue
}
msgStr := when.Format(this.timeFormat) + " [" + msg.Level + "] " + "[" + msg.Path + "] " + msg.Content
err := l.LogWrite(when, msgStr, level)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
}
}
}
func (this *LocalLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
if !this.init {
this.SetLogger(AdapterConsole)
}
msgSt := new(loginfo)
src := ""
if len(v) > 0 {
msg = fmt.Sprintf(msg, v...)
}
when := time.Now()
_, file, lineno, ok := runtime.Caller(this.callDepth)
var strim string = "src/"
if this.usePath != "" {
strim = this.usePath
}
if ok {
src = strings.Replace(
fmt.Sprintf("%s:%d", stringTrim(file, strim), lineno), "%2e", ".", -1)
}
msgSt.Level = levelPrefix[logLevel]
msgSt.Path = src
msgSt.Content = msg
msgSt.Name = this.appName
msgSt.Time = when.Format(this.timeFormat)
this.writeToLoggers(when, msgSt, logLevel)
return nil
}
func (this *LocalLogger) Fatal(format string, args ...interface{}) {
this.Emer("###Exec Panic:"+format, args...)
os.Exit(1)
}
func (this *LocalLogger) Panic(format string, args ...interface{}) {
this.Emer("###Exec Panic:"+format, args...)
panic(fmt.Sprintf(format, args...))
}
// Emer Log EMERGENCY level message.
func (this *LocalLogger) Emer(format string, v ...interface{}) {
this.writeMsg(LevelEmergency, format, v...)
}
// Alert Log ALERT level message.
func (this *LocalLogger) Alert(format string, v ...interface{}) {
this.writeMsg(LevelAlert, format, v...)
}
// Crit Log CRITICAL level message.
func (this *LocalLogger) Crit(format string, v ...interface{}) {
this.writeMsg(LevelCritical, format, v...)
}
// Error Log ERROR level message.
func (this *LocalLogger) Error(format string, v ...interface{}) {
this.writeMsg(LevelError, format, v...)
}
// Warn Log WARNING level message.
func (this *LocalLogger) Warn(format string, v ...interface{}) {
this.writeMsg(LevelWarning, format, v...)
}
// Info Log INFO level message.
func (this *LocalLogger) Info(format string, v ...interface{}) {
this.writeMsg(LevelInformational, format, v...)
}
// Debug Log DEBUG level message.
func (this *LocalLogger) Debug(format string, v ...interface{}) {
this.writeMsg(LevelDebug, format, v...)
}
// Trace Log TRAC level message.
func (this *LocalLogger) Trace(format string, v ...interface{}) {
this.writeMsg(LevelTrace, format, v...)
}
func (this *LocalLogger) Close() {
for _, l := range this.outputs {
l.Destroy()
}
this.outputs = nil
}
func (this *LocalLogger) Reset() {
for _, l := range this.outputs {
l.Destroy()
}
this.outputs = nil
}
func (this *LocalLogger) SetCallDepth(depth int) {
this.callDepth = depth
}
// GetlocalLogger returns the defaultLogger
func GetlocalLogger() *LocalLogger {
return defaultLogger
}
// Reset will remove all the adapter
func Reset() {
defaultLogger.Reset()
}
func SetLogPathTrim(trimPath string) {
defaultLogger.SetLogPathTrim(trimPath)
}
// param 可以是log配置文件名,也可以是log配置内容,默认DEBUG输出到控制台
func SetLogger(param ...string) error {
if 0 == len(param) {
//默认只输出到控制台
defaultLogger.SetLogger(AdapterConsole)
return nil
}
c := param[0]
conf := new(logConfig)
err := json.Unmarshal([]byte(c), conf)
if err != nil { //不是json,就认为是配置文件,如果都不是,打印日志,然后退出
// Open the configuration file
fd, err := os.Open(c)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not open %s for configure: %s\n", c, err)
os.Exit(1)
return err
}
contents, err := ioutil.ReadAll(fd)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not read %s: %s\n", c, err)
os.Exit(1)
return err
}
err = json.Unmarshal(contents, conf)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not Unmarshal %s: %s\n", contents, err)
os.Exit(1)
return err
}
}
if conf.TimeFormat != "" {
defaultLogger.timeFormat = conf.TimeFormat
}
if conf.Console != nil {
console, _ := json.Marshal(conf.Console)
defaultLogger.SetLogger(AdapterConsole, string(console))
}
if conf.File != nil {
file, _ := json.Marshal(conf.File)
defaultLogger.SetLogger(AdapterFile, string(file))
}
if conf.Conn != nil {
conn, _ := json.Marshal(conf.Conn)
defaultLogger.SetLogger(AdapterConn, string(conn))
}
return nil
}
// Painc logs a message at emergency level and panic.
func Painc(f interface{}, v ...interface{}) {
defaultLogger.Panic(formatLog(f, v...))
}
// Fatal logs a message at emergency level and exit.
func Fatal(f interface{}, v ...interface{}) {
defaultLogger.Fatal(formatLog(f, v...))
}
// Emer logs a message at emergency level.
func Emer(f interface{}, v ...interface{}) {
defaultLogger.Emer(formatLog(f, v...))
}
// Alert logs a message at alert level.
func Alert(f interface{}, v ...interface{}) {
defaultLogger.Alert(formatLog(f, v...))
}
// Crit logs a message at critical level.
func Crit(f interface{}, v ...interface{}) {
defaultLogger.Crit(formatLog(f, v...))
}
// Error logs a message at error level.
func Error(f interface{}, v ...interface{}) {
defaultLogger.Error(formatLog(f, v...))
}
// Warn logs a message at warning level.
func Warn(f interface{}, v ...interface{}) {
defaultLogger.Warn(formatLog(f, v...))
}
// Info logs a message at info level.
func Info(f interface{}, v ...interface{}) {
defaultLogger.Info(formatLog(f, v...))
}
// Notice logs a message at debug level.
func Debug(f interface{}, v ...interface{}) {
defaultLogger.Debug(formatLog(f, v...))
}
// Trace logs a message at trace level.
func Trace(f interface{}, v ...interface{}) {
defaultLogger.Trace(formatLog(f, v...))
}
func formatLog(f interface{}, v ...interface{}) string {
var msg string
switch f.(type) {
case string:
msg = f.(string)
if len(v) == 0 {
return msg
}
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
//format string
} else {
//do not contain format char
msg += strings.Repeat(" %v", len(v))
}
default:
msg = fmt.Sprint(f)
if len(v) == 0 {
return msg
}
msg += strings.Repeat(" %v", len(v))
}
return fmt.Sprintf(msg, v...)
}
func stringTrim(s string, cut string) string {
ss := strings.SplitN(s, cut, 2)
if 1 == len(ss) {
return ss[0]
}
return ss[1]
}