Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
8b94a0d256 | |||
38247cf3c9 | |||
36a085eccb | |||
|
e0682ed122 | ||
|
cd85fd66ff | ||
|
3c03b5c497 | ||
|
cd94900f19 | ||
|
ce9ab060d9 | ||
|
c653a48667 | ||
|
8c78c2a6e2 | ||
|
3a09faf4b8 | ||
|
8998a4938d | ||
|
01c35869b8 | ||
|
1d9b78eb37 | ||
|
b3a1b78006 | ||
|
c014aca84b | ||
|
4843033fe0 | ||
|
5e9f6ee73f | ||
|
27ff5fb3db | ||
|
eebd6500f3 | ||
|
d6024cf64c | ||
|
1e57935100 | ||
|
e6655504e9 | ||
|
3fc7c01156 | ||
|
8d7fdb2a98 | ||
|
f35dc15fb2 | ||
|
48f1a7b6ca | ||
|
f0f0b1db40 | ||
|
e662190f7d | ||
|
3acbffe5f4 | ||
|
7cb5ecc181 | ||
|
ed9201b453 | ||
|
973fd817d5 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
runtime
|
||||
|
90
README.md
90
README.md
@ -1,2 +1,92 @@
|
||||
# grlogs
|
||||
本库为争游内部日志公共库, 该库基于 beegoLogger 基础上完善, 目前支持的引擎有 file、console、net、smtp、es、alisls
|
||||
|
||||
## 代码示例
|
||||
|
||||
1. 引入
|
||||
```
|
||||
import "golib.gaore.com/GaoreGo/grlogs"
|
||||
```
|
||||
2. 简单用法
|
||||
```
|
||||
grlogs.Get("test", 128).Info("hello word")
|
||||
grlogs.Get("test").Warning("hello word")
|
||||
```
|
||||
`Get` 方法中 `lable` 参数为标签,为识别分类所用,在Grlogs里一个分类使用一个管道进行日志
|
||||
|
||||
3. 进阶用法
|
||||
```
|
||||
logger := grlogs.GetEs("wifi")
|
||||
logger.SetAdapter(LevelAll, AdapterElasticSearch)
|
||||
logger.SetAdapter(LevelInfo, AdapterFile)
|
||||
logger.Critical("出错了")
|
||||
logger.Info("出错了")
|
||||
```
|
||||
|
||||
4. 如果需要写入es 或 alils, 必须设置环境变量 `GRLOG_APP_NAME`, 不能有反斜杠, 如
|
||||
```shell script
|
||||
export GRLOG_APP_NAME=mkt.gaore.com;
|
||||
```
|
||||
还需要额外引入es库,完成初始化动作
|
||||
```go
|
||||
import _ "golib.gaore.com/GaoreGo/grlogs/logs/es"
|
||||
```
|
||||
|
||||
```go
|
||||
import _ "golib.gaore.com/GaoreGo/grlogs/logs/alils"
|
||||
```
|
||||
|
||||
5. 文件日志会写入到 `./runtime/logs/` 文件夹 **请务必在项目构建阶段创建该目录**
|
||||
|
||||
|
||||
6. `AliLS` 日志接入的是阿里SLS GOSDK , 下面是关于 [https://github.com/aliyun/aliyun-log-go-sdk/tree/master/producer] 描述
|
||||
|
||||
producer提供了两种关闭模式,分为有限关闭和安全关闭,安全关闭会等待producer中缓存的所有的数据全部发送完成以后在关闭producer,有限关闭会接收用户传递的一个参数值,时间单位为秒,当开始关闭producer的时候开始计时,超过传递的设定值还未能完全关闭producer的话会强制退出producer,此时可能会有部分数据未被成功发送而丢失。
|
||||
|
||||
所以用了aliLS 的 adapter 时,最好调用`grlogs.Close()` 或 `grlogs.CloseAll()` 方法安全关闭通道,以刷新缓冲区
|
||||
|
||||
7. 完整示例
|
||||
```go
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "golib.gaore.com/GaoreGo/grlogs/logs/es"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetLogger(t *testing.T) {
|
||||
// 新建 channel 大小为128 标识为nds 日志通道 , Get 的方法 默认带 console 和 file 输出
|
||||
l := grlogs.Get("nds", 128).SetAdapter(LevelAll, AdapterElasticSearch)
|
||||
l.Debug("我正在调试")
|
||||
l.Critical("出错了")
|
||||
|
||||
// 复用 nds 的日志通道
|
||||
grlogs.Get("nds").Warning("hadoee %s", time.Now().Format(time.RFC1123))
|
||||
grlogs.Get("nds").Warning("hadoee %s", time.Now().Format(time.RFC1123))
|
||||
|
||||
// 新建 channel 大小为默认 标识为wifi 日志通道 , GetEs 的方法 默认带 console 和 file 和 elatisearch 输出
|
||||
grlogs.GetEs("wifi")
|
||||
for i := 0; i < 10; i++ {
|
||||
grlogs.Get("wifi").Warning("Warning")
|
||||
grlogs.Get("wifi").Warn("Warn")
|
||||
grlogs.Get("wifi").Debug("Debug")
|
||||
grlogs.Get("wifi").Error("Error")
|
||||
grlogs.Get("wifi").Notice("Notice")
|
||||
grlogs.Get("wifi").Info("Info")
|
||||
grlogs.Get("wifi").Alert("Alert")
|
||||
}
|
||||
|
||||
Get("wifi").Critical("neoweiwoewe")
|
||||
}
|
||||
|
||||
func TestDropAdapter(t *testing.T) {
|
||||
grlogs.SetAdapter(LevelAll, AdapterAliLs)
|
||||
grlogs.DropAdapter(AdapterAliLs)
|
||||
grlogs.Informational(errors.New("he hello"))
|
||||
grlogs.SetAdapter(LevelAll, AdapterAliLs)
|
||||
grlogs.Debug(errors.New("he hello"))
|
||||
grlogs.CloseAll()
|
||||
}
|
||||
```
|
73
config.go
Normal file
73
config.go
Normal file
@ -0,0 +1,73 @@
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type ConsoleLogConfig struct {
|
||||
Level Level `json:"level"`
|
||||
}
|
||||
|
||||
func (c *ConsoleLogConfig) String() string {
|
||||
b, _ := json.Marshal(c)
|
||||
return bytes.NewBuffer(b).String()
|
||||
}
|
||||
|
||||
type FileLogConfig struct {
|
||||
Filename string `json:"filename"`
|
||||
Level Level `json:"level"`
|
||||
Maxlines int `json:"maxlines"`
|
||||
Daily bool `json:"daily"`
|
||||
Maxdays int `json:"maxdays"`
|
||||
Color bool `json:"color"`
|
||||
Rotate bool `json:"rotate"`
|
||||
}
|
||||
|
||||
func (c *FileLogConfig) String() string {
|
||||
b, _ := json.Marshal(c)
|
||||
return bytes.NewBuffer(b).String()
|
||||
}
|
||||
|
||||
type ConnLogConfig struct {
|
||||
ReconnectOnMsg bool `json:"reconnect_on_msg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level Level `json:"level"`
|
||||
}
|
||||
|
||||
func (c *ConnLogConfig) String() string {
|
||||
b, _ := json.Marshal(c)
|
||||
return bytes.NewBuffer(b).String()
|
||||
}
|
||||
|
||||
type EsConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Dsn string `json:"dsn"`
|
||||
Level Level `json:"level"`
|
||||
Index string `json:"index"`
|
||||
}
|
||||
|
||||
func (c *EsConfig) String() string {
|
||||
b, _ := json.Marshal(c)
|
||||
return bytes.NewBuffer(b).String()
|
||||
}
|
||||
|
||||
type AliLSConfig struct {
|
||||
Project string `json:"project"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
KeyID string `json:"key_id"`
|
||||
KeySecret string `json:"key_secret"`
|
||||
LogStore string `json:"log_store"`
|
||||
Topics []string `json:"topics"`
|
||||
Source string `json:"source"`
|
||||
Level Level `json:"level"`
|
||||
FlushWhen int `json:"flush_when"`
|
||||
}
|
||||
|
||||
func (c *AliLSConfig) String() string {
|
||||
b, _ := json.Marshal(c)
|
||||
return bytes.NewBuffer(b).String()
|
||||
}
|
77
function.go
77
function.go
@ -1,77 +0,0 @@
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Empty struct {}
|
||||
|
||||
//传入一个字符串,返回一个加密后的md5
|
||||
func Md5(str string) string {
|
||||
data := []byte(str)
|
||||
has := md5.Sum(data)
|
||||
md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
|
||||
return md5str
|
||||
}
|
||||
|
||||
func Md5File(filePath string) string {
|
||||
hash := md5.New()
|
||||
data, _ := ioutil.ReadFile(filePath)
|
||||
hash.Write([]byte(data))
|
||||
md5str := hex.EncodeToString(hash.Sum(nil))
|
||||
return md5str
|
||||
}
|
||||
|
||||
func GetPackageName() string {
|
||||
packpath := reflect.TypeOf(Empty{}).PkgPath()
|
||||
if idx := strings.Index(packpath, "/"); idx > 0 {
|
||||
return packpath[:idx]
|
||||
} else {
|
||||
return packpath
|
||||
}
|
||||
}
|
||||
|
||||
func GetCwd(filepath ...string) string {
|
||||
_, filename1, _, _ := runtime.Caller(0)
|
||||
filename2, _ := os.Getwd()
|
||||
if !strings.HasSuffix(filename2, GetPackageName()) {
|
||||
paths := append([]string{path.Dir(filename1), "/../../"}, filepath...)
|
||||
return path.Join(paths...)
|
||||
} else {
|
||||
paths := append([]string{filename2, "/"}, filepath...)
|
||||
return path.Join(paths...)
|
||||
}
|
||||
}
|
||||
|
||||
func SetCwd() error {
|
||||
_, filename1, _, _ := runtime.Caller(0)
|
||||
filename2, _ := os.Getwd()
|
||||
if !strings.HasSuffix(filename2, GetPackageName()) {
|
||||
paths := append([]string{path.Dir(filename1), "/../../"})
|
||||
return os.Chdir(path.Join(paths...))
|
||||
} else {
|
||||
paths := append([]string{filename2, "/"})
|
||||
return os.Chdir(path.Join(paths...))
|
||||
}
|
||||
}
|
||||
|
||||
func RandString(length int) string {
|
||||
str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
bytes := []byte(str)
|
||||
result := []byte{}
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < length; i++ {
|
||||
result = append(result, bytes[r.Intn(len(bytes))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
139
funtion.go
Normal file
139
funtion.go
Normal file
@ -0,0 +1,139 @@
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var adatperSetMapper = map[Adapter]func(l *Logger, level Level) error{
|
||||
|
||||
AdapterSocket: func(l *Logger, level Level) error {
|
||||
c := ConnLogConfig{
|
||||
ReconnectOnMsg: false,
|
||||
Reconnect: true,
|
||||
Net: "",
|
||||
Addr: "127.0.0.1:9888",
|
||||
Level: level,
|
||||
}
|
||||
return l.SetLogger(logs.AdapterConn, c.String())
|
||||
},
|
||||
|
||||
AdapterFile: func(l *Logger, level Level) error {
|
||||
if wd, err := os.Getwd(); err == nil {
|
||||
c := FileLogConfig{
|
||||
Filename: path.Join(wd, fmt.Sprintf("runtime/logs/%s.log", l.Lable)),
|
||||
Level: level,
|
||||
Maxlines: 0,
|
||||
Daily: true,
|
||||
Maxdays: 7,
|
||||
Color: true,
|
||||
Rotate: true,
|
||||
}
|
||||
|
||||
return l.SetLogger(logs.AdapterFile, c.String())
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
|
||||
AdapterConsole: func(l *Logger, level Level) error {
|
||||
c := ConsoleLogConfig{Level: level}
|
||||
return l.SetLogger(logs.AdapterConsole, c.String())
|
||||
},
|
||||
|
||||
AdapterElasticSearch: func(l *Logger, level Level) error {
|
||||
c := GenEsConfig(level)
|
||||
return l.SetLogger(logs.AdapterEs, c.String())
|
||||
},
|
||||
|
||||
AdapterAliLs: func(l *Logger, level Level) error {
|
||||
c := GenAliConfig(level)
|
||||
return l.SetLogger(logs.AdapterAliLS, c.String())
|
||||
},
|
||||
}
|
||||
|
||||
func GenAliConfig(level Level) *AliLSConfig {
|
||||
var project string = "gaore-app-logstore"
|
||||
var endpoint string
|
||||
|
||||
if os.Getenv(envkey) == "prod" || os.Getenv(envkey) == "" || os.Getenv(envkey) == "gray" {
|
||||
endpoint = project + ".cn-shenzhen-intranet.log.aliyuncs.com"
|
||||
} else if os.Getenv(envkey) == "dev" {
|
||||
endpoint = project + ".cn-shenzhen.log.aliyuncs.com"
|
||||
}
|
||||
|
||||
c := &AliLSConfig{
|
||||
Project: project,
|
||||
Endpoint: endpoint,
|
||||
KeyID: "LTAI4GCHwcqtrFD4DHRHxR4k",
|
||||
KeySecret: "Ln19xfVYy6OMlJeF9aBvFl4fhRUKBl",
|
||||
LogStore: "gaore-app-logstore",
|
||||
Topics: []string{os.Getenv("GRLOG_APP_NAME")},
|
||||
Source: "",
|
||||
Level: level,
|
||||
FlushWhen: 0,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func GenEsConfig(level Level) *EsConfig {
|
||||
dsn := "http://es-cn-tl32xlfmu00015h34.public.elasticsearch.aliyuncs.com:9200/"
|
||||
if os.Getenv(envkey) == "prod" || os.Getenv(envkey) == "" || os.Getenv(envkey) == "gray" {
|
||||
dsn = "http://es-cn-tl32xlfmu00015h34.elasticsearch.aliyuncs.com:9200/"
|
||||
}
|
||||
|
||||
c := &EsConfig{
|
||||
Username: "kaifa_api",
|
||||
Password: "2quYX3bTeahO",
|
||||
Dsn: dsn,
|
||||
Level: level,
|
||||
Index: os.Getenv("GRLOG_APP_NAME"),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
var adatperDropMapper = map[Adapter]func(l *Logger) error{
|
||||
|
||||
AdapterAliLs: func(l *Logger) error {
|
||||
return l.BeeLogger.DelLogger(logs.AdapterAliLS)
|
||||
},
|
||||
|
||||
AdapterFile: func(l *Logger) error {
|
||||
return l.BeeLogger.DelLogger(logs.AdapterFile)
|
||||
},
|
||||
|
||||
AdapterConsole: func(l *Logger) error {
|
||||
return l.BeeLogger.DelLogger(logs.AdapterConsole)
|
||||
},
|
||||
|
||||
AdapterElasticSearch: func(l *Logger) error {
|
||||
return l.BeeLogger.DelLogger(logs.AdapterEs)
|
||||
},
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
59
grlogs.go
Normal file
59
grlogs.go
Normal file
@ -0,0 +1,59 @@
|
||||
package grlogs
|
||||
|
||||
import "strings"
|
||||
|
||||
func Info(v ...interface{}) {
|
||||
Get("grlogs").Info(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Informational(v ...interface{}) {
|
||||
Get("grlogs").Informational(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Warning(v ...interface{}) {
|
||||
Get("grlogs").Warning(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Warn(v ...interface{}) {
|
||||
Get("grlogs").Warn(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Notice(v ...interface{}) {
|
||||
Get("grlogs").Notice(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Error(v ...interface{}) {
|
||||
Get("grlogs").Error(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Critical(v ...interface{}) {
|
||||
Get("grlogs").Critical(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Alert(v ...interface{}) {
|
||||
Get("grlogs").Alert(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Emergency(v ...interface{}) {
|
||||
Get("grlogs").Emergency(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Trace(v ...interface{}) {
|
||||
Get("grlogs").Trace(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
Get("grlogs").Debug(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func generateFmtStr(n int) string {
|
||||
return strings.Repeat("%v ", n)
|
||||
}
|
||||
|
||||
func SetAdapter(level Level, adapter Adapter) {
|
||||
Get("grlogs").SetAdapter(level, adapter)
|
||||
}
|
||||
|
||||
func DropAdapter(adapter Adapter) {
|
||||
Get("grlogs").DropAdapter(adapter)
|
||||
}
|
212
log.go
212
log.go
@ -1,101 +1,145 @@
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"golib.gaore.com/GaoreGo/grlogs/logs"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
LEVEL_NONE = iota
|
||||
LEVEL_CRITICAL = iota
|
||||
LEVEL_ERROR = iota
|
||||
LEVEL_WARNING = iota
|
||||
LEVEL_WARN = iota
|
||||
LEVEL_INFO = iota
|
||||
LEVEL_DEBUG = iota
|
||||
LEVEL_ALL = iota
|
||||
)
|
||||
var loggers = sync.Map{}
|
||||
|
||||
type ConsoleLogConfig struct {
|
||||
Level int `json:"level"`
|
||||
var envkey = "CENTER_RUNMODE"
|
||||
|
||||
var defaultModeMapping = map[string][]AdapterTupple{
|
||||
"es": []AdapterTupple{{LevelAll, AdapterConsole}, {LevelAll, AdapterElasticSearch}},
|
||||
"ali": []AdapterTupple{{LevelAll, AdapterConsole}, {LevelAll, AdapterAliLs}},
|
||||
"": []AdapterTupple{{LevelAll, AdapterConsole}, {LevelAll, AdapterFile}},
|
||||
}
|
||||
|
||||
type FileLogConfig struct {
|
||||
Filename string `json:"filename"`
|
||||
Level int `json:"level"`
|
||||
Maxlines int `json:"maxlines"`
|
||||
Daily bool `json:"daily"`
|
||||
Maxdays int `json:"maxdays"`
|
||||
Color bool `json:"color"`
|
||||
Rotate bool `json:"rotate"`
|
||||
}
|
||||
|
||||
type ConnLogConfig struct {
|
||||
ReconnectOnMsg bool `json:"reconnect_on_msg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
var loggers = make(map[string]*Logger)
|
||||
|
||||
type Logger struct {
|
||||
Lable string
|
||||
*logs.BeeLogger
|
||||
}
|
||||
|
||||
func (l *Logger) Write(p []byte) (n int, err error) {
|
||||
l.Debug(bytes.NewBuffer(p).String())
|
||||
return
|
||||
}
|
||||
type Getter func() *Logger
|
||||
|
||||
func (l *Logger) SetFormat() {
|
||||
|
||||
}
|
||||
|
||||
func (l *Logger) Stop() {
|
||||
l.BeeLogger.Close()
|
||||
}
|
||||
|
||||
func GetLogger(name string) *Logger {
|
||||
|
||||
if l, ok := loggers[name]; ok {
|
||||
return l
|
||||
} else {
|
||||
|
||||
var level int = LEVEL_WARN
|
||||
|
||||
if beego.BConfig.RunMode == "dev" {
|
||||
level = LEVEL_ALL
|
||||
func (self *Logger) SetAdapter(level Level, adapter Adapter) *Logger {
|
||||
if call, ok := adatperSetMapper[adapter]; ok {
|
||||
if err := call(self, level); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
conf1 := FileLogConfig{
|
||||
Filename: GetCwd(fmt.Sprintf("runtime/logs/%s.log", name)),
|
||||
Level: LEVEL_ALL,
|
||||
Maxlines: 0,
|
||||
Daily: true,
|
||||
Maxdays: 7,
|
||||
Color: true,
|
||||
Rotate: true,
|
||||
}
|
||||
|
||||
conf2 := ConsoleLogConfig{
|
||||
Level: level,
|
||||
}
|
||||
|
||||
confString, _ := json.Marshal(&conf1)
|
||||
confString2, _ := json.Marshal(&conf2)
|
||||
loggers[name] = &Logger{}
|
||||
loggers[name].BeeLogger = logs.NewLogger()
|
||||
loggers[name].SetLogger(logs.AdapterFile, bytes.NewBuffer(confString).String())
|
||||
loggers[name].SetLogger(logs.AdapterConsole, bytes.NewBuffer(confString2).String())
|
||||
loggers[name].BeeLogger.SetPrefix("_" + name)
|
||||
loggers[name].BeeLogger.EnableFuncCallDepth(true)
|
||||
loggers[name].BeeLogger.SetLogFuncCallDepth(2)
|
||||
return loggers[name]
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *Logger) DropAdapter(adapter Adapter) *Logger {
|
||||
if call, ok := adatperDropMapper[adapter]; ok {
|
||||
if err := call(self); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func newLoggerFromMap(label string, defaultmode string, channelLens ...int64) Getter {
|
||||
|
||||
if tmp, ok := loggers.Load(label); ok {
|
||||
return tmp.(Getter)
|
||||
}
|
||||
|
||||
var l *Logger
|
||||
var once sync.Once
|
||||
wapperGetter := Getter(func() *Logger {
|
||||
once.Do(func() {
|
||||
|
||||
var channelLensNum int64 = 100
|
||||
if len(channelLens) > 0 && channelLens[0] > 0 {
|
||||
channelLensNum = channelLens[0]
|
||||
}
|
||||
|
||||
l = &Logger{BeeLogger: logs.NewLogger(channelLensNum)}
|
||||
l.Lable = label
|
||||
l.BeeLogger = logs.NewLogger(channelLensNum)
|
||||
l.BeeLogger.Lable = label
|
||||
l.Env = os.Getenv(envkey)
|
||||
l.SetPrefix(fmt.Sprintf("[env:%s logger:%s]", os.Getenv(envkey), label))
|
||||
l.EnableFuncCallDepth(true)
|
||||
l.SetLogFuncCallDepth(2)
|
||||
if mode, ok := defaultModeMapping[defaultmode]; ok {
|
||||
for _, v := range mode {
|
||||
l.SetAdapter(v.Level, v.Adapter)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return l
|
||||
})
|
||||
|
||||
tmp, loaded := loggers.LoadOrStore(label, wapperGetter)
|
||||
if loaded {
|
||||
return tmp.(Getter)
|
||||
}
|
||||
|
||||
return wapperGetter
|
||||
}
|
||||
|
||||
func Get(label string, channelLens ...int64) *Logger {
|
||||
getter := newLoggerFromMap(label, "", channelLens...)
|
||||
return getter()
|
||||
}
|
||||
|
||||
func GetEs(label string, channelLens ...int64) *Logger {
|
||||
getter := newLoggerFromMap(label, "es", channelLens...)
|
||||
return getter()
|
||||
}
|
||||
|
||||
func GetAli(label string, channelLens ...int64) *Logger {
|
||||
getter := newLoggerFromMap(label, "ali", channelLens...)
|
||||
return getter()
|
||||
}
|
||||
|
||||
func Close(lables ...string) {
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, lable := range lables {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if v, ok := loggers.Load(lable); ok {
|
||||
if tmp, ok := v.(*Logger); ok {
|
||||
loggers.Delete(lable)
|
||||
tmp.BeeLogger.Close()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
}
|
||||
|
||||
func CloseAll() {
|
||||
wg := &sync.WaitGroup{}
|
||||
loggers.Range(func(key, value interface{}) bool {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
if tmp, ok := value.(*Logger); ok {
|
||||
tmp.BeeLogger.Close()
|
||||
loggers.Delete(key)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func init() {
|
||||
var level Level = LevelInfo
|
||||
if os.Getenv(envkey) == "dev" {
|
||||
level = LevelAll
|
||||
}
|
||||
|
||||
defaultModeMapping["es"] = []AdapterTupple{{level, AdapterConsole}, {level, AdapterElasticSearch}}
|
||||
defaultModeMapping["ali"] = []AdapterTupple{{level, AdapterConsole}, {level, AdapterAliLs}}
|
||||
defaultModeMapping[""] = []AdapterTupple{{level, AdapterConsole}, {level, AdapterFile}}
|
||||
}
|
||||
|
94
log_test.go
94
log_test.go
@ -1,12 +1,98 @@
|
||||
package grlogs
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
"errors"
|
||||
_ "golib.gaore.com/GaoreGo/grlogs/logs/alils"
|
||||
_ "golib.gaore.com/GaoreGo/grlogs/logs/es"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetLogger(t *testing.T) {
|
||||
logs.NewLogger()
|
||||
logs.Info(LEVEL_ALL)
|
||||
GetLogger("nds").Debug("akldalskflasfa")
|
||||
// 新建 channel 大小为128 标识为nds 日志通道 , Get 的方法 默认带 console 和 file 输出
|
||||
l := Get("nds", 128).SetAdapter(LevelAll, AdapterElasticSearch)
|
||||
l.Debug("我正在调试")
|
||||
l.Critical("出错了")
|
||||
|
||||
// 复用 nds 的日志通道
|
||||
Get("nds").Warning("hadoee %s", time.Now().Format(time.RFC1123))
|
||||
Get("nds").Warning("hadoee %s", time.Now().Format(time.RFC1123))
|
||||
|
||||
// 新建 channel 大小为默认 标识为wifi 日志通道 , GetEs 的方法 默认带 console 和 file 和 elatisearch 输出
|
||||
GetEs("wifi")
|
||||
for i := 0; i < 10; i++ {
|
||||
Get("wifi").Warning("Warning")
|
||||
Get("wifi").Warn("Warn")
|
||||
Get("wifi").Debug("Debug")
|
||||
Get("wifi").Error("Error")
|
||||
Get("wifi").Notice("Notice")
|
||||
Get("wifi").Info("Info")
|
||||
Get("wifi").Alert("Alert")
|
||||
}
|
||||
|
||||
Get("wifi").Critical("neoweiwoewe")
|
||||
}
|
||||
|
||||
func TestGetAliLs(t *testing.T) {
|
||||
|
||||
lable := "test_alils"
|
||||
l := GetAli(lable).Async(128)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
l.Info("Info")
|
||||
l.Debug("Debug")
|
||||
l.Warn("Warn")
|
||||
l.Warning("Warning")
|
||||
l.Error("Error")
|
||||
l.Error("Error\n\n 测试换行")
|
||||
l.Warn("Warn")
|
||||
}
|
||||
|
||||
time.Sleep(time.Hour * 1)
|
||||
}
|
||||
|
||||
func TestDropAdapter(t *testing.T) {
|
||||
SetAdapter(LevelAll, AdapterAliLs)
|
||||
DropAdapter(AdapterAliLs)
|
||||
Informational(errors.New("he hello"))
|
||||
SetAdapter(LevelAll, AdapterAliLs)
|
||||
Debug(errors.New("he hello"))
|
||||
CloseAll()
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
GetAli("ok").Debug("%d", i)
|
||||
time.Sleep(time.Second * 10)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
GetAli("ok").Debug("aaaaaa%d", i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestGetEs(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
GetEs("ok").Debug("%d", i)
|
||||
time.Sleep(time.Second * 10)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
GetEs("ok").Debug("aaaaaa%d", i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
72
logs/README.md
Normal file
72
logs/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
## logs
|
||||
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||
|
||||
|
||||
## How to install?
|
||||
|
||||
go get github.com/astaxie/beego/logs
|
||||
|
||||
|
||||
## What adapters are supported?
|
||||
|
||||
As of now this logs support console, file,smtp and conn.
|
||||
|
||||
|
||||
## How to use it?
|
||||
|
||||
First you must import it
|
||||
|
||||
```golang
|
||||
import (
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
```
|
||||
|
||||
Then init a Log (example with console adapter)
|
||||
|
||||
```golang
|
||||
log := logs.NewLogger(10000)
|
||||
log.SetLogger("console", "")
|
||||
```
|
||||
|
||||
> the first params stand for how many channel
|
||||
|
||||
Use it like this:
|
||||
|
||||
```golang
|
||||
log.Trace("trace")
|
||||
log.Info("info")
|
||||
log.Warn("warning")
|
||||
log.Debug("debug")
|
||||
log.Critical("critical")
|
||||
```
|
||||
|
||||
## File adapter
|
||||
|
||||
Configure file adapter like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
```
|
||||
|
||||
## Conn adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Info("info")
|
||||
```
|
||||
|
||||
## Smtp adapter
|
||||
|
||||
Configure like this:
|
||||
|
||||
```golang
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
||||
```
|
83
logs/accesslog.go
Normal file
83
logs/accesslog.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||
apacheFormat = "APACHE_FORMAT"
|
||||
jsonFormat = "JSON_FORMAT"
|
||||
)
|
||||
|
||||
// AccessLogRecord struct for holding access log data.
|
||||
type AccessLogRecord struct {
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
RequestTime time.Time `json:"request_time"`
|
||||
RequestMethod string `json:"request_method"`
|
||||
Request string `json:"request"`
|
||||
ServerProtocol string `json:"server_protocol"`
|
||||
Host string `json:"host"`
|
||||
Status int `json:"status"`
|
||||
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||
HTTPReferrer string `json:"http_referrer"`
|
||||
HTTPUserAgent string `json:"http_user_agent"`
|
||||
RemoteUser string `json:"remote_user"`
|
||||
}
|
||||
|
||||
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
disableEscapeHTML(encoder)
|
||||
|
||||
err := encoder.Encode(r)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
|
||||
func disableEscapeHTML(i interface{}) {
|
||||
if e, ok := i.(interface {
|
||||
SetEscapeHTML(bool)
|
||||
}); ok {
|
||||
e.SetEscapeHTML(false)
|
||||
}
|
||||
}
|
||||
|
||||
// AccessLog - Format and print access log.
|
||||
func AccessLog(r *AccessLogRecord, format string) {
|
||||
var msg string
|
||||
switch format {
|
||||
case apacheFormat:
|
||||
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||
case jsonFormat:
|
||||
fallthrough
|
||||
default:
|
||||
jsonData, err := r.json()
|
||||
if err != nil {
|
||||
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||
} else {
|
||||
msg = string(jsonData)
|
||||
}
|
||||
}
|
||||
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
|
||||
}
|
98
logs/alils/alils.go
Normal file
98
logs/alils/alils.go
Normal file
@ -0,0 +1,98 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/aliyun/aliyun-log-go-sdk/producer"
|
||||
"golib.gaore.com/GaoreGo/grlogs/logs"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewAliLS() logs.Logger {
|
||||
cw := &alilsLogger{
|
||||
Level: logs.LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
type alilsLogger struct {
|
||||
producer *producer.Producer
|
||||
callback *Callback
|
||||
Project string `json:"project"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
KeyID string `json:"key_id"`
|
||||
KeySecret string `json:"key_secret"`
|
||||
LogStore string `json:"log_store"`
|
||||
Topics []string `json:"topics"`
|
||||
Source string `json:"source"`
|
||||
Level int `json:"level"`
|
||||
FlushWhen int `json:"flush_when"`
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func (a *alilsLogger) Init(jsonconfig string) error {
|
||||
|
||||
err := json.Unmarshal([]byte(jsonconfig), a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
producerConfig := producer.GetDefaultProducerConfig()
|
||||
producerConfig.Endpoint = a.Endpoint
|
||||
producerConfig.AccessKeyID = a.KeyID
|
||||
producerConfig.AccessKeySecret = a.KeySecret
|
||||
producerConfig.LingerMs = 100
|
||||
producerConfig.NoRetryStatusCodeList = []int{-1}
|
||||
producerConfig.Retries = 2
|
||||
producerConfig.AllowLogLevel = "error"
|
||||
producerConfig.MaxIoWorkerCount = int64(runtime.NumCPU())
|
||||
a.producer = producer.InitProducer(producerConfig)
|
||||
a.callback = &Callback{}
|
||||
a.producer.Start()
|
||||
a.Debug = os.Getenv("GRLOG_ALILS_DEBUG") == "on"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *alilsLogger) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
|
||||
vals := map[string]string{"msg": msg, "level": fmt.Sprintf("%d", level)}
|
||||
vals["level_string"] = logs.GetLevelString(level)
|
||||
vals["env"] = env
|
||||
vals["lable"] = lable
|
||||
vals["hostname"] = GetHostname()
|
||||
vals["working_idr"] = Getwd()
|
||||
vals["home_dir"] = GetUserHomename()
|
||||
vals["hardware_addr"] = GetCurrentInterfaceHardwareAddr()
|
||||
vals["client_addrs"] = GetCurrentInterfaceAddrs()
|
||||
|
||||
log := producer.GenerateLog(uint32(when.Unix()), vals)
|
||||
|
||||
if a.Debug {
|
||||
for _, topic := range a.Topics {
|
||||
if err := a.producer.SendLogWithCallBack(a.Project, a.LogStore, topic, a.Source, log, a.callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, topic := range a.Topics {
|
||||
if err := a.producer.SendLog(a.Project, a.LogStore, topic, a.Source, log); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *alilsLogger) Destroy() {
|
||||
a.producer.SafeClose()
|
||||
a.producer.Close(300)
|
||||
}
|
||||
|
||||
func (a *alilsLogger) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register(logs.AdapterAliLS, NewAliLS)
|
||||
}
|
25
logs/alils/callback.go
Normal file
25
logs/alils/callback.go
Normal file
@ -0,0 +1,25 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/aliyun/aliyun-log-go-sdk/producer"
|
||||
)
|
||||
|
||||
type Callback struct {
|
||||
}
|
||||
|
||||
func (callback *Callback) Success(result *producer.Result) {
|
||||
attemptList := result.GetReservedAttempts()
|
||||
for _, attempt := range attemptList {
|
||||
fmt.Printf("alilog %+v \n", attempt)
|
||||
}
|
||||
}
|
||||
|
||||
func (callback *Callback) Fail(result *producer.Result) {
|
||||
fmt.Println("IsSuccessful", result.IsSuccessful())
|
||||
fmt.Println("GetErrorCode", result.GetErrorCode())
|
||||
fmt.Println("GetErrorMessage", result.GetErrorMessage())
|
||||
fmt.Println("GetReservedAttempts", result.GetReservedAttempts())
|
||||
fmt.Println("GetRequestId", result.GetRequestId())
|
||||
fmt.Println("GetTimeStampMs", result.GetTimeStampMs())
|
||||
}
|
47
logs/alils/sys.go
Normal file
47
logs/alils/sys.go
Normal file
@ -0,0 +1,47 @@
|
||||
package alils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetCurrentInterface() *net.Interface {
|
||||
if inter, err := net.InterfaceByName("eth0"); err == nil {
|
||||
return inter
|
||||
} else if inter, err := net.InterfaceByName("en0"); err == nil {
|
||||
return inter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCurrentInterfaceHardwareAddr() string {
|
||||
if inter := GetCurrentInterface(); inter != nil {
|
||||
return fmt.Sprintf("%s", inter.HardwareAddr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetCurrentInterfaceAddrs() string {
|
||||
if inter := GetCurrentInterface(); inter != nil {
|
||||
if addrs, err := inter.Addrs(); err == nil {
|
||||
return fmt.Sprintf("%s", addrs)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetHostname() string {
|
||||
hostname, _ := os.Hostname()
|
||||
return hostname
|
||||
}
|
||||
|
||||
func GetUserHomename() string {
|
||||
homedir, _ := os.UserHomeDir()
|
||||
return homedir
|
||||
}
|
||||
|
||||
func Getwd() string {
|
||||
wd, _ := os.Getwd()
|
||||
return wd
|
||||
}
|
117
logs/conn.go
Normal file
117
logs/conn.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// connWriter implements LoggerInterface.
|
||||
// it writes messages in keep-live tcp connection.
|
||||
type connWriter struct {
|
||||
lg *logWriter
|
||||
innerWriter io.WriteCloser
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
Net string `json:"net"`
|
||||
Addr string `json:"addr"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// NewConn create new ConnWrite returning as LoggerInterface.
|
||||
func NewConn() Logger {
|
||||
conn := new(connWriter)
|
||||
conn.Level = LevelTrace
|
||||
return conn
|
||||
}
|
||||
|
||||
// Init init connection writer with json config.
|
||||
// json config only need key "level".
|
||||
func (c *connWriter) Init(jsonConfig string) error {
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// WriteMsg write message in connection.
|
||||
// if connection is down, try to re-connect.
|
||||
func (c *connWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.needToConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
|
||||
c.lg.writeln(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *connWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
// Destroy destroy connection writer and close tcp listener.
|
||||
func (c *connWriter) Destroy() {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connWriter) connect() error {
|
||||
if c.innerWriter != nil {
|
||||
c.innerWriter.Close()
|
||||
c.innerWriter = nil
|
||||
}
|
||||
|
||||
conn, err := net.Dial(c.Net, c.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = newLogWriter(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connWriter) needToConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
c.Reconnect = false
|
||||
return true
|
||||
}
|
||||
|
||||
if c.innerWriter == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.ReconnectOnMsg
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConn, NewConn)
|
||||
}
|
25
logs/conn_test.go
Normal file
25
logs/conn_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
log := NewLogger(1000)
|
||||
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||
log.Informational("informational")
|
||||
}
|
99
logs/console.go
Normal file
99
logs/console.go
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/shiena/ansicolor"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
type brush func(string) string
|
||||
|
||||
// newBrush return a fix color Brush
|
||||
func newBrush(color string) brush {
|
||||
pre := "\033["
|
||||
reset := "\033[0m"
|
||||
return func(text string) string {
|
||||
return pre + color + "m" + text + reset
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []brush{
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;44"), // Debug Background blue
|
||||
}
|
||||
|
||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type consoleWriter struct {
|
||||
lg *logWriter
|
||||
Level int `json:"level"`
|
||||
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
|
||||
}
|
||||
|
||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||
Level: LevelDebug,
|
||||
Colorful: true,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init init console logger.
|
||||
// jsonConfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||
if len(jsonConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// WriteMsg write message in console.
|
||||
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.Colorful {
|
||||
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
|
||||
}
|
||||
c.lg.writeln(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (c *consoleWriter) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (c *consoleWriter) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterConsole, NewConsole)
|
||||
}
|
51
logs/console_test.go
Normal file
51
logs/console_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Try each log level in decreasing order of priority.
|
||||
func testConsoleCalls(bl *BeeLogger) {
|
||||
bl.Emergency("emergency")
|
||||
bl.Alert("alert")
|
||||
bl.Critical("critical")
|
||||
bl.Error("error")
|
||||
bl.Warning("warning")
|
||||
bl.Notice("notice")
|
||||
bl.Informational("informational")
|
||||
bl.Debug("debug")
|
||||
}
|
||||
|
||||
// Test console logging by visually comparing the lines being output with and
|
||||
// without a log level specification.
|
||||
func TestConsole(t *testing.T) {
|
||||
log1 := NewLogger(10000)
|
||||
log1.EnableFuncCallDepth(true)
|
||||
log1.SetLogger("console", "")
|
||||
testConsoleCalls(log1)
|
||||
|
||||
log2 := NewLogger(100)
|
||||
log2.SetLogger("console", `{"level":3}`)
|
||||
testConsoleCalls(log2)
|
||||
}
|
||||
|
||||
// Test console without color
|
||||
func TestConsoleNoColor(t *testing.T) {
|
||||
log := NewLogger(100)
|
||||
log.SetLogger("console", `{"color":false}`)
|
||||
testConsoleCalls(log)
|
||||
}
|
111
logs/es/es.go
Normal file
111
logs/es/es.go
Normal file
@ -0,0 +1,111 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golib.gaore.com/GaoreGo/goes"
|
||||
"golib.gaore.com/GaoreGo/grlogs/logs"
|
||||
)
|
||||
|
||||
// NewES return a LoggerInterface
|
||||
func NewES() logs.Logger {
|
||||
cw := &esLogger{
|
||||
Level: logs.LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
type esLogger struct {
|
||||
*goes.Client
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DSN string `json:"dsn"`
|
||||
Level int `json:"level"`
|
||||
IndexName string `json:"index"`
|
||||
}
|
||||
|
||||
// {"dsn":"http://localhost:9200/","level":1}
|
||||
func (el *esLogger) Init(jsonconfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonconfig), el)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if el.DSN == "" {
|
||||
return errors.New("empty dsn")
|
||||
} else if u, err := url.Parse(el.DSN); err != nil {
|
||||
return err
|
||||
} else if u.Path == "" {
|
||||
return errors.New("missing prefix")
|
||||
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
|
||||
return err
|
||||
} else {
|
||||
|
||||
tr := &http.Transport{
|
||||
ResponseHeaderTimeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
cl := &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
conn := goes.NewClient(host, port)
|
||||
conn.AuthUsername = el.Username
|
||||
conn.AuthPassword = el.Password
|
||||
conn.WithHTTPClient(cl)
|
||||
el.Client = conn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteMsg will write the msg and level into es
|
||||
func (el *esLogger) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
|
||||
if level > el.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
vals := make(map[string]interface{})
|
||||
vals["@timestamp"] = when.Format(time.RFC3339)
|
||||
vals["@msg"] = msg
|
||||
vals["@level"] = level
|
||||
vals["level_string"] = logs.GetLevelString(level)
|
||||
vals["env"] = env
|
||||
vals["lable"] = lable
|
||||
vals["hostname"] = GetHostname()
|
||||
vals["working_idr"] = Getwd()
|
||||
vals["home_dir"] = GetUserHomename()
|
||||
vals["hardware_addr"] = GetCurrentInterfaceHardwareAddr()
|
||||
vals["client_addrs"] = GetCurrentInterfaceAddrs()
|
||||
|
||||
if el.IndexName == "" {
|
||||
return errors.New("index name is empty")
|
||||
}
|
||||
|
||||
d := goes.Document{
|
||||
Index: fmt.Sprintf("%s-%04d.%02d.%02d", el.IndexName, when.Year(), when.Month(), when.Day()),
|
||||
Type: "_doc",
|
||||
Fields: vals,
|
||||
}
|
||||
_, err := el.Index(d, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// Destroy is a empty method
|
||||
func (el *esLogger) Destroy() {
|
||||
|
||||
}
|
||||
|
||||
// Flush is a empty method
|
||||
func (el *esLogger) Flush() {
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register(logs.AdapterEs, NewES)
|
||||
}
|
47
logs/es/sys.go
Normal file
47
logs/es/sys.go
Normal file
@ -0,0 +1,47 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetCurrentInterface() *net.Interface {
|
||||
if inter, err := net.InterfaceByName("eth0"); err == nil {
|
||||
return inter
|
||||
} else if inter, err := net.InterfaceByName("en0"); err == nil {
|
||||
return inter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCurrentInterfaceHardwareAddr() string {
|
||||
if inter := GetCurrentInterface(); inter != nil {
|
||||
return fmt.Sprintf("%s", inter.HardwareAddr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetCurrentInterfaceAddrs() string {
|
||||
if inter := GetCurrentInterface(); inter != nil {
|
||||
if addrs, err := inter.Addrs(); err == nil {
|
||||
return fmt.Sprintf("%s", addrs)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetHostname() string {
|
||||
hostname, _ := os.Hostname()
|
||||
return hostname
|
||||
}
|
||||
|
||||
func GetUserHomename() string {
|
||||
homedir, _ := os.UserHomeDir()
|
||||
return homedir
|
||||
}
|
||||
|
||||
func Getwd() string {
|
||||
wd, _ := os.Getwd()
|
||||
return wd
|
||||
}
|
405
logs/file.go
Normal file
405
logs/file.go
Normal file
@ -0,0 +1,405 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type fileLogWriter struct {
|
||||
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
fileWriter *os.File
|
||||
|
||||
// Rotate at line
|
||||
MaxLines int `json:"maxlines"`
|
||||
maxLinesCurLines int
|
||||
|
||||
MaxFiles int `json:"maxfiles"`
|
||||
MaxFilesCurFiles int
|
||||
|
||||
// Rotate at size
|
||||
MaxSize int `json:"maxsize"`
|
||||
maxSizeCurSize int
|
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"`
|
||||
MaxDays int64 `json:"maxdays"`
|
||||
dailyOpenDate int
|
||||
dailyOpenTime time.Time
|
||||
|
||||
// Rotate hourly
|
||||
Hourly bool `json:"hourly"`
|
||||
MaxHours int64 `json:"maxhours"`
|
||||
hourlyOpenDate int
|
||||
hourlyOpenTime time.Time
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
Level int `json:"level"`
|
||||
|
||||
Perm string `json:"perm"`
|
||||
|
||||
RotatePerm string `json:"rotateperm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
}
|
||||
|
||||
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Hourly: false,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
RotatePerm: "0440",
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
MaxLines: 10000000,
|
||||
MaxFiles: 999,
|
||||
MaxSize: 1 << 28,
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":"0600"
|
||||
// }
|
||||
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
w.suffix = filepath.Ext(w.Filename)
|
||||
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||
if w.suffix == "" {
|
||||
w.suffix = ".log"
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *fileLogWriter) startLogger() error {
|
||||
file, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.fileWriter != nil {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
w.fileWriter = file
|
||||
return w.initFd()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Daily && day != w.dailyOpenDate)
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
|
||||
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||
(w.Hourly && hour != w.hourlyOpenDate)
|
||||
|
||||
}
|
||||
|
||||
// WriteMsg write logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
hd, d, h := formatTimeHeader(when)
|
||||
msg = string(hd) + msg + "\n"
|
||||
if w.Rotate {
|
||||
w.RLock()
|
||||
if w.needRotateHourly(len(msg), h) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateHourly(len(msg), h) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else if w.needRotateDaily(len(msg), d) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotateDaily(len(msg), d) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else {
|
||||
w.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
_, err := w.fileWriter.Write([]byte(msg))
|
||||
if err == nil {
|
||||
w.maxLinesCurLines++
|
||||
w.maxSizeCurSize += len(msg)
|
||||
}
|
||||
w.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filepath := path.Dir(w.Filename)
|
||||
os.MkdirAll(filepath, os.FileMode(perm))
|
||||
|
||||
fd, err := os.OpenFile(w.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(w.Filename, os.FileMode(perm))
|
||||
}
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) initFd() error {
|
||||
fd := w.fileWriter
|
||||
fInfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s", err)
|
||||
}
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenTime = time.Now()
|
||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||
w.hourlyOpenTime = time.Now()
|
||||
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||
w.maxLinesCurLines = 0
|
||||
if w.Hourly {
|
||||
go w.hourlyRotate(w.hourlyOpenTime)
|
||||
} else if w.Daily {
|
||||
go w.dailyRotate(w.dailyOpenTime)
|
||||
}
|
||||
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.maxLinesCurLines = count
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateDaily(0, time.Now().Day()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||
<-tm.C
|
||||
w.Lock()
|
||||
if w.needRotateHourly(0, time.Now().Hour()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.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
|
||||
}
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
// file exists
|
||||
// Find the next available number
|
||||
num := w.MaxFilesCurFiles + 1
|
||||
fName := ""
|
||||
format := ""
|
||||
var openTime time.Time
|
||||
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
//even if the file is not exist or other ,we should RESTART the logger
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
if w.Hourly {
|
||||
format = "2006010215"
|
||||
openTime = w.hourlyOpenTime
|
||||
} else if w.Daily {
|
||||
format = "2006-01-02"
|
||||
openTime = w.dailyOpenTime
|
||||
}
|
||||
|
||||
// only when one of them be setted, then the file would be splited
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
w.MaxFilesCurFiles = num
|
||||
}
|
||||
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||
}
|
||||
|
||||
// close fileWriter before rename
|
||||
w.fileWriter.Close()
|
||||
|
||||
// Rename the file to its new found name
|
||||
// even if occurs error,we MUST guarantee to restart new logger
|
||||
err = os.Rename(w.Filename, fName)
|
||||
if err != nil {
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||
|
||||
RESTART_LOGGER:
|
||||
|
||||
startLoggerErr := w.startLogger()
|
||||
go w.deleteOldLog()
|
||||
|
||||
if startLoggerErr != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) deleteOldLog() {
|
||||
dir := filepath.Dir(w.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 w.Hourly {
|
||||
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
} else if w.Daily {
|
||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Destroy close the file description, close file writer.
|
||||
func (w *fileLogWriter) Destroy() {
|
||||
w.fileWriter.Close()
|
||||
}
|
||||
|
||||
// Flush flush file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *fileLogWriter) Flush() {
|
||||
w.fileWriter.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterFile, newFileWriter)
|
||||
}
|
420
logs/file_test.go
Normal file
420
logs/file_test.go
Normal file
@ -0,0 +1,420 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFilePerm(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
// use 0666 as test perm cause the default umask is 022
|
||||
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
file, err := os.Stat("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file.Mode() != 0666 {
|
||||
t.Fatal("unexpected log file permission")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFile1(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
f, err := os.Open("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
var expected = LevelDebug + 1
|
||||
if lineNum != expected {
|
||||
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFile2(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", fmt.Sprintf(`{"filename":"test2.log","level":%d}`, LevelError))
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
f, err := os.Open("test2.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
var expected = LevelError + 1
|
||||
if lineNum != expected {
|
||||
t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
os.Remove("test2.log")
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||
b, err := exists(rotateName)
|
||||
if !b || err != nil {
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate not generated")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, true, false)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileDailyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
func TestFileDailyRotate_06(t *testing.T) { //test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
|
||||
s, _ := os.Lstat(rotateName)
|
||||
if s.Mode() != 0440 {
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate file mode error")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
b, err := exists(rotateName)
|
||||
if !b || err != nil {
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate not generated")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2, false, true)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_hour.log"
|
||||
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
|
||||
testFileHourlyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
log.Info("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
|
||||
s, _ := os.Lstat(rotateName)
|
||||
if s.Mode() != 0440 {
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
t.Fatal("rotate file mode error")
|
||||
}
|
||||
os.Remove(rotateName)
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: daily,
|
||||
MaxDays: 7,
|
||||
Hourly: hourly,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
|
||||
if daily {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
}
|
||||
|
||||
if hourly {
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
|
||||
}
|
||||
|
||||
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug, "", "dev")
|
||||
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
|
||||
today = today.Add(-1 * time.Second)
|
||||
fw.dailyRotate(today)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Hourly: true,
|
||||
MaxHours: 168,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
RotatePerm: "0440",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
|
||||
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
|
||||
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
|
||||
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
|
||||
hour = hour.Add(-1 * time.Second)
|
||||
fw.hourlyRotate(hour)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func BenchmarkFile(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileAsynchronous(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.Async()
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileCallDepth(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.EnableFuncCallDepth(true)
|
||||
log.SetLogFuncCallDepth(2)
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileAsynchronousCallDepth(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
log.EnableFuncCallDepth(true)
|
||||
log.SetLogFuncCallDepth(2)
|
||||
log.Async()
|
||||
for i := 0; i < b.N; i++ {
|
||||
log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
||||
|
||||
func BenchmarkFileOnGoroutine(b *testing.B) {
|
||||
log := NewLogger(100000)
|
||||
log.SetLogger("file", `{"filename":"test4.log"}`)
|
||||
for i := 0; i < b.N; i++ {
|
||||
go log.Debug("debug")
|
||||
}
|
||||
os.Remove("test4.log")
|
||||
}
|
72
logs/jianliao.go
Normal file
72
logs/jianliao.go
Normal file
@ -0,0 +1,72 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||
type JLWriter struct {
|
||||
AuthorName string `json:"authorname"`
|
||||
Title string `json:"title"`
|
||||
WebhookURL string `json:"webhookurl"`
|
||||
RedirectURL string `json:"redirecturl,omitempty"`
|
||||
ImageURL string `json:"imageurl,omitempty"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// newJLWriter create jiaoliao writer.
|
||||
func newJLWriter() Logger {
|
||||
return &JLWriter{Level: LevelTrace}
|
||||
}
|
||||
|
||||
// Init JLWriter with json config string
|
||||
func (s *JLWriter) Init(jsonconfig string) error {
|
||||
return json.Unmarshal([]byte(jsonconfig), s)
|
||||
}
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("authorName", s.AuthorName)
|
||||
form.Add("title", s.Title)
|
||||
form.Add("text", text)
|
||||
if s.RedirectURL != "" {
|
||||
form.Add("redirectUrl", s.RedirectURL)
|
||||
}
|
||||
if s.ImageURL != "" {
|
||||
form.Add("imageUrl", s.ImageURL)
|
||||
}
|
||||
|
||||
resp, err := http.PostForm(s.WebhookURL, form)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *JLWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *JLWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterJianLiao, newJLWriter)
|
||||
}
|
671
logs/log.go
Normal file
671
logs/log.go
Normal file
@ -0,0 +1,671 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package logs provide a general log interface
|
||||
// Usage:
|
||||
//
|
||||
// import "golib.gaore.com/GaoreGo/grlogs/logs"
|
||||
//
|
||||
// log := NewLogger(10000)
|
||||
// log.SetLogger("console", "")
|
||||
//
|
||||
// > the first params stand for how many channel
|
||||
//
|
||||
// Use it like this:
|
||||
//
|
||||
// log.Trace("trace")
|
||||
// log.Info("info")
|
||||
// log.Warn("warning")
|
||||
// log.Debug("debug")
|
||||
// log.Critical("critical")
|
||||
//
|
||||
// more docs http://beego.me/docs/module/logs.md
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
const (
|
||||
LevelEmergency = iota
|
||||
LevelAlert
|
||||
LevelCritical
|
||||
LevelError
|
||||
LevelWarning
|
||||
LevelNotice
|
||||
LevelInformational
|
||||
LevelDebug
|
||||
)
|
||||
|
||||
// levelLogLogger is defined to implement log.Logger
|
||||
// the real log level will be LevelEmergency
|
||||
const levelLoggerImpl = -1
|
||||
|
||||
// Name for adapter with beego official support
|
||||
const (
|
||||
AdapterConsole = "console"
|
||||
AdapterFile = "file"
|
||||
AdapterMultiFile = "multifile"
|
||||
AdapterMail = "smtp"
|
||||
AdapterConn = "conn"
|
||||
AdapterEs = "es"
|
||||
AdapterJianLiao = "jianliao"
|
||||
AdapterSlack = "slack"
|
||||
AdapterAliLS = "alils"
|
||||
)
|
||||
|
||||
// Legacy log level constants to ensure backwards compatibility.
|
||||
const (
|
||||
LevelInfo = LevelInformational
|
||||
LevelTrace = LevelDebug
|
||||
LevelWarn = LevelWarning
|
||||
)
|
||||
|
||||
type newLoggerFunc func() Logger
|
||||
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
Init(config string) error
|
||||
WriteMsg(when time.Time, msg string, level int, lable string, env string) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
|
||||
var adapters = make(map[string]newLoggerFunc)
|
||||
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, log newLoggerFunc) {
|
||||
if log == nil {
|
||||
panic("logs: Register provide is nil")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic("logs: Register called twice for provider " + name)
|
||||
}
|
||||
adapters[name] = log
|
||||
}
|
||||
|
||||
// BeeLogger is default logger in beego application.
|
||||
// it can contain several providers and log message into all providers.
|
||||
type BeeLogger struct {
|
||||
lock sync.Mutex
|
||||
level int
|
||||
init bool
|
||||
enableFuncCallDepth bool
|
||||
loggerFuncCallDepth int
|
||||
asynchronous bool
|
||||
prefix string
|
||||
msgChanLen int64
|
||||
msgChan chan *logMsg
|
||||
signalChan chan string
|
||||
wg sync.WaitGroup
|
||||
outputs []*nameLogger
|
||||
Lable string
|
||||
Env string
|
||||
}
|
||||
|
||||
const defaultAsyncMsgLen = 1e3
|
||||
|
||||
type nameLogger struct {
|
||||
Logger
|
||||
name string
|
||||
}
|
||||
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
when time.Time
|
||||
}
|
||||
|
||||
var logMsgPool *sync.Pool
|
||||
|
||||
// NewLogger returns a new BeeLogger.
|
||||
// channelLen means the number of messages in chan(used where asynchronous is true).
|
||||
// if the buffering chan is full, logger adapters write to file or other way.
|
||||
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 2
|
||||
bl.msgChanLen = append(channelLens, 0)[0]
|
||||
if bl.msgChanLen <= 0 {
|
||||
bl.msgChanLen = defaultAsyncMsgLen
|
||||
}
|
||||
bl.signalChan = make(chan string, 1)
|
||||
bl.setLogger(AdapterConsole)
|
||||
return bl
|
||||
}
|
||||
|
||||
func GetLevelString(level int) string {
|
||||
return levelPrefix[level]
|
||||
}
|
||||
|
||||
// Async set the log to asynchronous and start the goroutine
|
||||
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if bl.asynchronous {
|
||||
return bl
|
||||
}
|
||||
bl.asynchronous = true
|
||||
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||
bl.msgChanLen = msgLen[0]
|
||||
}
|
||||
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
|
||||
logMsgPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &logMsg{}
|
||||
},
|
||||
}
|
||||
bl.wg.Add(1)
|
||||
go bl.startLogger()
|
||||
return bl
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||
config := append(configs, "{}")[0]
|
||||
for _, l := range bl.outputs {
|
||||
if l.name == adapterName {
|
||||
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||
}
|
||||
}
|
||||
|
||||
logAdapter, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
|
||||
lg := logAdapter()
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
return err
|
||||
}
|
||||
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if !bl.init {
|
||||
bl.outputs = []*nameLogger{}
|
||||
bl.init = true
|
||||
}
|
||||
return bl.setLogger(adapterName, configs...)
|
||||
}
|
||||
|
||||
// DelLogger remove a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
outputs := []*nameLogger{}
|
||||
for _, lg := range bl.outputs {
|
||||
if lg.name == adapterName {
|
||||
lg.Destroy()
|
||||
} else {
|
||||
outputs = append(outputs, lg)
|
||||
}
|
||||
}
|
||||
if len(outputs) == len(bl.outputs) {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
bl.outputs = outputs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(when, msg, level, bl.Lable, bl.Env)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// writeMsg will always add a '\n' character
|
||||
if p[len(p)-1] == '\n' {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
// set levelLoggerImpl to ensure all log message will be write out
|
||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||
if err == nil {
|
||||
return len(p), err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
|
||||
if !bl.init {
|
||||
bl.lock.Lock()
|
||||
bl.setLogger(AdapterConsole)
|
||||
bl.lock.Unlock()
|
||||
}
|
||||
|
||||
if len(v) > 0 {
|
||||
msg = fmt.Sprintf(msg, v...)
|
||||
}
|
||||
|
||||
msg = bl.prefix + " " + msg
|
||||
|
||||
when := time.Now()
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
_, filename := path.Split(file)
|
||||
msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
|
||||
}
|
||||
|
||||
//set level info in front of filename info
|
||||
if logLevel == levelLoggerImpl {
|
||||
// set to emergency to ensure all log will be print out correctly
|
||||
logLevel = LevelEmergency
|
||||
} else {
|
||||
msg = levelPrefix[logLevel] + " " + msg
|
||||
}
|
||||
|
||||
if bl.asynchronous {
|
||||
lm := logMsgPool.Get().(*logMsg)
|
||||
lm.level = logLevel
|
||||
lm.msg = msg
|
||||
lm.when = when
|
||||
bl.msgChan <- lm
|
||||
} else {
|
||||
bl.writeToLoggers(when, msg, logLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLevel Set log message level.
|
||||
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||
// log providers will not even be sent the message.
|
||||
func (bl *BeeLogger) SetLevel(l int) {
|
||||
bl.level = l
|
||||
}
|
||||
|
||||
// GetLevel Get Current log message level.
|
||||
func (bl *BeeLogger) GetLevel() int {
|
||||
return bl.level
|
||||
}
|
||||
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||
bl.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||
return bl.loggerFuncCallDepth
|
||||
}
|
||||
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
bl.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// set prefix
|
||||
func (bl *BeeLogger) SetPrefix(s string) {
|
||||
bl.prefix = s
|
||||
}
|
||||
|
||||
// start logger chan reading.
|
||||
// when chan is not empty, write logs.
|
||||
func (bl *BeeLogger) startLogger() {
|
||||
gameOver := false
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msgChan:
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
case sg := <-bl.signalChan:
|
||||
// Now should only send "flush" or "close" to bl.signalChan
|
||||
bl.flush()
|
||||
if sg == "close" {
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
gameOver = true
|
||||
}
|
||||
bl.wg.Done()
|
||||
}
|
||||
if gameOver {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emergency Log EMERGENCY level message.
|
||||
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||
if LevelEmergency > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelEmergency, format, v...)
|
||||
}
|
||||
|
||||
// Alert Log ALERT level message.
|
||||
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||
if LevelAlert > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelAlert, format, v...)
|
||||
}
|
||||
|
||||
// Critical Log CRITICAL level message.
|
||||
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
if LevelCritical > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelCritical, format, v...)
|
||||
}
|
||||
|
||||
// Error Log ERROR level message.
|
||||
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
if LevelError > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelError, format, v...)
|
||||
}
|
||||
|
||||
// Warning Log WARNING level message.
|
||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||
if LevelWarn > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelWarn, format, v...)
|
||||
}
|
||||
|
||||
// Notice Log NOTICE level message.
|
||||
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||
if LevelNotice > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelNotice, format, v...)
|
||||
}
|
||||
|
||||
// Informational Log INFORMATIONAL level message.
|
||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||
if LevelInfo > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelInfo, format, v...)
|
||||
}
|
||||
|
||||
// Debug Log DEBUG level message.
|
||||
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelDebug, format, v...)
|
||||
}
|
||||
|
||||
// Warn Log WARN level message.
|
||||
// compatibility alias for Warning()
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
if LevelWarn > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelWarn, format, v...)
|
||||
}
|
||||
|
||||
// Info Log INFO level message.
|
||||
// compatibility alias for Informational()
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
if LevelInfo > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelInfo, format, v...)
|
||||
}
|
||||
|
||||
// Trace Log TRACE level message.
|
||||
// compatibility alias for Debug()
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
bl.writeMsg(LevelDebug, format, v...)
|
||||
}
|
||||
|
||||
// Flush flush all chan data.
|
||||
func (bl *BeeLogger) Flush() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "flush"
|
||||
bl.wg.Wait()
|
||||
bl.wg.Add(1)
|
||||
return
|
||||
}
|
||||
bl.flush()
|
||||
}
|
||||
|
||||
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
func (bl *BeeLogger) Close() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "close"
|
||||
bl.wg.Wait()
|
||||
close(bl.msgChan)
|
||||
} else {
|
||||
bl.flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
close(bl.signalChan)
|
||||
}
|
||||
|
||||
// Reset close all outputs, and set bl.outputs to nil
|
||||
func (bl *BeeLogger) Reset() {
|
||||
bl.Flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) flush() {
|
||||
if bl.asynchronous {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// beeLogger references the used application logger.
|
||||
var beeLogger = NewLogger()
|
||||
|
||||
// GetBeeLogger returns the default BeeLogger
|
||||
func GetBeeLogger() *BeeLogger {
|
||||
return beeLogger
|
||||
}
|
||||
|
||||
var beeLoggerMap = struct {
|
||||
sync.RWMutex
|
||||
logs map[string]*log.Logger
|
||||
}{
|
||||
logs: map[string]*log.Logger{},
|
||||
}
|
||||
|
||||
// GetLogger returns the default BeeLogger
|
||||
func GetLogger(prefixes ...string) *log.Logger {
|
||||
prefix := append(prefixes, "")[0]
|
||||
if prefix != "" {
|
||||
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||
}
|
||||
beeLoggerMap.RLock()
|
||||
l, ok := beeLoggerMap.logs[prefix]
|
||||
if ok {
|
||||
beeLoggerMap.RUnlock()
|
||||
return l
|
||||
}
|
||||
beeLoggerMap.RUnlock()
|
||||
beeLoggerMap.Lock()
|
||||
defer beeLoggerMap.Unlock()
|
||||
l, ok = beeLoggerMap.logs[prefix]
|
||||
if !ok {
|
||||
l = log.New(beeLogger, prefix, 0)
|
||||
beeLoggerMap.logs[prefix] = l
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Reset will remove all the adapter
|
||||
func Reset() {
|
||||
beeLogger.Reset()
|
||||
}
|
||||
|
||||
// Async set the beelogger with Async mode and hold msglen messages
|
||||
func Async(msgLen ...int64) *BeeLogger {
|
||||
return beeLogger.Async(msgLen...)
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level used by the simple logger.
|
||||
func SetLevel(l int) {
|
||||
beeLogger.SetLevel(l)
|
||||
}
|
||||
|
||||
// SetPrefix sets the prefix
|
||||
func SetPrefix(s string) {
|
||||
beeLogger.SetPrefix(s)
|
||||
}
|
||||
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func EnableFuncCallDepth(b bool) {
|
||||
beeLogger.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// SetLogFuncCall set the CallDepth, default is 4
|
||||
func SetLogFuncCall(b bool) {
|
||||
beeLogger.EnableFuncCallDepth(b)
|
||||
beeLogger.SetLogFuncCallDepth(4)
|
||||
}
|
||||
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func SetLogFuncCallDepth(d int) {
|
||||
beeLogger.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(adapter string, config ...string) error {
|
||||
return beeLogger.SetLogger(adapter, config...)
|
||||
}
|
||||
|
||||
// Emergency logs a message at emergency level.
|
||||
func Emergency(f interface{}, v ...interface{}) {
|
||||
beeLogger.Emergency(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Alert logs a message at alert level.
|
||||
func Alert(f interface{}, v ...interface{}) {
|
||||
beeLogger.Alert(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(f interface{}, v ...interface{}) {
|
||||
beeLogger.Critical(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(f interface{}, v ...interface{}) {
|
||||
beeLogger.Error(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warning(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Warn compatibility alias for Warning()
|
||||
func Warn(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Notice logs a message at notice level.
|
||||
func Notice(f interface{}, v ...interface{}) {
|
||||
beeLogger.Notice(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Informational logs a message at info level.
|
||||
func Informational(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Info compatibility alias for Warning()
|
||||
func Info(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(f interface{}, v ...interface{}) {
|
||||
beeLogger.Debug(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
// compatibility alias for Warning()
|
||||
func Trace(f interface{}, v ...interface{}) {
|
||||
beeLogger.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...)
|
||||
}
|
175
logs/logger.go
Normal file
175
logs/logger.go
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func newLogWriter(wr io.Writer) *logWriter {
|
||||
return &logWriter{writer: wr}
|
||||
}
|
||||
|
||||
func (lg *logWriter) writeln(when time.Time, msg string) {
|
||||
lg.Lock()
|
||||
h, _, _ := formatTimeHeader(when)
|
||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||
lg.Unlock()
|
||||
}
|
||||
|
||||
const (
|
||||
y1 = `0123456789`
|
||||
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
mo1 = `000000000111`
|
||||
mo2 = `123456789012`
|
||||
d1 = `0000000001111111111222222222233`
|
||||
d2 = `1234567890123456789012345678901`
|
||||
h1 = `000000000011111111112222`
|
||||
h2 = `012345678901234567890123`
|
||||
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
ns1 = `0123456789`
|
||||
)
|
||||
|
||||
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
ns := when.Nanosecond() / 1000000
|
||||
//len("2006/01/02 15:04:05.123 ")==24
|
||||
var buf [24]byte
|
||||
|
||||
buf[0] = y1[y/1000%10]
|
||||
buf[1] = y2[y/100]
|
||||
buf[2] = y3[y-y/100*100]
|
||||
buf[3] = y4[y-y/100*100]
|
||||
buf[4] = '/'
|
||||
buf[5] = mo1[mo-1]
|
||||
buf[6] = mo2[mo-1]
|
||||
buf[7] = '/'
|
||||
buf[8] = d1[d-1]
|
||||
buf[9] = d2[d-1]
|
||||
buf[10] = ' '
|
||||
buf[11] = h1[h]
|
||||
buf[12] = h2[h]
|
||||
buf[13] = ':'
|
||||
buf[14] = mi1[mi]
|
||||
buf[15] = mi2[mi]
|
||||
buf[16] = ':'
|
||||
buf[17] = s1[s]
|
||||
buf[18] = s2[s]
|
||||
buf[19] = '.'
|
||||
buf[20] = ns1[ns/100]
|
||||
buf[21] = ns1[ns%100/10]
|
||||
buf[22] = ns1[ns%10]
|
||||
|
||||
buf[23] = ' '
|
||||
|
||||
return buf[0:], d, h
|
||||
}
|
||||
|
||||
var (
|
||||
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||
|
||||
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||
|
||||
reset = string([]byte{27, 91, 48, 109})
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var colorMap map[string]string
|
||||
|
||||
func initColor() {
|
||||
if runtime.GOOS == "windows" {
|
||||
green = w32Green
|
||||
white = w32White
|
||||
yellow = w32Yellow
|
||||
red = w32Red
|
||||
blue = w32Blue
|
||||
magenta = w32Magenta
|
||||
cyan = w32Cyan
|
||||
}
|
||||
colorMap = map[string]string{
|
||||
//by color
|
||||
"green": green,
|
||||
"white": white,
|
||||
"yellow": yellow,
|
||||
"red": red,
|
||||
//by method
|
||||
"GET": blue,
|
||||
"POST": cyan,
|
||||
"PUT": yellow,
|
||||
"DELETE": red,
|
||||
"PATCH": green,
|
||||
"HEAD": magenta,
|
||||
"OPTIONS": white,
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByStatus return color by http code
|
||||
// 2xx return Green
|
||||
// 3xx return White
|
||||
// 4xx return Yellow
|
||||
// 5xx return Red
|
||||
func ColorByStatus(code int) string {
|
||||
once.Do(initColor)
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
return colorMap["green"]
|
||||
case code >= 300 && code < 400:
|
||||
return colorMap["white"]
|
||||
case code >= 400 && code < 500:
|
||||
return colorMap["yellow"]
|
||||
default:
|
||||
return colorMap["red"]
|
||||
}
|
||||
}
|
||||
|
||||
// ColorByMethod return color by http code
|
||||
func ColorByMethod(method string) string {
|
||||
once.Do(initColor)
|
||||
if c := colorMap[method]; c != "" {
|
||||
return c
|
||||
}
|
||||
return reset
|
||||
}
|
||||
|
||||
// ResetColor return reset color
|
||||
func ResetColor() string {
|
||||
return reset
|
||||
}
|
57
logs/logger_test.go
Normal file
57
logs/logger_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2016 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFormatHeader_0(t *testing.T) {
|
||||
tm := time.Now()
|
||||
if tm.Year() >= 2100 {
|
||||
t.FailNow()
|
||||
}
|
||||
dur := time.Second
|
||||
for {
|
||||
if tm.Year() >= 2100 {
|
||||
break
|
||||
}
|
||||
h, _, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
}
|
||||
tm = tm.Add(dur)
|
||||
dur *= 2
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHeader_1(t *testing.T) {
|
||||
tm := time.Now()
|
||||
year := tm.Year()
|
||||
dur := time.Second
|
||||
for {
|
||||
if tm.Year() >= year+1 {
|
||||
break
|
||||
}
|
||||
h, _, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
}
|
||||
tm = tm.Add(dur)
|
||||
}
|
||||
}
|
119
logs/multifile.go
Normal file
119
logs/multifile.go
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A filesLogWriter manages several fileLogWriter
|
||||
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||
// the rotate attribute also acts like fileLogWriter
|
||||
type multiFileLogWriter struct {
|
||||
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||
fullLogWriter *fileLogWriter
|
||||
Separate []string `json:"separate"`
|
||||
}
|
||||
|
||||
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":0,
|
||||
// "maxsize":0,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600,
|
||||
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||
// }
|
||||
|
||||
func (f *multiFileLogWriter) Init(config string) error {
|
||||
writer := newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fullLogWriter = writer
|
||||
f.writers[LevelDebug+1] = writer
|
||||
|
||||
//unmarshal "separate" field to f.Separate
|
||||
json.Unmarshal([]byte(config), f)
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(config), &jsonMap)
|
||||
|
||||
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||
for _, v := range f.Separate {
|
||||
if v == levelNames[i] {
|
||||
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||
jsonMap["level"] = i
|
||||
bs, _ := json.Marshal(jsonMap)
|
||||
writer = newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(string(bs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writers[i] = writer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Destroy() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if f.fullLogWriter != nil {
|
||||
f.fullLogWriter.WriteMsg(when, msg, level, lable, env)
|
||||
}
|
||||
for i := 0; i < len(f.writers)-1; i++ {
|
||||
if f.writers[i] != nil {
|
||||
if level == f.writers[i].Level {
|
||||
f.writers[i].WriteMsg(when, msg, level, lable, env)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Flush() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFilesWriter() Logger {
|
||||
return &multiFileLogWriter{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterMultiFile, newFilesWriter)
|
||||
}
|
78
logs/multifile_test.go
Normal file
78
logs/multifile_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFiles_1(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
fns := []string{""}
|
||||
fns = append(fns, levelNames[0:]...)
|
||||
name := "test"
|
||||
suffix := ".log"
|
||||
for _, fn := range fns {
|
||||
|
||||
file := name + suffix
|
||||
if fn != "" {
|
||||
file = name + "." + fn + suffix
|
||||
}
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
lastLine := ""
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lastLine = string(line)
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
var expected = 1
|
||||
if fn == "" {
|
||||
expected = LevelDebug + 1
|
||||
}
|
||||
if lineNum != expected {
|
||||
t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
if lineNum == 1 {
|
||||
if !strings.Contains(lastLine, fn) {
|
||||
t.Fatal(file + " " + lastLine + " not contains the log msg " + fn)
|
||||
}
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
|
||||
}
|
60
logs/slack.go
Normal file
60
logs/slack.go
Normal file
@ -0,0 +1,60 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||
type SLACKWriter struct {
|
||||
WebhookURL string `json:"webhookurl"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// newSLACKWriter create jiaoliao writer.
|
||||
func newSLACKWriter() Logger {
|
||||
return &SLACKWriter{Level: LevelTrace}
|
||||
}
|
||||
|
||||
// Init SLACKWriter with json config string
|
||||
func (s *SLACKWriter) Init(jsonconfig string) error {
|
||||
return json.Unmarshal([]byte(jsonconfig), s)
|
||||
}
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("payload", text)
|
||||
|
||||
resp, err := http.PostForm(s.WebhookURL, form)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *SLACKWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *SLACKWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterSlack, newSLACKWriter)
|
||||
}
|
149
logs/smtp.go
Normal file
149
logs/smtp.go
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||
type SMTPWriter struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Subject string `json:"subject"`
|
||||
FromAddress string `json:"fromAddress"`
|
||||
RecipientAddresses []string `json:"sendTos"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
// NewSMTPWriter create smtp writer.
|
||||
func newSMTPWriter() Logger {
|
||||
return &SMTPWriter{Level: LevelTrace}
|
||||
}
|
||||
|
||||
// Init smtp writer with json config.
|
||||
// config like:
|
||||
// {
|
||||
// "username":"example@gmail.com",
|
||||
// "password:"password",
|
||||
// "host":"smtp.gmail.com:465",
|
||||
// "subject":"email title",
|
||||
// "fromAddress":"from@example.com",
|
||||
// "sendTos":["email1","email2"],
|
||||
// "level":LevelError
|
||||
// }
|
||||
func (s *SMTPWriter) Init(jsonconfig string) error {
|
||||
return json.Unmarshal([]byte(jsonconfig), s)
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||
return nil
|
||||
}
|
||||
return smtp.PlainAuth(
|
||||
"",
|
||||
s.Username,
|
||||
s.Password,
|
||||
host,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||
client, err := smtp.Dial(hostAddressWithPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||
tlsConn := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: host,
|
||||
}
|
||||
if err = client.StartTLS(tlsConn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if auth != nil {
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.Mail(fromAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range recipients {
|
||||
if err = client.Rcpt(rec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(msgContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int, lable string, env string) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
|
||||
hp := strings.Split(s.Host, ":")
|
||||
|
||||
// Set up authentication information.
|
||||
auth := s.getSMTPAuth(hp[0])
|
||||
|
||||
// Connect to the server, authenticate, set the sender and recipient,
|
||||
// and send the email all in one step.
|
||||
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||
}
|
||||
|
||||
// Flush implementing method. empty.
|
||||
func (s *SMTPWriter) Flush() {
|
||||
}
|
||||
|
||||
// Destroy implementing method. empty.
|
||||
func (s *SMTPWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register(AdapterMail, newSMTPWriter)
|
||||
}
|
27
logs/smtp_test.go
Normal file
27
logs/smtp_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSmtp(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||
log.Critical("sendmail critical")
|
||||
time.Sleep(time.Second * 30)
|
||||
}
|
36
options.go
Normal file
36
options.go
Normal file
@ -0,0 +1,36 @@
|
||||
package grlogs
|
||||
|
||||
type Level int
|
||||
|
||||
type Adapter string
|
||||
|
||||
type AdapterTupple struct {
|
||||
Level Level
|
||||
Adapter Adapter
|
||||
}
|
||||
|
||||
const (
|
||||
_ Level = iota
|
||||
LevelNone Level = iota
|
||||
LevelCritical Level = iota
|
||||
LevelError Level = iota
|
||||
LevelWarning Level = iota
|
||||
LevelWarn Level = iota
|
||||
LevelInfo Level = iota
|
||||
LevelDebug Level = iota
|
||||
LevelAll Level = iota
|
||||
)
|
||||
|
||||
const (
|
||||
AdapterFile Adapter = "file"
|
||||
AdapterConsole Adapter = "console"
|
||||
AdapterSocket Adapter = "socket"
|
||||
AdapterElasticSearch Adapter = "es"
|
||||
AdapterAliLs Adapter = "alils"
|
||||
)
|
||||
|
||||
const (
|
||||
DEV = "dev"
|
||||
GRAY = "gray"
|
||||
PROD = "prod"
|
||||
)
|
Loading…
Reference in New Issue
Block a user