From e973e30d361da976d80b7267fcb3a815a9d4e95e Mon Sep 17 00:00:00 2001 From: cuisongliu Date: Fri, 19 Apr 2019 09:59:46 +0800 Subject: [PATCH] fix pkg --- Godeps/Godeps.json | 5 + README_zh.md | 15 +- cmd/init.go | 2 +- install/send_package.go | 26 +- install/utils.go | 4 - vendor/github.com/wonderivan/logger/README.md | 143 ++++++ vendor/github.com/wonderivan/logger/conn.go | 141 ++++++ .../github.com/wonderivan/logger/console.go | 92 ++++ vendor/github.com/wonderivan/logger/file.go | 286 +++++++++++ vendor/github.com/wonderivan/logger/log.go | 464 ++++++++++++++++++ 10 files changed, 1154 insertions(+), 24 deletions(-) create mode 100644 vendor/github.com/wonderivan/logger/README.md create mode 100755 vendor/github.com/wonderivan/logger/conn.go create mode 100755 vendor/github.com/wonderivan/logger/console.go create mode 100755 vendor/github.com/wonderivan/logger/file.go create mode 100755 vendor/github.com/wonderivan/logger/log.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 30752e929..e6bb88826 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -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" diff --git a/README_zh.md b/README_zh.md index e1d4416cb..09affb8a8 100644 --- a/README_zh.md +++ b/README_zh.md @@ -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,只要与你地址不冲突请不要改 ``` diff --git a/cmd/init.go b/cmd/init.go index 1c56d3c22..6fb17d2d9 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -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") } diff --git a/install/send_package.go b/install/send_package.go index 05d37daa8..40f12f7cf 100644 --- a/install/send_package.go +++ b/install/send_package.go @@ -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) diff --git a/install/utils.go b/install/utils.go index b0c0291bb..9fa3c13fc 100644 --- a/install/utils.go +++ b/install/utils.go @@ -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) diff --git a/vendor/github.com/wonderivan/logger/README.md b/vendor/github.com/wonderivan/logger/README.md new file mode 100644 index 000000000..43d62bd59 --- /dev/null +++ b/vendor/github.com/wonderivan/logger/README.md @@ -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检索和分析 \ No newline at end of file diff --git a/vendor/github.com/wonderivan/logger/conn.go b/vendor/github.com/wonderivan/logger/conn.go new file mode 100755 index 000000000..d914f8e27 --- /dev/null +++ b/vendor/github.com/wonderivan/logger/conn.go @@ -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}) +} diff --git a/vendor/github.com/wonderivan/logger/console.go b/vendor/github.com/wonderivan/logger/console.go new file mode 100755 index 000000000..842d83b4c --- /dev/null +++ b/vendor/github.com/wonderivan/logger/console.go @@ -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", + }) +} diff --git a/vendor/github.com/wonderivan/logger/file.go b/vendor/github.com/wonderivan/logger/file.go new file mode 100755 index 000000000..a7654a97e --- /dev/null +++ b/vendor/github.com/wonderivan/logger/file.go @@ -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, + }) +} diff --git a/vendor/github.com/wonderivan/logger/log.go b/vendor/github.com/wonderivan/logger/log.go new file mode 100755 index 000000000..e6c4acdf7 --- /dev/null +++ b/vendor/github.com/wonderivan/logger/log.go @@ -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] +}