mirror of https://github.com/ouqiang/gocron
用seelog替换默认的log库
parent
6fc6a0d65d
commit
26d5c1cba9
19
cmd/web.go
19
cmd/web.go
|
@ -26,10 +26,16 @@ var CmdWeb = cli.Command{
|
||||||
Value: DefaultPort,
|
Value: DefaultPort,
|
||||||
Usage: "bind port number",
|
Usage: "bind port number",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "env,e",
|
||||||
|
Value: "dev",
|
||||||
|
Usage: "runtime environment, dev|test|prod",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx *cli.Context) {
|
func run(ctx *cli.Context) {
|
||||||
|
setEnvironment(ctx)
|
||||||
app.InitEnv()
|
app.InitEnv()
|
||||||
m := macaron.Classic()
|
m := macaron.Classic()
|
||||||
// 注册路由
|
// 注册路由
|
||||||
|
@ -64,7 +70,7 @@ func registerMiddleware(m *macaron.Macaron) {
|
||||||
|
|
||||||
// 解析端口
|
// 解析端口
|
||||||
func parsePort(ctx *cli.Context) int {
|
func parsePort(ctx *cli.Context) int {
|
||||||
var port int
|
var port int = DefaultPort
|
||||||
if ctx.IsSet("port") {
|
if ctx.IsSet("port") {
|
||||||
port = ctx.Int("port")
|
port = ctx.Int("port")
|
||||||
}
|
}
|
||||||
|
@ -74,3 +80,14 @@ func parsePort(ctx *cli.Context) int {
|
||||||
|
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setEnvironment(ctx *cli.Context) {
|
||||||
|
var env string = ""
|
||||||
|
if ctx.IsSet("env") {
|
||||||
|
env = ctx.String("env")
|
||||||
|
}
|
||||||
|
|
||||||
|
if env == "prod" {
|
||||||
|
macaron.Env = macaron.PROD
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,10 @@ package ansible
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/ouqiang/cron-scheduler/models"
|
"github.com/ouqiang/cron-scheduler/models"
|
||||||
"github.com/ouqiang/cron-scheduler/modules/utils"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"github.com/ouqiang/cron-scheduler/modules/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 主机名
|
// 主机名
|
||||||
|
@ -37,11 +37,11 @@ func (h *Hosts) Write() {
|
||||||
host := new(models.Host)
|
host := new(models.Host)
|
||||||
hostModels, err := host.List()
|
hostModels, err := host.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(hostModels) == 0 {
|
if len(hostModels) == 0 {
|
||||||
utils.RecordLog("hosts内容为空")
|
logger.Info("hosts内容为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/ouqiang/cron-scheduler/modules/utils"
|
"github.com/ouqiang/cron-scheduler/modules/utils"
|
||||||
"github.com/ouqiang/cron-scheduler/service"
|
"github.com/ouqiang/cron-scheduler/service"
|
||||||
"github.com/ouqiang/cron-scheduler/modules/setting"
|
"github.com/ouqiang/cron-scheduler/modules/setting"
|
||||||
|
"github.com/ouqiang/cron-scheduler/modules/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,6 +24,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitEnv() {
|
func InitEnv() {
|
||||||
|
logger.InitLogger()
|
||||||
CheckEnv()
|
CheckEnv()
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -58,15 +60,11 @@ func IsInstalled() bool {
|
||||||
func CheckEnv() {
|
func CheckEnv() {
|
||||||
// ansible不支持安装在windows上, windows只能作为被控机
|
// ansible不支持安装在windows上, windows只能作为被控机
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
panic("不支持在windows上运行")
|
logger.Fatal("不支持在windows上运行")
|
||||||
}
|
}
|
||||||
_, err := utils.ExecShell("ansible", "--version")
|
_, err := utils.ExecShell("ansible", "--version")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
|
||||||
_, err = utils.ExecShell("ansible-playbook", "--version")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +72,7 @@ func CheckEnv() {
|
||||||
func CreateInstallLock() error {
|
func CreateInstallLock() error {
|
||||||
_, err := os.Create(ConfDir + "/install.lock")
|
_, err := os.Create(ConfDir + "/install.lock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("创建安装锁文件失败")
|
logger.Error("创建安装锁文件失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -101,10 +99,10 @@ func checkDirExists(path ...string) {
|
||||||
for _, value := range path {
|
for _, value := range path {
|
||||||
_, err := os.Stat(value)
|
_, err := os.Stat(value)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
panic(value + "目录不存在")
|
logger.Fatal(value + "目录不存在")
|
||||||
}
|
}
|
||||||
if os.IsPermission(err) {
|
if os.IsPermission(err) {
|
||||||
panic(value + "目录无权限操作")
|
logger.Fatal(value + "目录无权限操作")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,11 +111,11 @@ func checkDirExists(path ...string) {
|
||||||
func getDbConfig(configFile string) map[string]string {
|
func getDbConfig(configFile string) map[string]string {
|
||||||
config, err := setting.Read(configFile)
|
config, err := setting.Read(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
section := config.Section("db")
|
section := config.Section("db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
logger.Fatal(err)
|
||||||
}
|
}
|
||||||
var db map[string]string = make(map[string]string)
|
var db map[string]string = make(map[string]string)
|
||||||
db["user"] = section.Key("user").String()
|
db["user"] = section.Key("user").String()
|
||||||
|
@ -130,4 +128,4 @@ func getDbConfig(configFile string) map[string]string {
|
||||||
db["engine"] = section.Key("engine").String()
|
db["engine"] = section.Key("engine").String()
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cihub/seelog"
|
||||||
|
"gopkg.in/macaron.v1"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 日志库
|
||||||
|
|
||||||
|
type Level int8
|
||||||
|
|
||||||
|
var logger seelog.LoggerInterface
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEBUG = iota
|
||||||
|
INFO
|
||||||
|
WARN
|
||||||
|
ERROR
|
||||||
|
FATAL
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitLogger() {
|
||||||
|
config := getLogConfig()
|
||||||
|
l, err := seelog.LoggerFromConfigAsString(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
write(DEBUG, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
write(INFO, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(v ...interface{}) {
|
||||||
|
write(WARN, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(v ...interface{}) {
|
||||||
|
write(ERROR, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fatal(v ...interface{}) {
|
||||||
|
write(FATAL, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func write(level Level, v ...interface{}) {
|
||||||
|
defer logger.Flush()
|
||||||
|
|
||||||
|
switch level {
|
||||||
|
case DEBUG:
|
||||||
|
logger.Debug(v)
|
||||||
|
case INFO:
|
||||||
|
logger.Info(v)
|
||||||
|
case WARN:
|
||||||
|
logger.Warn(v)
|
||||||
|
case FATAL:
|
||||||
|
logger.Critical(v)
|
||||||
|
os.Exit(1)
|
||||||
|
case ERROR:
|
||||||
|
logger.Error(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogConfig() string {
|
||||||
|
config := `
|
||||||
|
<seelog>
|
||||||
|
<outputs formatid="main">
|
||||||
|
%s
|
||||||
|
<filter levels="info,critical,error,warn">
|
||||||
|
<file path="log/cron.log" />
|
||||||
|
</filter>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="main" format="%%Date/%%Time [%%LEV] %%Msg%%n"/>
|
||||||
|
</formats>
|
||||||
|
</seelog>`
|
||||||
|
|
||||||
|
consoleConfig := ""
|
||||||
|
if macaron.Env == macaron.DEV {
|
||||||
|
consoleConfig =
|
||||||
|
`
|
||||||
|
<filter levels="info,debug,critical,warn,error">
|
||||||
|
<console />
|
||||||
|
</filter>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
config = fmt.Sprintf(config, consoleConfig)
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/ouqiang/cron-scheduler/modules/logger"
|
||||||
|
)
|
||||||
|
|
||||||
// json 格式输出
|
// json 格式输出
|
||||||
|
|
||||||
|
@ -32,7 +35,7 @@ func (j *Json) response(code int, message string, data interface{}) string {
|
||||||
|
|
||||||
result, err := json.Marshal(resp)
|
result, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RecordLog(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(result)
|
return string(result)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package utils
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
@ -43,10 +42,4 @@ func RandNumber(max int) int {
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
return r.Intn(max)
|
return r.Intn(max)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日志记录
|
|
||||||
// todo 保存到哪里 文件,数据库还是elasticsearch?,暂时输出到终端
|
|
||||||
func RecordLog(v ...interface{}) {
|
|
||||||
log.Println(v)
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/ouqiang/cron-scheduler/modules/utils"
|
"github.com/ouqiang/cron-scheduler/modules/utils"
|
||||||
"gopkg.in/macaron.v1"
|
"gopkg.in/macaron.v1"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"github.com/ouqiang/cron-scheduler/modules/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 系统安装
|
// 系统安装
|
||||||
|
@ -38,17 +39,18 @@ func Show(ctx *macaron.Context) {
|
||||||
func Install(ctx *macaron.Context, form InstallForm) string {
|
func Install(ctx *macaron.Context, form InstallForm) string {
|
||||||
json := utils.Json{}
|
json := utils.Json{}
|
||||||
if app.Installed {
|
if app.Installed {
|
||||||
|
logger.Warn("系统重复安装")
|
||||||
return json.Failure(utils.ResponseFailure, "系统已安装!")
|
return json.Failure(utils.ResponseFailure, "系统已安装!")
|
||||||
}
|
}
|
||||||
err := testDbConnection(form)
|
err := testDbConnection(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return json.Failure(utils.ResponseFailure, "数据库连接失败")
|
return json.Failure(utils.ResponseFailure, "数据库连接失败")
|
||||||
}
|
}
|
||||||
// 写入数据库配置
|
// 写入数据库配置
|
||||||
err = writeConfig(form)
|
err = writeConfig(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return json.Failure(utils.ResponseFailure, "数据库配置写入文件失败")
|
return json.Failure(utils.ResponseFailure, "数据库配置写入文件失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,21 +59,21 @@ func Install(ctx *macaron.Context, form InstallForm) string {
|
||||||
migration := new(models.Migration)
|
migration := new(models.Migration)
|
||||||
err = migration.Exec(form.DbName)
|
err = migration.Exec(form.DbName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return json.Failure(utils.ResponseFailure, "创建数据库表失败")
|
return json.Failure(utils.ResponseFailure, "创建数据库表失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建管理员账号
|
// 创建管理员账号
|
||||||
err = createAdminUser(form)
|
err = createAdminUser(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return json.Failure(utils.ResponseFailure, "创建管理员账号失败")
|
return json.Failure(utils.ResponseFailure, "创建管理员账号失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建安装锁
|
// 创建安装锁
|
||||||
err = app.CreateInstallLock()
|
err = app.CreateInstallLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
return json.Failure(utils.ResponseFailure, "创建文件安装锁失败")
|
return json.Failure(utils.ResponseFailure, "创建文件安装锁失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +124,13 @@ func testDbConnection(form InstallForm) error {
|
||||||
dbConfig["password"] = form.DbPassword
|
dbConfig["password"] = form.DbPassword
|
||||||
dbConfig["charset"] = "utf8"
|
dbConfig["charset"] = "utf8"
|
||||||
db, err := models.CreateTmpDb(dbConfig)
|
db, err := models.CreateTmpDb(dbConfig)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
db.Close()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer db.Close()
|
||||||
|
err = db.Ping()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"github.com/ouqiang/cron-scheduler/models"
|
"github.com/ouqiang/cron-scheduler/models"
|
||||||
"github.com/ouqiang/cron-scheduler/modules/ansible"
|
"github.com/ouqiang/cron-scheduler/modules/ansible"
|
||||||
"github.com/ouqiang/cron-scheduler/modules/crontask"
|
"github.com/ouqiang/cron-scheduler/modules/crontask"
|
||||||
"github.com/ouqiang/cron-scheduler/modules/utils"
|
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"github.com/ouqiang/cron-scheduler/modules/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Task struct{}
|
type Task struct{}
|
||||||
|
@ -20,11 +20,11 @@ func (task *Task) Initialize() {
|
||||||
taskModel := new(models.Task)
|
taskModel := new(models.Task)
|
||||||
taskList, err := taskModel.ActiveList()
|
taskList, err := taskModel.ActiveList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("获取任务列表错误-", err.Error())
|
logger.Error("获取任务列表错误-", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(taskList) == 0 {
|
if len(taskList) == 0 {
|
||||||
utils.RecordLog("任务列表为空")
|
logger.Debug("任务列表为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, item := range taskList {
|
for _, item := range taskList {
|
||||||
|
@ -36,14 +36,14 @@ func (task *Task) Initialize() {
|
||||||
func (task *Task) Add(taskModel models.Task) {
|
func (task *Task) Add(taskModel models.Task) {
|
||||||
taskFunc := createHandlerJob(taskModel)
|
taskFunc := createHandlerJob(taskModel)
|
||||||
if taskFunc == nil {
|
if taskFunc == nil {
|
||||||
utils.RecordLog("添加任务#不存在的任务协议编号", taskModel.Protocol)
|
logger.Error("添加任务#不存在的任务协议编号", taskModel.Protocol)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 定时任务
|
// 定时任务
|
||||||
if taskModel.Type == models.Timing {
|
if taskModel.Type == models.Timing {
|
||||||
err := crontask.DefaultCronTask.AddOrReplace(strconv.Itoa(taskModel.Id), taskModel.Spec, taskFunc)
|
err := crontask.DefaultCronTask.AddOrReplace(strconv.Itoa(taskModel.Id), taskModel.Spec, taskFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
} else if taskModel.Type == models.Delay {
|
} else if taskModel.Type == models.Delay {
|
||||||
// 延时任务
|
// 延时任务
|
||||||
|
@ -65,7 +65,7 @@ func (h *HTTPHandler) Run(taskModel models.Task) (result string, err error) {
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("POST", taskModel.Command, nil)
|
req, err := http.NewRequest("POST", taskModel.Command, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("创建HTTP请求错误-", err.Error())
|
logger.Error("创建HTTP请求错误-", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
|
||||||
|
@ -78,12 +78,12 @@ func (h *HTTPHandler) Run(taskModel models.Task) (result string, err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("HTTP请求错误-", err.Error())
|
logger.Error("HTTP请求错误-", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("读取HTTP请求返回值失败-", err.Error())
|
logger.Error("读取HTTP请求返回值失败-", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(body), err
|
return string(body), err
|
||||||
|
@ -160,14 +160,14 @@ func createHandlerJob(taskModel models.Task) cron.FuncJob {
|
||||||
taskFunc := func() {
|
taskFunc := func() {
|
||||||
taskLogId, err := createTaskLog(taskModel.Id)
|
taskLogId, err := createTaskLog(taskModel.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("写入任务日志失败-", err)
|
logger.Error("写入任务日志失败-", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// err != nil 执行失败
|
// err != nil 执行失败
|
||||||
result, err := handler.Run(taskModel)
|
result, err := handler.Run(taskModel)
|
||||||
_, err = updateTaskLog(int(taskLogId), result, err)
|
_, err = updateTaskLog(int(taskLogId), result, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.RecordLog("更新任务日志失败-", err)
|
logger.Error("更新任务日志失败-", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2012, Cloud Instruments Co., Ltd. <info@cin.io>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Cloud Instruments Co., Ltd. nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,116 @@
|
||||||
|
Seelog
|
||||||
|
=======
|
||||||
|
|
||||||
|
Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log messages.
|
||||||
|
It is natively written in the [Go](http://golang.org/) programming language.
|
||||||
|
|
||||||
|
[![Build Status](https://drone.io/github.com/cihub/seelog/status.png)](https://drone.io/github.com/cihub/seelog/latest)
|
||||||
|
|
||||||
|
Features
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* Xml configuring to be able to change logger parameters without recompilation
|
||||||
|
* Changing configurations on the fly without app restart
|
||||||
|
* Possibility to set different log configurations for different project files and functions
|
||||||
|
* Adjustable message formatting
|
||||||
|
* Simultaneous log output to multiple streams
|
||||||
|
* Choosing logger priority strategy to minimize performance hit
|
||||||
|
* Different output writers
|
||||||
|
* Console writer
|
||||||
|
* File writer
|
||||||
|
* Buffered writer (Chunk writer)
|
||||||
|
* Rolling log writer (Logging with rotation)
|
||||||
|
* SMTP writer
|
||||||
|
* Others... (See [Wiki](https://github.com/cihub/seelog/wiki))
|
||||||
|
* Log message wrappers (JSON, XML, etc.)
|
||||||
|
* Global variables and functions for easy usage in standalone apps
|
||||||
|
* Functions for flexible usage in libraries
|
||||||
|
|
||||||
|
Quick-start
|
||||||
|
-----------
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer log.Flush()
|
||||||
|
log.Info("Hello from Seelog!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you don't have the Go development environment installed, visit the
|
||||||
|
[Getting Started](http://golang.org/doc/install.html) document and follow the instructions. Once you're ready, execute the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/cihub/seelog
|
||||||
|
```
|
||||||
|
|
||||||
|
*IMPORTANT*: If you are not using the latest release version of Go, check out this [wiki page](https://github.com/cihub/seelog/wiki/Notes-on-'go-get')
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Seelog has github wiki pages, which contain detailed how-tos references: https://github.com/cihub/seelog/wiki
|
||||||
|
|
||||||
|
Examples
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Seelog examples can be found here: [seelog-examples](https://github.com/cihub/seelog-examples)
|
||||||
|
|
||||||
|
Issues
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Feel free to push issues that could make Seelog better: https://github.com/cihub/seelog/issues
|
||||||
|
|
||||||
|
Changelog
|
||||||
|
---------------
|
||||||
|
* **v2.6** : Config using code and custom formatters
|
||||||
|
* Configuration using code in addition to xml (All internal receiver/dispatcher/logger types are now exported).
|
||||||
|
* Custom formatters. Check [wiki](https://github.com/cihub/seelog/wiki/Custom-formatters)
|
||||||
|
* Bugfixes and internal improvements.
|
||||||
|
* **v2.5** : Interaction with other systems. Part 2: custom receivers
|
||||||
|
* Finished custom receivers feature. Check [wiki](https://github.com/cihub/seelog/wiki/custom-receivers)
|
||||||
|
* Added 'LoggerFromCustomReceiver'
|
||||||
|
* Added 'LoggerFromWriterWithMinLevelAndFormat'
|
||||||
|
* Added 'LoggerFromCustomReceiver'
|
||||||
|
* Added 'LoggerFromParamConfigAs...'
|
||||||
|
* **v2.4** : Interaction with other systems. Part 1: wrapping seelog
|
||||||
|
* Added configurable caller stack skip logic
|
||||||
|
* Added 'SetAdditionalStackDepth' to 'LoggerInterface'
|
||||||
|
* **v2.3** : Rethinking 'rolling' receiver
|
||||||
|
* Reimplemented 'rolling' receiver
|
||||||
|
* Added 'Max rolls' feature for 'rolling' receiver with type='date'
|
||||||
|
* Fixed 'rolling' receiver issue: renaming on Windows
|
||||||
|
* **v2.2** : go1.0 compatibility point [go1.0 tag]
|
||||||
|
* Fixed internal bugs
|
||||||
|
* Added 'ANSI n [;k]' format identifier: %EscN
|
||||||
|
* Made current release go1 compatible
|
||||||
|
* **v2.1** : Some new features
|
||||||
|
* Rolling receiver archiving option.
|
||||||
|
* Added format identifier: %Line
|
||||||
|
* Smtp: added paths to PEM files directories
|
||||||
|
* Added format identifier: %FuncShort
|
||||||
|
* Warn, Error and Critical methods now return an error
|
||||||
|
* **v2.0** : Second major release. BREAKING CHANGES.
|
||||||
|
* Support of binaries with stripped symbols
|
||||||
|
* Added log strategy: adaptive
|
||||||
|
* Critical message now forces Flush()
|
||||||
|
* Added predefined formats: xml-debug, xml-debug-short, xml, xml-short, json-debug, json-debug-short, json, json-short, debug, debug-short, fast
|
||||||
|
* Added receiver: conn (network connection writer)
|
||||||
|
* BREAKING CHANGE: added Tracef, Debugf, Infof, etc. to satisfy the print/printf principle
|
||||||
|
* Bug fixes
|
||||||
|
* **v1.0** : Initial release. Features:
|
||||||
|
* Xml config
|
||||||
|
* Changing configurations on the fly without app restart
|
||||||
|
* Contraints and exceptions
|
||||||
|
* Formatting
|
||||||
|
* Log strategies: sync, async loop, async timer
|
||||||
|
* Receivers: buffered, console, file, rolling, smtp
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive/gzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is the interface for reading files from an archive.
|
||||||
|
type Reader interface {
|
||||||
|
NextFile() (name string, err error)
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCloser is the interface that groups Reader with the Close method.
|
||||||
|
type ReadCloser interface {
|
||||||
|
Reader
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is the interface for writing files to an archived format.
|
||||||
|
type Writer interface {
|
||||||
|
NextFile(name string, fi os.FileInfo) error
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteCloser is the interface that groups Writer with the Close method.
|
||||||
|
type WriteCloser interface {
|
||||||
|
Writer
|
||||||
|
io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopCloser struct{ Reader }
|
||||||
|
|
||||||
|
func (nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
// NopCloser returns a ReadCloser with a no-op Close method wrapping the
|
||||||
|
// provided Reader r.
|
||||||
|
func NopCloser(r Reader) ReadCloser {
|
||||||
|
return nopCloser{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies from src to dest until either EOF is reached on src or an error
|
||||||
|
// occurs.
|
||||||
|
//
|
||||||
|
// When the archive format of src matches that of dst, Copy streams the files
|
||||||
|
// directly into dst. Otherwise, copy buffers the contents to disk to compute
|
||||||
|
// headers before writing to dst.
|
||||||
|
func Copy(dst Writer, src Reader) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case tarReader:
|
||||||
|
if dst, ok := dst.(tarWriter); ok {
|
||||||
|
return copyTar(dst, src)
|
||||||
|
}
|
||||||
|
case zipReader:
|
||||||
|
if dst, ok := dst.(zipWriter); ok {
|
||||||
|
return copyZip(dst, src)
|
||||||
|
}
|
||||||
|
// Switch on concrete type because gzip has no special methods
|
||||||
|
case *gzip.Reader:
|
||||||
|
if dst, ok := dst.(*gzip.Writer); ok {
|
||||||
|
_, err := io.Copy(dst, src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyBuffer(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBuffer(dst Writer, src Reader) (err error) {
|
||||||
|
const defaultFileMode = 0666
|
||||||
|
|
||||||
|
buf, err := ioutil.TempFile("", "archive_copy_buffer")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(buf.Name()) // Do not care about failure removing temp
|
||||||
|
defer buf.Close() // Do not care about failure closing temp
|
||||||
|
for {
|
||||||
|
// Handle the next file
|
||||||
|
name, err := src.NextFile()
|
||||||
|
switch err {
|
||||||
|
case io.EOF: // Done copying
|
||||||
|
return nil
|
||||||
|
default: // Failed to write: bail out
|
||||||
|
return err
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer the file
|
||||||
|
if _, err := io.Copy(buf, src); err != nil {
|
||||||
|
return fmt.Errorf("buffer to disk: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to the start of the file for full file copy
|
||||||
|
if _, err := buf.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set desired file permissions
|
||||||
|
if err := os.Chmod(buf.Name(), defaultFileMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fi, err := buf.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the buffered file
|
||||||
|
if err := dst.NextFile(name, fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(dst, buf); err != nil {
|
||||||
|
return fmt.Errorf("copy to dst: %v", err)
|
||||||
|
}
|
||||||
|
if err := buf.Truncate(0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := buf.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tarReader interface {
|
||||||
|
Next() (*tar.Header, error)
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
type tarWriter interface {
|
||||||
|
WriteHeader(hdr *tar.Header) error
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipReader interface {
|
||||||
|
Files() []*zip.File
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipWriter interface {
|
||||||
|
CreateHeader(fh *zip.FileHeader) (io.Writer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyTar(w tarWriter, r tarReader) error {
|
||||||
|
for {
|
||||||
|
hdr, err := r.Next()
|
||||||
|
switch err {
|
||||||
|
case io.EOF:
|
||||||
|
return nil
|
||||||
|
default: // Handle error
|
||||||
|
return err
|
||||||
|
case nil: // Proceed below
|
||||||
|
}
|
||||||
|
|
||||||
|
info := hdr.FileInfo()
|
||||||
|
// Skip directories
|
||||||
|
if info.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := w.WriteHeader(hdr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyZip(zw zipWriter, r zipReader) error {
|
||||||
|
for _, f := range r.Files() {
|
||||||
|
if err := copyZipFile(zw, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyZipFile(zw zipWriter, f *zip.File) error {
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close() // Read-only
|
||||||
|
|
||||||
|
hdr := f.FileHeader
|
||||||
|
hdr.SetModTime(time.Now())
|
||||||
|
w, err := zw.CreateHeader(&hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, rc)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Package gzip implements reading and writing of gzip format compressed files.
|
||||||
|
// See the compress/gzip package for more details.
|
||||||
|
package gzip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is an io.Reader that can be read to retrieve uncompressed data from a
|
||||||
|
// gzip-format compressed file.
|
||||||
|
type Reader struct {
|
||||||
|
gzip.Reader
|
||||||
|
name string
|
||||||
|
isEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates a new Reader reading the given reader.
|
||||||
|
func NewReader(r io.Reader, name string) (*Reader, error) {
|
||||||
|
gr, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Reader{
|
||||||
|
Reader: *gr,
|
||||||
|
name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile returns the file name. Calls subsequent to the first call will
|
||||||
|
// return EOF.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
if r.isEOF {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
r.isEOF = true
|
||||||
|
return r.name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is an io.WriteCloser. Writes to a Writer are compressed and written to w.
|
||||||
|
type Writer struct {
|
||||||
|
gzip.Writer
|
||||||
|
name string
|
||||||
|
noMoreFiles bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile never returns a next file, and should not be called more than once.
|
||||||
|
func (w *Writer) NextFile(name string, _ os.FileInfo) error {
|
||||||
|
if w.noMoreFiles {
|
||||||
|
return fmt.Errorf("gzip: only accepts one file: already received %q and now %q", w.name, name)
|
||||||
|
}
|
||||||
|
w.noMoreFiles = true
|
||||||
|
w.name = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer. Writes to the returned writer are compressed
|
||||||
|
// and written to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *gzip.NewWriter(w)}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader provides sequential access to the contents of a tar archive.
|
||||||
|
type Reader struct {
|
||||||
|
tar.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates a new Reader reading from r.
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{Reader: *tar.NewReader(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile advances to the next file in the tar archive.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
hdr, err := r.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hdr.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||||
|
type Writer struct {
|
||||||
|
tar.Writer
|
||||||
|
closers []io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter creates a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *tar.NewWriter(w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriteMultiCloser creates a new Writer writing to w that also closes all
|
||||||
|
// closers in order on close.
|
||||||
|
func NewWriteMultiCloser(w io.WriteCloser, closers ...io.Closer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
Writer: *tar.NewWriter(w),
|
||||||
|
closers: closers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile computes and writes a header and prepares to accept the file's
|
||||||
|
// contents.
|
||||||
|
func (w *Writer) NextFile(name string, fi os.FileInfo) error {
|
||||||
|
if name == "" {
|
||||||
|
name = fi.Name()
|
||||||
|
}
|
||||||
|
hdr, err := tar.FileInfoHeader(fi, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Name = name
|
||||||
|
return w.WriteHeader(hdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the tar archive and all other closers, flushing any unwritten
|
||||||
|
// data to the underlying writer.
|
||||||
|
func (w *Writer) Close() error {
|
||||||
|
err := w.Writer.Close()
|
||||||
|
for _, c := range w.closers {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package zip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader provides sequential access to the contents of a zip archive.
|
||||||
|
type Reader struct {
|
||||||
|
zip.Reader
|
||||||
|
unread []*zip.File
|
||||||
|
rc io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new Reader reading from r, which is assumed to have the
|
||||||
|
// given size in bytes.
|
||||||
|
func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
|
||||||
|
zr, err := zip.NewReader(r, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Reader{Reader: *zr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile advances to the next file in the zip archive.
|
||||||
|
func (r *Reader) NextFile() (name string, err error) {
|
||||||
|
// Initialize unread
|
||||||
|
if r.unread == nil {
|
||||||
|
r.unread = r.Files()[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close previous file
|
||||||
|
if r.rc != nil {
|
||||||
|
r.rc.Close() // Read-only
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.unread) == 0 {
|
||||||
|
return "", io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open and return next unread
|
||||||
|
f := r.unread[0]
|
||||||
|
name, r.unread = f.Name, r.unread[1:]
|
||||||
|
r.rc, err = f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||||
|
return r.rc.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files returns the full list of files in the zip archive.
|
||||||
|
func (r *Reader) Files() []*zip.File {
|
||||||
|
return r.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer provides sequential writing of a zip archive.1 format.
|
||||||
|
type Writer struct {
|
||||||
|
zip.Writer
|
||||||
|
w io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{Writer: *zip.NewWriter(w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextFile computes and writes a header and prepares to accept the file's
|
||||||
|
// contents.
|
||||||
|
func (w *Writer) NextFile(name string, fi os.FileInfo) error {
|
||||||
|
if name == "" {
|
||||||
|
name = fi.Name()
|
||||||
|
}
|
||||||
|
hdr, err := zip.FileInfoHeader(fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Name = name
|
||||||
|
w.w, err = w.CreateHeader(hdr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||||
|
return w.w.Write(p)
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
adaptiveLoggerMaxInterval = time.Minute
|
||||||
|
adaptiveLoggerMaxCriticalMsgCount = uint32(1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncAdaptiveLogger represents asynchronous adaptive logger which acts like
|
||||||
|
// an async timer logger, but its interval depends on the current message count
|
||||||
|
// in the queue.
|
||||||
|
//
|
||||||
|
// Interval = I, minInterval = m, maxInterval = M, criticalMsgCount = C, msgCount = c:
|
||||||
|
// I = m + (C - Min(c, C)) / C * (M - m)
|
||||||
|
type asyncAdaptiveLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
minInterval time.Duration
|
||||||
|
criticalMsgCount uint32
|
||||||
|
maxInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous adaptive logger
|
||||||
|
func NewAsyncAdaptiveLogger(
|
||||||
|
config *logConfig,
|
||||||
|
minInterval time.Duration,
|
||||||
|
maxInterval time.Duration,
|
||||||
|
criticalMsgCount uint32) (*asyncAdaptiveLogger, error) {
|
||||||
|
|
||||||
|
if minInterval <= 0 {
|
||||||
|
return nil, errors.New("async adaptive logger min interval should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxInterval > adaptiveLoggerMaxInterval {
|
||||||
|
return nil, fmt.Errorf("async adaptive logger max interval should be <= %s",
|
||||||
|
adaptiveLoggerMaxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if criticalMsgCount <= 0 {
|
||||||
|
return nil, errors.New("async adaptive logger critical msg count should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if criticalMsgCount > adaptiveLoggerMaxCriticalMsgCount {
|
||||||
|
return nil, fmt.Errorf("async adaptive logger critical msg count should be <= %s",
|
||||||
|
adaptiveLoggerMaxInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
asnAdaptiveLogger := new(asyncAdaptiveLogger)
|
||||||
|
|
||||||
|
asnAdaptiveLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
asnAdaptiveLogger.minInterval = minInterval
|
||||||
|
asnAdaptiveLogger.maxInterval = maxInterval
|
||||||
|
asnAdaptiveLogger.criticalMsgCount = criticalMsgCount
|
||||||
|
|
||||||
|
go asnAdaptiveLogger.processQueue()
|
||||||
|
|
||||||
|
return asnAdaptiveLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) processItem() (closed bool, itemCount int) {
|
||||||
|
asnAdaptiveLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnAdaptiveLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnAdaptiveLogger.msgQueue.Len() == 0 && !asnAdaptiveLogger.Closed() {
|
||||||
|
asnAdaptiveLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnAdaptiveLogger.Closed() {
|
||||||
|
return true, asnAdaptiveLogger.msgQueue.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
asnAdaptiveLogger.processQueueElement()
|
||||||
|
return false, asnAdaptiveLogger.msgQueue.Len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// I = m + (C - Min(c, C)) / C * (M - m) =>
|
||||||
|
// I = m + cDiff * mDiff,
|
||||||
|
// cDiff = (C - Min(c, C)) / C)
|
||||||
|
// mDiff = (M - m)
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) calcAdaptiveInterval(msgCount int) time.Duration {
|
||||||
|
critCountF := float64(asnAdaptiveLogger.criticalMsgCount)
|
||||||
|
cDiff := (critCountF - math.Min(float64(msgCount), critCountF)) / critCountF
|
||||||
|
mDiff := float64(asnAdaptiveLogger.maxInterval - asnAdaptiveLogger.minInterval)
|
||||||
|
|
||||||
|
return asnAdaptiveLogger.minInterval + time.Duration(cDiff*mDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnAdaptiveLogger *asyncAdaptiveLogger) processQueue() {
|
||||||
|
for !asnAdaptiveLogger.Closed() {
|
||||||
|
closed, itemCount := asnAdaptiveLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
interval := asnAdaptiveLogger.calcAdaptiveInterval(itemCount)
|
||||||
|
|
||||||
|
<-time.After(interval)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxQueueSize is the critical number of messages in the queue that result in an immediate flush.
|
||||||
|
const (
|
||||||
|
MaxQueueSize = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgQueueItem struct {
|
||||||
|
level LogLevel
|
||||||
|
context LogContextInterface
|
||||||
|
message fmt.Stringer
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncLogger represents common data for all asynchronous loggers
|
||||||
|
type asyncLogger struct {
|
||||||
|
commonLogger
|
||||||
|
msgQueue *list.List
|
||||||
|
queueHasElements *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAsyncLogger creates a new asynchronous logger
|
||||||
|
func newAsyncLogger(config *logConfig) *asyncLogger {
|
||||||
|
asnLogger := new(asyncLogger)
|
||||||
|
|
||||||
|
asnLogger.msgQueue = list.New()
|
||||||
|
asnLogger.queueHasElements = sync.NewCond(new(sync.Mutex))
|
||||||
|
|
||||||
|
asnLogger.commonLogger = *newCommonLogger(config, asnLogger)
|
||||||
|
|
||||||
|
return asnLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) innerLog(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
asnLogger.addMsgToQueue(level, context, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) Close() {
|
||||||
|
asnLogger.m.Lock()
|
||||||
|
defer asnLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.flushQueue(true)
|
||||||
|
asnLogger.config.RootDispatcher.Flush()
|
||||||
|
|
||||||
|
if err := asnLogger.config.RootDispatcher.Close(); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
asnLogger.closedM.Lock()
|
||||||
|
asnLogger.closed = true
|
||||||
|
asnLogger.closedM.Unlock()
|
||||||
|
asnLogger.queueHasElements.Broadcast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) Flush() {
|
||||||
|
asnLogger.m.Lock()
|
||||||
|
defer asnLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.flushQueue(true)
|
||||||
|
asnLogger.config.RootDispatcher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) flushQueue(lockNeeded bool) {
|
||||||
|
if lockNeeded {
|
||||||
|
asnLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLogger.queueHasElements.L.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for asnLogger.msgQueue.Len() > 0 {
|
||||||
|
asnLogger.processQueueElement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) processQueueElement() {
|
||||||
|
if asnLogger.msgQueue.Len() > 0 {
|
||||||
|
backElement := asnLogger.msgQueue.Front()
|
||||||
|
msg, _ := backElement.Value.(msgQueueItem)
|
||||||
|
asnLogger.processLogMsg(msg.level, msg.message, msg.context)
|
||||||
|
asnLogger.msgQueue.Remove(backElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLogger *asyncLogger) addMsgToQueue(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
if !asnLogger.Closed() {
|
||||||
|
asnLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
if asnLogger.msgQueue.Len() >= MaxQueueSize {
|
||||||
|
fmt.Printf("Seelog queue overflow: more than %v messages in the queue. Flushing.\n", MaxQueueSize)
|
||||||
|
asnLogger.flushQueue(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueItem := msgQueueItem{level, context, message}
|
||||||
|
|
||||||
|
asnLogger.msgQueue.PushBack(queueItem)
|
||||||
|
asnLogger.queueHasElements.Broadcast()
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("queue closed! Cannot process element: %d %#v", level, message)
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// asyncLoopLogger represents asynchronous logger which processes the log queue in
|
||||||
|
// a 'for' loop
|
||||||
|
type asyncLoopLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||||
|
func NewAsyncLoopLogger(config *logConfig) *asyncLoopLogger {
|
||||||
|
|
||||||
|
asnLoopLogger := new(asyncLoopLogger)
|
||||||
|
|
||||||
|
asnLoopLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
|
||||||
|
go asnLoopLogger.processQueue()
|
||||||
|
|
||||||
|
return asnLoopLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLoopLogger *asyncLoopLogger) processItem() (closed bool) {
|
||||||
|
asnLoopLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnLoopLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnLoopLogger.msgQueue.Len() == 0 && !asnLoopLogger.Closed() {
|
||||||
|
asnLoopLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnLoopLogger.Closed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
asnLoopLogger.processQueueElement()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnLoopLogger *asyncLoopLogger) processQueue() {
|
||||||
|
for !asnLoopLogger.Closed() {
|
||||||
|
closed := asnLoopLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncTimerLogger represents asynchronous logger which processes the log queue each
|
||||||
|
// 'duration' nanoseconds
|
||||||
|
type asyncTimerLogger struct {
|
||||||
|
asyncLogger
|
||||||
|
interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAsyncLoopLogger creates a new asynchronous loop logger
|
||||||
|
func NewAsyncTimerLogger(config *logConfig, interval time.Duration) (*asyncTimerLogger, error) {
|
||||||
|
|
||||||
|
if interval <= 0 {
|
||||||
|
return nil, errors.New("async logger interval should be > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
asnTimerLogger := new(asyncTimerLogger)
|
||||||
|
|
||||||
|
asnTimerLogger.asyncLogger = *newAsyncLogger(config)
|
||||||
|
asnTimerLogger.interval = interval
|
||||||
|
|
||||||
|
go asnTimerLogger.processQueue()
|
||||||
|
|
||||||
|
return asnTimerLogger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnTimerLogger *asyncTimerLogger) processItem() (closed bool) {
|
||||||
|
asnTimerLogger.queueHasElements.L.Lock()
|
||||||
|
defer asnTimerLogger.queueHasElements.L.Unlock()
|
||||||
|
|
||||||
|
for asnTimerLogger.msgQueue.Len() == 0 && !asnTimerLogger.Closed() {
|
||||||
|
asnTimerLogger.queueHasElements.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if asnTimerLogger.Closed() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
asnTimerLogger.processQueueElement()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (asnTimerLogger *asyncTimerLogger) processQueue() {
|
||||||
|
for !asnTimerLogger.Closed() {
|
||||||
|
closed := asnTimerLogger.processItem()
|
||||||
|
|
||||||
|
if closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(asnTimerLogger.interval)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// syncLogger performs logging in the same goroutine where 'Trace/Debug/...'
|
||||||
|
// func was called
|
||||||
|
type syncLogger struct {
|
||||||
|
commonLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSyncLogger creates a new synchronous logger
|
||||||
|
func NewSyncLogger(config *logConfig) *syncLogger {
|
||||||
|
syncLogger := new(syncLogger)
|
||||||
|
|
||||||
|
syncLogger.commonLogger = *newCommonLogger(config, syncLogger)
|
||||||
|
|
||||||
|
return syncLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) innerLog(
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
message fmt.Stringer) {
|
||||||
|
|
||||||
|
syncLogger.processLogMsg(level, message, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) Close() {
|
||||||
|
syncLogger.m.Lock()
|
||||||
|
defer syncLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !syncLogger.Closed() {
|
||||||
|
if err := syncLogger.config.RootDispatcher.Close(); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
syncLogger.closedM.Lock()
|
||||||
|
syncLogger.closed = true
|
||||||
|
syncLogger.closedM.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncLogger *syncLogger) Flush() {
|
||||||
|
syncLogger.m.Lock()
|
||||||
|
defer syncLogger.m.Unlock()
|
||||||
|
|
||||||
|
if !syncLogger.Closed() {
|
||||||
|
syncLogger.config.RootDispatcher.Flush()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggerFromConfigAsFile creates logger with config from file. File should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsFile(fileName string) (LoggerInterface, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
conf, err := configFromReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromConfigAsBytes creates a logger with config from bytes stream. Bytes should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsBytes(data []byte) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromConfigAsString creates a logger with config from a string. String should contain valid seelog xml.
|
||||||
|
func LoggerFromConfigAsString(data string) (LoggerInterface, error) {
|
||||||
|
return LoggerFromConfigAsBytes([]byte(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsFile does the same as LoggerFromConfigAsFile, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsFile(fileName string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
conf, err := configFromReaderWithConfig(file, parserParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsBytes does the same as LoggerFromConfigAsBytes, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsBytes(data []byte, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromReaderWithConfig(bytes.NewBuffer(data), parserParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromParamConfigAsString does the same as LoggerFromConfigAsString, but includes special parser options.
|
||||||
|
// See 'CfgParseParams' comments.
|
||||||
|
func LoggerFromParamConfigAsString(data string, parserParams *CfgParseParams) (LoggerInterface, error) {
|
||||||
|
return LoggerFromParamConfigAsBytes([]byte(data), parserParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromWriterWithMinLevel is shortcut for LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||||
|
func LoggerFromWriterWithMinLevel(output io.Writer, minLevel LogLevel) (LoggerInterface, error) {
|
||||||
|
return LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromWriterWithMinLevelAndFormat creates a proxy logger that uses io.Writer as the
|
||||||
|
// receiver with minimal level = minLevel and with specified format.
|
||||||
|
//
|
||||||
|
// All messages with level more or equal to minLevel will be written to output and
|
||||||
|
// formatted using the default seelog format.
|
||||||
|
//
|
||||||
|
// Can be called for usage with non-Seelog systems
|
||||||
|
func LoggerFromWriterWithMinLevelAndFormat(output io.Writer, minLevel LogLevel, format string) (LoggerInterface, error) {
|
||||||
|
constraints, err := NewMinMaxConstraints(minLevel, CriticalLvl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
formatter, err := NewFormatter(format)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dispatcher, err := NewSplitDispatcher(formatter, []interface{}{output})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromXMLDecoder creates logger with config from a XML decoder starting from a specific node.
|
||||||
|
// It should contain valid seelog xml, except for root node name.
|
||||||
|
func LoggerFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (LoggerInterface, error) {
|
||||||
|
conf, err := configFromXMLDecoder(xmlParser, rootNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerFromCustomReceiver creates a proxy logger that uses a CustomReceiver as the
|
||||||
|
// receiver.
|
||||||
|
//
|
||||||
|
// All messages will be sent to the specified custom receiver without additional
|
||||||
|
// formatting ('%Msg' format is used).
|
||||||
|
//
|
||||||
|
// Check CustomReceiver, RegisterReceiver for additional info.
|
||||||
|
//
|
||||||
|
// NOTE 1: CustomReceiver.AfterParse is only called when a receiver is instantiated
|
||||||
|
// by the config parser while parsing config. So, if you are not planning to use the
|
||||||
|
// same CustomReceiver for both proxying (via LoggerFromCustomReceiver call) and
|
||||||
|
// loading from config, just leave AfterParse implementation empty.
|
||||||
|
//
|
||||||
|
// NOTE 2: Unlike RegisterReceiver, LoggerFromCustomReceiver takes an already initialized
|
||||||
|
// instance that implements CustomReceiver. So, fill it with data and perform any initialization
|
||||||
|
// logic before calling this func and it won't be lost.
|
||||||
|
//
|
||||||
|
// So:
|
||||||
|
// * RegisterReceiver takes value just to get the reflect.Type from it and then
|
||||||
|
// instantiate it as many times as config is reloaded.
|
||||||
|
//
|
||||||
|
// * LoggerFromCustomReceiver takes value and uses it without modification and
|
||||||
|
// reinstantiation, directy passing it to the dispatcher tree.
|
||||||
|
func LoggerFromCustomReceiver(receiver CustomReceiver) (LoggerInterface, error) {
|
||||||
|
constraints, err := NewMinMaxConstraints(TraceLvl, CriticalLvl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := NewCustomReceiverDispatcherByValue(msgonlyformatter, receiver, "user-proxy", CustomReceiverInitArgs{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dispatcher, err := NewSplitDispatcher(msgonlyformatter, []interface{}{output})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createLoggerFromFullConfig(conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloneLogger(logger LoggerInterface) (LoggerInterface, error) {
|
||||||
|
switch logger := logger.(type) {
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected type %T", logger)
|
||||||
|
case *asyncAdaptiveLogger:
|
||||||
|
clone, err := NewAsyncAdaptiveLogger(logger.commonLogger.config, logger.minInterval, logger.maxInterval, logger.criticalMsgCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clone, nil
|
||||||
|
case *asyncLoopLogger:
|
||||||
|
return NewAsyncLoopLogger(logger.commonLogger.config), nil
|
||||||
|
case *asyncTimerLogger:
|
||||||
|
clone, err := NewAsyncTimerLogger(logger.commonLogger.config, logger.interval)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clone, nil
|
||||||
|
case *syncLogger:
|
||||||
|
return NewSyncLogger(logger.commonLogger.config), nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNodeMustHaveChildren = errors.New("node must have children")
|
||||||
|
errNodeCannotHaveChildren = errors.New("node cannot have children")
|
||||||
|
)
|
||||||
|
|
||||||
|
type unexpectedChildElementError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnexpectedChildElementError(msg string) *unexpectedChildElementError {
|
||||||
|
custmsg := "Unexpected child element: " + msg
|
||||||
|
return &unexpectedChildElementError{baseError{message: custmsg}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type missingArgumentError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMissingArgumentError(nodeName, attrName string) *missingArgumentError {
|
||||||
|
custmsg := "Output '" + nodeName + "' has no '" + attrName + "' attribute"
|
||||||
|
return &missingArgumentError{baseError{message: custmsg}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedAttributeError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnexpectedAttributeError(nodeName, attr string) *unexpectedAttributeError {
|
||||||
|
custmsg := nodeName + " has unexpected attribute: " + attr
|
||||||
|
return &unexpectedAttributeError{baseError{message: custmsg}}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type loggerTypeFromString uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncloggerTypeFromString = iota
|
||||||
|
asyncLooploggerTypeFromString
|
||||||
|
asyncTimerloggerTypeFromString
|
||||||
|
adaptiveLoggerTypeFromString
|
||||||
|
defaultloggerTypeFromString = asyncLooploggerTypeFromString
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
syncloggerTypeFromStringStr = "sync"
|
||||||
|
asyncloggerTypeFromStringStr = "asyncloop"
|
||||||
|
asyncTimerloggerTypeFromStringStr = "asynctimer"
|
||||||
|
adaptiveLoggerTypeFromStringStr = "adaptive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// asyncTimerLoggerData represents specific data for async timer logger
|
||||||
|
type asyncTimerLoggerData struct {
|
||||||
|
AsyncInterval uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// adaptiveLoggerData represents specific data for adaptive timer logger
|
||||||
|
type adaptiveLoggerData struct {
|
||||||
|
MinInterval uint32
|
||||||
|
MaxInterval uint32
|
||||||
|
CriticalMsgCount uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var loggerTypeToStringRepresentations = map[loggerTypeFromString]string{
|
||||||
|
syncloggerTypeFromString: syncloggerTypeFromStringStr,
|
||||||
|
asyncLooploggerTypeFromString: asyncloggerTypeFromStringStr,
|
||||||
|
asyncTimerloggerTypeFromString: asyncTimerloggerTypeFromStringStr,
|
||||||
|
adaptiveLoggerTypeFromString: adaptiveLoggerTypeFromStringStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLoggerTypeFromString parses a string and returns a corresponding logger type, if successful.
|
||||||
|
func getLoggerTypeFromString(logTypeString string) (level loggerTypeFromString, found bool) {
|
||||||
|
for logType, logTypeStr := range loggerTypeToStringRepresentations {
|
||||||
|
if logTypeStr == logTypeString {
|
||||||
|
return logType, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// logConfig stores logging configuration. Contains messages dispatcher, allowed log level rules
|
||||||
|
// (general constraints and exceptions)
|
||||||
|
type logConfig struct {
|
||||||
|
Constraints logLevelConstraints // General log level rules (>min and <max, or set of allowed levels)
|
||||||
|
Exceptions []*LogLevelException // Exceptions to general rules for specific files or funcs
|
||||||
|
RootDispatcher dispatcherInterface // Root of output tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoggerConfig(c logLevelConstraints, e []*LogLevelException, d dispatcherInterface) *logConfig {
|
||||||
|
return &logConfig{c, e, d}
|
||||||
|
}
|
||||||
|
|
||||||
|
// configForParsing is used when parsing config from file: logger type is deduced from string, params
|
||||||
|
// need to be converted from attributes to values and passed to specific logger constructor. Also,
|
||||||
|
// custom registered receivers and other parse params are used in this case.
|
||||||
|
type configForParsing struct {
|
||||||
|
logConfig
|
||||||
|
LogType loggerTypeFromString
|
||||||
|
LoggerData interface{}
|
||||||
|
Params *CfgParseParams // Check cfg_parser: CfgParseParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFullLoggerConfig(
|
||||||
|
constraints logLevelConstraints,
|
||||||
|
exceptions []*LogLevelException,
|
||||||
|
rootDispatcher dispatcherInterface,
|
||||||
|
logType loggerTypeFromString,
|
||||||
|
logData interface{},
|
||||||
|
cfgParams *CfgParseParams) (*configForParsing, error) {
|
||||||
|
if constraints == nil {
|
||||||
|
return nil, errors.New("constraints can not be nil")
|
||||||
|
}
|
||||||
|
if rootDispatcher == nil {
|
||||||
|
return nil, errors.New("rootDispatcher can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := new(configForParsing)
|
||||||
|
config.Constraints = constraints
|
||||||
|
config.Exceptions = exceptions
|
||||||
|
config.RootDispatcher = rootDispatcher
|
||||||
|
config.LogType = logType
|
||||||
|
config.LoggerData = logData
|
||||||
|
config.Params = cfgParams
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true if logging with specified log level is allowed in current context.
|
||||||
|
// If any of exception patterns match current context, then exception constraints are applied. Otherwise,
|
||||||
|
// the general constraints are used.
|
||||||
|
func (config *logConfig) IsAllowed(level LogLevel, context LogContextInterface) bool {
|
||||||
|
allowed := config.Constraints.IsAllowed(level) // General rule
|
||||||
|
|
||||||
|
// Exceptions:
|
||||||
|
if context.IsValid() {
|
||||||
|
for _, exception := range config.Exceptions {
|
||||||
|
if exception.MatchesContext(context) {
|
||||||
|
return exception.IsAllowed(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowed
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Represents constraints which form a general rule for log levels selection
|
||||||
|
type logLevelConstraints interface {
|
||||||
|
IsAllowed(level LogLevel) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// A minMaxConstraints represents constraints which use minimal and maximal allowed log levels.
|
||||||
|
type minMaxConstraints struct {
|
||||||
|
min LogLevel
|
||||||
|
max LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMinMaxConstraints creates a new minMaxConstraints struct with the specified min and max levels.
|
||||||
|
func NewMinMaxConstraints(min LogLevel, max LogLevel) (*minMaxConstraints, error) {
|
||||||
|
if min > max {
|
||||||
|
return nil, fmt.Errorf("min level can't be greater than max. Got min: %d, max: %d", min, max)
|
||||||
|
}
|
||||||
|
if min < TraceLvl || min > CriticalLvl {
|
||||||
|
return nil, fmt.Errorf("min level can't be less than Trace or greater than Critical. Got min: %d", min)
|
||||||
|
}
|
||||||
|
if max < TraceLvl || max > CriticalLvl {
|
||||||
|
return nil, fmt.Errorf("max level can't be less than Trace or greater than Critical. Got max: %d", max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &minMaxConstraints{min, max}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true, if log level is in [min, max] range (inclusive).
|
||||||
|
func (minMaxConstr *minMaxConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
return level >= minMaxConstr.min && level <= minMaxConstr.max
|
||||||
|
}
|
||||||
|
|
||||||
|
func (minMaxConstr *minMaxConstraints) String() string {
|
||||||
|
return fmt.Sprintf("Min: %s. Max: %s", minMaxConstr.min, minMaxConstr.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================
|
||||||
|
|
||||||
|
// A listConstraints represents constraints which use allowed log levels list.
|
||||||
|
type listConstraints struct {
|
||||||
|
allowedLevels map[LogLevel]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListConstraints creates a new listConstraints struct with the specified allowed levels.
|
||||||
|
func NewListConstraints(allowList []LogLevel) (*listConstraints, error) {
|
||||||
|
if allowList == nil {
|
||||||
|
return nil, errors.New("list can't be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
allowLevels, err := createMapFromList(allowList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = validateOffLevel(allowLevels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listConstraints{allowLevels}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (listConstr *listConstraints) String() string {
|
||||||
|
allowedList := "List: "
|
||||||
|
|
||||||
|
listLevel := make([]string, len(listConstr.allowedLevels))
|
||||||
|
|
||||||
|
var logLevel LogLevel
|
||||||
|
i := 0
|
||||||
|
for logLevel = TraceLvl; logLevel <= Off; logLevel++ {
|
||||||
|
if listConstr.allowedLevels[logLevel] {
|
||||||
|
listLevel[i] = logLevel.String()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedList += strings.Join(listLevel, ",")
|
||||||
|
|
||||||
|
return allowedList
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMapFromList(allowedList []LogLevel) (map[LogLevel]bool, error) {
|
||||||
|
allowedLevels := make(map[LogLevel]bool, 0)
|
||||||
|
for _, level := range allowedList {
|
||||||
|
if level < TraceLvl || level > Off {
|
||||||
|
return nil, fmt.Errorf("level can't be less than Trace or greater than Critical. Got level: %d", level)
|
||||||
|
}
|
||||||
|
allowedLevels[level] = true
|
||||||
|
}
|
||||||
|
return allowedLevels, nil
|
||||||
|
}
|
||||||
|
func validateOffLevel(allowedLevels map[LogLevel]bool) error {
|
||||||
|
if _, ok := allowedLevels[Off]; ok && len(allowedLevels) > 1 {
|
||||||
|
return errors.New("logLevel Off cant be mixed with other levels")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true, if log level is in allowed log levels list.
|
||||||
|
// If the list contains the only item 'common.Off' then IsAllowed will always return false for any input values.
|
||||||
|
func (listConstr *listConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
for l := range listConstr.allowedLevels {
|
||||||
|
if l == level && level != Off {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowedLevels returns allowed levels configuration as a map.
|
||||||
|
func (listConstr *listConstraints) AllowedLevels() map[LogLevel]bool {
|
||||||
|
return listConstr.allowedLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
//=======================================================
|
||||||
|
|
||||||
|
type offConstraints struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOffConstraints() (*offConstraints, error) {
|
||||||
|
return &offConstraints{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (offConstr *offConstraints) IsAllowed(level LogLevel) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (offConstr *offConstraints) String() string {
|
||||||
|
return "Off constraint"
|
||||||
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
workingDir = "/"
|
||||||
|
stackCache map[uintptr]*logContext
|
||||||
|
stackCacheLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err == nil {
|
||||||
|
workingDir = filepath.ToSlash(wd) + "/"
|
||||||
|
}
|
||||||
|
stackCache = make(map[uintptr]*logContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents runtime caller context.
|
||||||
|
type LogContextInterface interface {
|
||||||
|
// Caller's function name.
|
||||||
|
Func() string
|
||||||
|
// Caller's line number.
|
||||||
|
Line() int
|
||||||
|
// Caller's file short path (in slashed form).
|
||||||
|
ShortPath() string
|
||||||
|
// Caller's file full path (in slashed form).
|
||||||
|
FullPath() string
|
||||||
|
// Caller's file name (without path).
|
||||||
|
FileName() string
|
||||||
|
// True if the context is correct and may be used.
|
||||||
|
// If false, then an error in context evaluation occurred and
|
||||||
|
// all its other data may be corrupted.
|
||||||
|
IsValid() bool
|
||||||
|
// Time when log function was called.
|
||||||
|
CallTime() time.Time
|
||||||
|
// Custom context that can be set by calling logger.SetContext
|
||||||
|
CustomContext() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns context of the caller
|
||||||
|
func currentContext(custom interface{}) (LogContextInterface, error) {
|
||||||
|
return specifyContext(1, custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractCallerInfo(skip int) (*logContext, error) {
|
||||||
|
var stack [1]uintptr
|
||||||
|
if runtime.Callers(skip+1, stack[:]) != 1 {
|
||||||
|
return nil, errors.New("error during runtime.Callers")
|
||||||
|
}
|
||||||
|
pc := stack[0]
|
||||||
|
|
||||||
|
// do we have a cache entry?
|
||||||
|
stackCacheLock.RLock()
|
||||||
|
ctx, ok := stackCache[pc]
|
||||||
|
stackCacheLock.RUnlock()
|
||||||
|
if ok {
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// look up the details of the given caller
|
||||||
|
funcInfo := runtime.FuncForPC(pc)
|
||||||
|
if funcInfo == nil {
|
||||||
|
return nil, errors.New("error during runtime.FuncForPC")
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortPath string
|
||||||
|
fullPath, line := funcInfo.FileLine(pc)
|
||||||
|
if strings.HasPrefix(fullPath, workingDir) {
|
||||||
|
shortPath = fullPath[len(workingDir):]
|
||||||
|
} else {
|
||||||
|
shortPath = fullPath
|
||||||
|
}
|
||||||
|
funcName := funcInfo.Name()
|
||||||
|
if strings.HasPrefix(funcName, workingDir) {
|
||||||
|
funcName = funcName[len(workingDir):]
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = &logContext{
|
||||||
|
funcName: funcName,
|
||||||
|
line: line,
|
||||||
|
shortPath: shortPath,
|
||||||
|
fullPath: fullPath,
|
||||||
|
fileName: filepath.Base(fullPath),
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the details in the cache; note that it's possible we might
|
||||||
|
// have written an entry into the map in between the test above and
|
||||||
|
// this section, but the behaviour is still correct
|
||||||
|
stackCacheLock.Lock()
|
||||||
|
stackCache[pc] = ctx
|
||||||
|
stackCacheLock.Unlock()
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns context of the function with placed "skip" stack frames of the caller
|
||||||
|
// If skip == 0 then behaves like currentContext
|
||||||
|
// Context is returned in any situation, even if error occurs. But, if an error
|
||||||
|
// occurs, the returned context is an error context, which contains no paths
|
||||||
|
// or names, but states that they can't be extracted.
|
||||||
|
func specifyContext(skip int, custom interface{}) (LogContextInterface, error) {
|
||||||
|
callTime := time.Now()
|
||||||
|
if skip < 0 {
|
||||||
|
err := fmt.Errorf("can not skip negative stack frames")
|
||||||
|
return &errorContext{callTime, err}, err
|
||||||
|
}
|
||||||
|
caller, err := extractCallerInfo(skip + 2)
|
||||||
|
if err != nil {
|
||||||
|
return &errorContext{callTime, err}, err
|
||||||
|
}
|
||||||
|
ctx := new(logContext)
|
||||||
|
*ctx = *caller
|
||||||
|
ctx.callTime = callTime
|
||||||
|
ctx.custom = custom
|
||||||
|
return ctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents a normal runtime caller context.
|
||||||
|
type logContext struct {
|
||||||
|
funcName string
|
||||||
|
line int
|
||||||
|
shortPath string
|
||||||
|
fullPath string
|
||||||
|
fileName string
|
||||||
|
callTime time.Time
|
||||||
|
custom interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) IsValid() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) Func() string {
|
||||||
|
return context.funcName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) Line() int {
|
||||||
|
return context.line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) ShortPath() string {
|
||||||
|
return context.shortPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) FullPath() string {
|
||||||
|
return context.fullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) FileName() string {
|
||||||
|
return context.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) CallTime() time.Time {
|
||||||
|
return context.callTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context *logContext) CustomContext() interface{} {
|
||||||
|
return context.custom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Represents an error context
|
||||||
|
type errorContext struct {
|
||||||
|
errorTime time.Time
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) getErrorText(prefix string) string {
|
||||||
|
return fmt.Sprintf("%s() error: %s", prefix, errContext.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) IsValid() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) Line() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) Func() string {
|
||||||
|
return errContext.getErrorText("Func")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) ShortPath() string {
|
||||||
|
return errContext.getErrorText("ShortPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) FullPath() string {
|
||||||
|
return errContext.getErrorText("FullPath")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) FileName() string {
|
||||||
|
return errContext.getErrorText("FileName")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) CallTime() time.Time {
|
||||||
|
return errContext.errorTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (errContext *errorContext) CustomContext() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Used in rules creation to validate input file and func filters
|
||||||
|
var (
|
||||||
|
fileFormatValidator = regexp.MustCompile(`[a-zA-Z0-9\\/ _\*\.]*`)
|
||||||
|
funcFormatValidator = regexp.MustCompile(`[a-zA-Z0-9_\*\.]*`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogLevelException represents an exceptional case used when you need some specific files or funcs to
|
||||||
|
// override general constraints and to use their own.
|
||||||
|
type LogLevelException struct {
|
||||||
|
funcPatternParts []string
|
||||||
|
filePatternParts []string
|
||||||
|
|
||||||
|
funcPattern string
|
||||||
|
filePattern string
|
||||||
|
|
||||||
|
constraints logLevelConstraints
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogLevelException creates a new exception.
|
||||||
|
func NewLogLevelException(funcPattern string, filePattern string, constraints logLevelConstraints) (*LogLevelException, error) {
|
||||||
|
if constraints == nil {
|
||||||
|
return nil, errors.New("constraints can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(LogLevelException)
|
||||||
|
|
||||||
|
err := exception.initFuncPatternParts(funcPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exception.funcPattern = strings.Join(exception.funcPatternParts, "")
|
||||||
|
|
||||||
|
err = exception.initFilePatternParts(filePattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
exception.filePattern = strings.Join(exception.filePatternParts, "")
|
||||||
|
|
||||||
|
exception.constraints = constraints
|
||||||
|
|
||||||
|
return exception, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchesContext returns true if context matches the patterns of this LogLevelException
|
||||||
|
func (logLevelEx *LogLevelException) MatchesContext(context LogContextInterface) bool {
|
||||||
|
return logLevelEx.match(context.Func(), context.FullPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllowed returns true if log level is allowed according to the constraints of this LogLevelException
|
||||||
|
func (logLevelEx *LogLevelException) IsAllowed(level LogLevel) bool {
|
||||||
|
return logLevelEx.constraints.IsAllowed(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncPattern returns the function pattern of a exception
|
||||||
|
func (logLevelEx *LogLevelException) FuncPattern() string {
|
||||||
|
return logLevelEx.funcPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncPattern returns the file pattern of a exception
|
||||||
|
func (logLevelEx *LogLevelException) FilePattern() string {
|
||||||
|
return logLevelEx.filePattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// initFuncPatternParts checks whether the func filter has a correct format and splits funcPattern on parts
|
||||||
|
func (logLevelEx *LogLevelException) initFuncPatternParts(funcPattern string) (err error) {
|
||||||
|
|
||||||
|
if funcFormatValidator.FindString(funcPattern) != funcPattern {
|
||||||
|
return errors.New("func path \"" + funcPattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 _ * . allowed)")
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelEx.funcPatternParts = splitPattern(funcPattern)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether the file filter has a correct format and splits file patterns using splitPattern.
|
||||||
|
func (logLevelEx *LogLevelException) initFilePatternParts(filePattern string) (err error) {
|
||||||
|
|
||||||
|
if fileFormatValidator.FindString(filePattern) != filePattern {
|
||||||
|
return errors.New("file path \"" + filePattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 \\ / _ * . allowed)")
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelEx.filePatternParts = splitPattern(filePattern)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logLevelEx *LogLevelException) match(funcPath string, filePath string) bool {
|
||||||
|
if !stringMatchesPattern(logLevelEx.funcPatternParts, funcPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return stringMatchesPattern(logLevelEx.filePatternParts, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logLevelEx *LogLevelException) String() string {
|
||||||
|
str := fmt.Sprintf("Func: %s File: %s", logLevelEx.funcPattern, logLevelEx.filePattern)
|
||||||
|
|
||||||
|
if logLevelEx.constraints != nil {
|
||||||
|
str += fmt.Sprintf("Constr: %s", logLevelEx.constraints)
|
||||||
|
} else {
|
||||||
|
str += "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitPattern splits pattern into strings and asterisks. Example: "ab*cde**f" -> ["ab", "*", "cde", "*", "f"]
|
||||||
|
func splitPattern(pattern string) []string {
|
||||||
|
var patternParts []string
|
||||||
|
var lastChar rune
|
||||||
|
for _, char := range pattern {
|
||||||
|
if char == '*' {
|
||||||
|
if lastChar != '*' {
|
||||||
|
patternParts = append(patternParts, "*")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(patternParts) != 0 && lastChar != '*' {
|
||||||
|
patternParts[len(patternParts)-1] += string(char)
|
||||||
|
} else {
|
||||||
|
patternParts = append(patternParts, string(char))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastChar = char
|
||||||
|
}
|
||||||
|
|
||||||
|
return patternParts
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringMatchesPattern check whether testString matches pattern with asterisks.
|
||||||
|
// Standard regexp functionality is not used here because of performance issues.
|
||||||
|
func stringMatchesPattern(patternparts []string, testString string) bool {
|
||||||
|
if len(patternparts) == 0 {
|
||||||
|
return len(testString) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
part := patternparts[0]
|
||||||
|
if part != "*" {
|
||||||
|
index := strings.Index(testString, part)
|
||||||
|
if index == 0 {
|
||||||
|
return stringMatchesPattern(patternparts[1:], testString[len(part):])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(patternparts) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
newTestString := testString
|
||||||
|
part = patternparts[1]
|
||||||
|
for {
|
||||||
|
index := strings.Index(newTestString, part)
|
||||||
|
if index == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
newTestString = newTestString[index+len(part):]
|
||||||
|
result := stringMatchesPattern(patternparts[2:], newTestString)
|
||||||
|
if result {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// flusherInterface represents all objects that have to do cleanup
|
||||||
|
// at certain moments of time (e.g. before app shutdown to avoid data loss)
|
||||||
|
type flusherInterface interface {
|
||||||
|
Flush()
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// Log level type
|
||||||
|
type LogLevel uint8
|
||||||
|
|
||||||
|
// Log levels
|
||||||
|
const (
|
||||||
|
TraceLvl = iota
|
||||||
|
DebugLvl
|
||||||
|
InfoLvl
|
||||||
|
WarnLvl
|
||||||
|
ErrorLvl
|
||||||
|
CriticalLvl
|
||||||
|
Off
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log level string representations (used in configuration files)
|
||||||
|
const (
|
||||||
|
TraceStr = "trace"
|
||||||
|
DebugStr = "debug"
|
||||||
|
InfoStr = "info"
|
||||||
|
WarnStr = "warn"
|
||||||
|
ErrorStr = "error"
|
||||||
|
CriticalStr = "critical"
|
||||||
|
OffStr = "off"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelToStringRepresentations = map[LogLevel]string{
|
||||||
|
TraceLvl: TraceStr,
|
||||||
|
DebugLvl: DebugStr,
|
||||||
|
InfoLvl: InfoStr,
|
||||||
|
WarnLvl: WarnStr,
|
||||||
|
ErrorLvl: ErrorStr,
|
||||||
|
CriticalLvl: CriticalStr,
|
||||||
|
Off: OffStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelFromString parses a string and returns a corresponding log level, if sucessfull.
|
||||||
|
func LogLevelFromString(levelStr string) (level LogLevel, found bool) {
|
||||||
|
for lvl, lvlStr := range levelToStringRepresentations {
|
||||||
|
if lvlStr == levelStr {
|
||||||
|
return lvl, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelToString returns seelog string representation for a specified level. Returns "" for invalid log levels.
|
||||||
|
func (level LogLevel) String() string {
|
||||||
|
levelStr, ok := levelToStringRepresentations[level]
|
||||||
|
if ok {
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registeredReceivers = make(map[string]reflect.Type)
|
||||||
|
|
||||||
|
// RegisterReceiver records a custom receiver type, identified by a value
|
||||||
|
// of that type (second argument), under the specified name. Registered
|
||||||
|
// names can be used in the "name" attribute of <custom> config items.
|
||||||
|
//
|
||||||
|
// RegisterReceiver takes the type of the receiver argument, without taking
|
||||||
|
// the value into the account. So do NOT enter any data to the second argument
|
||||||
|
// and only call it like:
|
||||||
|
// RegisterReceiver("somename", &MyReceiverType{})
|
||||||
|
//
|
||||||
|
// After that, when a '<custom>' config tag with this name is used,
|
||||||
|
// a receiver of the specified type would be instantiated. Check
|
||||||
|
// CustomReceiver comments for interface details.
|
||||||
|
//
|
||||||
|
// NOTE 1: RegisterReceiver fails if you attempt to register different types
|
||||||
|
// with the same name.
|
||||||
|
//
|
||||||
|
// NOTE 2: RegisterReceiver registers those receivers that must be used in
|
||||||
|
// the configuration files (<custom> items). Basically it is just the way
|
||||||
|
// you tell seelog config parser what should it do when it meets a
|
||||||
|
// <custom> tag with a specific name and data attributes.
|
||||||
|
//
|
||||||
|
// But If you are only using seelog as a proxy to an already instantiated
|
||||||
|
// CustomReceiver (via LoggerFromCustomReceiver func), you should not call RegisterReceiver.
|
||||||
|
func RegisterReceiver(name string, receiver CustomReceiver) {
|
||||||
|
newType := reflect.TypeOf(reflect.ValueOf(receiver).Elem().Interface())
|
||||||
|
if t, ok := registeredReceivers[name]; ok && t != newType {
|
||||||
|
panic(fmt.Sprintf("duplicate types for %s: %s != %s", name, t, newType))
|
||||||
|
}
|
||||||
|
registeredReceivers[name] = newType
|
||||||
|
}
|
||||||
|
|
||||||
|
func customReceiverByName(name string) (creceiver CustomReceiver, err error) {
|
||||||
|
rt, ok := registeredReceivers[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("custom receiver name not registered: '%s'", name)
|
||||||
|
}
|
||||||
|
v, ok := reflect.New(rt).Interface().(CustomReceiver)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot instantiate receiver with name='%s'", name)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiverInitArgs represent arguments passed to the CustomReceiver.Init
|
||||||
|
// func when custom receiver is being initialized.
|
||||||
|
type CustomReceiverInitArgs struct {
|
||||||
|
// XmlCustomAttrs represent '<custom>' xml config item attributes that
|
||||||
|
// start with "data-". Map keys will be the attribute names without the "data-".
|
||||||
|
// Map values will the those attribute values.
|
||||||
|
//
|
||||||
|
// E.g. if you have a '<custom name="somename" data-attr1="a1" data-attr2="a2"/>'
|
||||||
|
// you will get map with 2 key-value pairs: "attr1"->"a1", "attr2"->"a2"
|
||||||
|
//
|
||||||
|
// Note that in custom items you can only use allowed attributes, like "name" and
|
||||||
|
// your custom attributes, starting with "data-". Any other will lead to a
|
||||||
|
// parsing error.
|
||||||
|
XmlCustomAttrs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver is the interface that external custom seelog message receivers
|
||||||
|
// must implement in order to be able to process seelog messages. Those receivers
|
||||||
|
// are set in the xml config file using the <custom> tag. Check receivers reference
|
||||||
|
// wiki section on that.
|
||||||
|
//
|
||||||
|
// Use seelog.RegisterReceiver on the receiver type before using it.
|
||||||
|
type CustomReceiver interface {
|
||||||
|
// ReceiveMessage is called when the custom receiver gets seelog message from
|
||||||
|
// a parent dispatcher.
|
||||||
|
//
|
||||||
|
// Message, level and context args represent all data that was included in the seelog
|
||||||
|
// message at the time it was logged.
|
||||||
|
//
|
||||||
|
// The formatting is already applied to the message and depends on the config
|
||||||
|
// like with any other receiver.
|
||||||
|
//
|
||||||
|
// If you would like to inform seelog of an error that happened during the handling of
|
||||||
|
// the message, return a non-nil error. This way you'll end up seeing your error like
|
||||||
|
// any other internal seelog error.
|
||||||
|
ReceiveMessage(message string, level LogLevel, context LogContextInterface) error
|
||||||
|
|
||||||
|
// AfterParse is called immediately after your custom receiver is instantiated by
|
||||||
|
// the xml config parser. So, if you need to do any startup logic after config parsing,
|
||||||
|
// like opening file or allocating any resources after the receiver is instantiated, do it here.
|
||||||
|
//
|
||||||
|
// If this func returns a non-nil error, then the loading procedure will fail. E.g.
|
||||||
|
// if you are loading a seelog xml config, the parser would not finish the loading
|
||||||
|
// procedure and inform about an error like with any other config error.
|
||||||
|
//
|
||||||
|
// If your custom logger needs some configuration, you can use custom attributes in
|
||||||
|
// your config. Check CustomReceiverInitArgs.XmlCustomAttrs comments.
|
||||||
|
//
|
||||||
|
// IMPORTANT: This func is NOT called when the LoggerFromCustomReceiver func is used
|
||||||
|
// to create seelog proxy logger using the custom receiver. This func is only called when
|
||||||
|
// receiver is instantiated from a config.
|
||||||
|
AfterParse(initArgs CustomReceiverInitArgs) error
|
||||||
|
|
||||||
|
// Flush is called when the custom receiver gets a 'flush' directive from a
|
||||||
|
// parent receiver. If custom receiver implements some kind of buffering or
|
||||||
|
// queing, then the appropriate reaction on a flush message is synchronous
|
||||||
|
// flushing of all those queues/buffers. If custom receiver doesn't have
|
||||||
|
// such mechanisms, then flush implementation may be left empty.
|
||||||
|
Flush()
|
||||||
|
|
||||||
|
// Close is called when the custom receiver gets a 'close' directive from a
|
||||||
|
// parent receiver. This happens when a top-level seelog dispatcher is sending
|
||||||
|
// 'close' to all child nodes and it means that current seelog logger is being closed.
|
||||||
|
// If you need to do any cleanup after your custom receiver is done, you should do
|
||||||
|
// it here.
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type customReceiverDispatcher struct {
|
||||||
|
formatter *formatter
|
||||||
|
innerReceiver CustomReceiver
|
||||||
|
customReceiverName string
|
||||||
|
usedArgs CustomReceiverInitArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomReceiverDispatcher creates a customReceiverDispatcher which dispatches data to a specific receiver created
|
||||||
|
// using a <custom> tag in the config file.
|
||||||
|
func NewCustomReceiverDispatcher(formatter *formatter, customReceiverName string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if len(customReceiverName) == 0 {
|
||||||
|
return nil, errors.New("custom receiver name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
creceiver, err := customReceiverByName(customReceiverName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = creceiver.AfterParse(cArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
disp := &customReceiverDispatcher{formatter, creceiver, customReceiverName, cArgs}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomReceiverDispatcherByValue is basically the same as NewCustomReceiverDispatcher, but using
|
||||||
|
// a specific CustomReceiver value instead of instantiating a new one by type.
|
||||||
|
func NewCustomReceiverDispatcherByValue(formatter *formatter, customReceiver CustomReceiver, name string, cArgs CustomReceiverInitArgs) (*customReceiverDispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if customReceiver == nil {
|
||||||
|
return nil, errors.New("customReceiver cannot be nil")
|
||||||
|
}
|
||||||
|
disp := &customReceiverDispatcher{formatter, customReceiver, name, cArgs}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
errorFunc(fmt.Errorf("panic in custom receiver '%s'.Dispatch: %s", reflect.TypeOf(disp.innerReceiver), err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := disp.innerReceiver.ReceiveMessage(disp.formatter.Format(message, level, context), level, context)
|
||||||
|
if err != nil {
|
||||||
|
errorFunc(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Flush() {
|
||||||
|
disp.innerReceiver.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomReceiver implementation. Check CustomReceiver comments.
|
||||||
|
func (disp *customReceiverDispatcher) Close() error {
|
||||||
|
disp.innerReceiver.Flush()
|
||||||
|
|
||||||
|
err := disp.innerReceiver.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *customReceiverDispatcher) String() string {
|
||||||
|
datas := ""
|
||||||
|
skeys := make([]string, 0, len(disp.usedArgs.XmlCustomAttrs))
|
||||||
|
for i := range disp.usedArgs.XmlCustomAttrs {
|
||||||
|
skeys = append(skeys, i)
|
||||||
|
}
|
||||||
|
sort.Strings(skeys)
|
||||||
|
for _, key := range skeys {
|
||||||
|
datas += fmt.Sprintf("<%s, %s> ", key, disp.usedArgs.XmlCustomAttrs[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
str := fmt.Sprintf("Custom receiver %s [fmt='%s'],[data='%s'],[inner='%s']\n",
|
||||||
|
disp.customReceiverName, disp.formatter.String(), datas, disp.innerReceiver)
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A dispatcherInterface is used to dispatch message to all underlying receivers.
|
||||||
|
// Dispatch logic depends on given context and log level. Any errors are reported using errorFunc.
|
||||||
|
// Also, as underlying receivers may have a state, dispatcher has a ShuttingDown method which performs
|
||||||
|
// an immediate cleanup of all data that is stored in the receivers
|
||||||
|
type dispatcherInterface interface {
|
||||||
|
flusherInterface
|
||||||
|
io.Closer
|
||||||
|
Dispatch(message string, level LogLevel, context LogContextInterface, errorFunc func(err error))
|
||||||
|
}
|
||||||
|
|
||||||
|
type dispatcher struct {
|
||||||
|
formatter *formatter
|
||||||
|
writers []*formattedWriter
|
||||||
|
dispatchers []dispatcherInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a dispatcher which dispatches data to a list of receivers.
|
||||||
|
// Each receiver should be either a Dispatcher or io.Writer, otherwise an error will be returned
|
||||||
|
func createDispatcher(formatter *formatter, receivers []interface{}) (*dispatcher, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter cannot be nil")
|
||||||
|
}
|
||||||
|
if receivers == nil || len(receivers) == 0 {
|
||||||
|
return nil, errors.New("receivers cannot be nil or empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
disp := &dispatcher{formatter, make([]*formattedWriter, 0), make([]dispatcherInterface, 0)}
|
||||||
|
for _, receiver := range receivers {
|
||||||
|
writer, ok := receiver.(*formattedWriter)
|
||||||
|
if ok {
|
||||||
|
disp.writers = append(disp.writers, writer)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ioWriter, ok := receiver.(io.Writer)
|
||||||
|
if ok {
|
||||||
|
writer, err := NewFormattedWriter(ioWriter, disp.formatter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
disp.writers = append(disp.writers, writer)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dispInterface, ok := receiver.(dispatcherInterface)
|
||||||
|
if ok {
|
||||||
|
disp.dispatchers = append(disp.dispatchers, dispInterface)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("method can receive either io.Writer or dispatcherInterface")
|
||||||
|
}
|
||||||
|
|
||||||
|
return disp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
|
||||||
|
for _, writer := range disp.writers {
|
||||||
|
err := writer.Write(message, level, context)
|
||||||
|
if err != nil {
|
||||||
|
errorFunc(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dispInterface := range disp.dispatchers {
|
||||||
|
dispInterface.Dispatch(message, level, context, errorFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush goes through all underlying writers which implement flusherInterface interface
|
||||||
|
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||||
|
func (disp *dispatcher) Flush() {
|
||||||
|
for _, disp := range disp.Dispatchers() {
|
||||||
|
disp.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatWriter := range disp.Writers() {
|
||||||
|
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||||
|
if ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close goes through all underlying writers which implement io.Closer interface
|
||||||
|
// and closes them. Recursively performs the same action for underlying dispatchers
|
||||||
|
// Before closing, writers are flushed to prevent loss of any buffered data, so
|
||||||
|
// a call to Flush() func before Close() is not necessary
|
||||||
|
func (disp *dispatcher) Close() error {
|
||||||
|
for _, disp := range disp.Dispatchers() {
|
||||||
|
disp.Flush()
|
||||||
|
err := disp.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, formatWriter := range disp.Writers() {
|
||||||
|
flusher, ok := formatWriter.Writer().(flusherInterface)
|
||||||
|
if ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
closer, ok := formatWriter.Writer().(io.Closer)
|
||||||
|
if ok {
|
||||||
|
err := closer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Writers() []*formattedWriter {
|
||||||
|
return disp.writers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) Dispatchers() []dispatcherInterface {
|
||||||
|
return disp.dispatchers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (disp *dispatcher) String() string {
|
||||||
|
str := "formatter: " + disp.formatter.String() + "\n"
|
||||||
|
|
||||||
|
str += " ->Dispatchers:"
|
||||||
|
|
||||||
|
if len(disp.dispatchers) == 0 {
|
||||||
|
str += "none\n"
|
||||||
|
} else {
|
||||||
|
str += "\n"
|
||||||
|
|
||||||
|
for _, disp := range disp.dispatchers {
|
||||||
|
str += fmt.Sprintf(" ->%s", disp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str += " ->Writers:"
|
||||||
|
|
||||||
|
if len(disp.writers) == 0 {
|
||||||
|
str += "none\n"
|
||||||
|
} else {
|
||||||
|
str += "\n"
|
||||||
|
|
||||||
|
for _, writer := range disp.writers {
|
||||||
|
str += fmt.Sprintf(" ->%s\n", writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A filterDispatcher writes the given message to underlying receivers only if message log level
|
||||||
|
// is in the allowed list.
|
||||||
|
type filterDispatcher struct {
|
||||||
|
*dispatcher
|
||||||
|
allowList map[LogLevel]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilterDispatcher creates a new filterDispatcher using a list of allowed levels.
|
||||||
|
func NewFilterDispatcher(formatter *formatter, receivers []interface{}, allowList ...LogLevel) (*filterDispatcher, error) {
|
||||||
|
disp, err := createDispatcher(formatter, receivers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allows := make(map[LogLevel]bool)
|
||||||
|
for _, allowLevel := range allowList {
|
||||||
|
allows[allowLevel] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return &filterDispatcher{disp, allows}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterDispatcher) Dispatch(
|
||||||
|
message string,
|
||||||
|
level LogLevel,
|
||||||
|
context LogContextInterface,
|
||||||
|
errorFunc func(err error)) {
|
||||||
|
isAllowed, ok := filter.allowList[level]
|
||||||
|
if ok && isAllowed {
|
||||||
|
filter.dispatcher.Dispatch(message, level, context, errorFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterDispatcher) String() string {
|
||||||
|
return fmt.Sprintf("filterDispatcher ->\n%s", filter.dispatcher)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A splitDispatcher just writes the given message to underlying receivers. (Splits the message stream.)
|
||||||
|
type splitDispatcher struct {
|
||||||
|
*dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSplitDispatcher(formatter *formatter, receivers []interface{}) (*splitDispatcher, error) {
|
||||||
|
disp, err := createDispatcher(formatter, receivers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &splitDispatcher{disp}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (splitter *splitDispatcher) String() string {
|
||||||
|
return fmt.Sprintf("splitDispatcher ->\n%s", splitter.dispatcher.String())
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) 2014 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package seelog implements logging functionality with flexible dispatching, filtering, and formatting.
|
||||||
|
|
||||||
|
Creation
|
||||||
|
|
||||||
|
To create a logger, use one of the following constructors:
|
||||||
|
func LoggerFromConfigAsBytes
|
||||||
|
func LoggerFromConfigAsFile
|
||||||
|
func LoggerFromConfigAsString
|
||||||
|
func LoggerFromWriterWithMinLevel
|
||||||
|
func LoggerFromWriterWithMinLevelAndFormat
|
||||||
|
func LoggerFromCustomReceiver (check https://github.com/cihub/seelog/wiki/Custom-receivers)
|
||||||
|
Example:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Flush()
|
||||||
|
... use logger ...
|
||||||
|
}
|
||||||
|
The "defer" line is important because if you are using asynchronous logger behavior, without this line you may end up losing some
|
||||||
|
messages when you close your application because they are processed in another non-blocking goroutine. To avoid that you
|
||||||
|
explicitly defer flushing all messages before closing.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
Logger created using one of the LoggerFrom* funcs can be used directly by calling one of the main log funcs.
|
||||||
|
Example:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer logger.Flush()
|
||||||
|
logger.Trace("test")
|
||||||
|
logger.Debugf("var = %s", "abc")
|
||||||
|
}
|
||||||
|
|
||||||
|
Having loggers as variables is convenient if you are writing your own package with internal logging or if you have
|
||||||
|
several loggers with different options.
|
||||||
|
But for most standalone apps it is more convenient to use package level funcs and vars. There is a package level
|
||||||
|
var 'Current' made for it. You can replace it with another logger using 'ReplaceLogger' and then use package level funcs:
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.ReplaceLogger(logger)
|
||||||
|
defer log.Flush()
|
||||||
|
log.Trace("test")
|
||||||
|
log.Debugf("var = %s", "abc")
|
||||||
|
}
|
||||||
|
Last lines
|
||||||
|
log.Trace("test")
|
||||||
|
log.Debugf("var = %s", "abc")
|
||||||
|
do the same as
|
||||||
|
log.Current.Trace("test")
|
||||||
|
log.Current.Debugf("var = %s", "abc")
|
||||||
|
In this example the 'Current' logger was replaced using a 'ReplaceLogger' call and became equal to 'logger' variable created from config.
|
||||||
|
This way you are able to use package level funcs instead of passing the logger variable.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
|
||||||
|
Main seelog point is to configure logger via config files and not the code.
|
||||||
|
The configuration is read by LoggerFrom* funcs. These funcs read xml configuration from different sources and try
|
||||||
|
to create a logger using it.
|
||||||
|
|
||||||
|
All the configuration features are covered in detail in the official wiki: https://github.com/cihub/seelog/wiki.
|
||||||
|
There are many sections covering different aspects of seelog, but the most important for understanding configs are:
|
||||||
|
https://github.com/cihub/seelog/wiki/Constraints-and-exceptions
|
||||||
|
https://github.com/cihub/seelog/wiki/Dispatchers-and-receivers
|
||||||
|
https://github.com/cihub/seelog/wiki/Formatting
|
||||||
|
https://github.com/cihub/seelog/wiki/Logger-types
|
||||||
|
After you understand these concepts, check the 'Reference' section on the main wiki page to get the up-to-date
|
||||||
|
list of dispatchers, receivers, formats, and logger types.
|
||||||
|
|
||||||
|
Here is an example config with all these features:
|
||||||
|
<seelog type="adaptive" mininterval="2000000" maxinterval="100000000" critmsgcount="500" minlevel="debug">
|
||||||
|
<exceptions>
|
||||||
|
<exception filepattern="test*" minlevel="error"/>
|
||||||
|
</exceptions>
|
||||||
|
<outputs formatid="all">
|
||||||
|
<file path="all.log"/>
|
||||||
|
<filter levels="info">
|
||||||
|
<console formatid="fmtinfo"/>
|
||||||
|
</filter>
|
||||||
|
<filter levels="error,critical" formatid="fmterror">
|
||||||
|
<console/>
|
||||||
|
<file path="errors.log"/>
|
||||||
|
</filter>
|
||||||
|
</outputs>
|
||||||
|
<formats>
|
||||||
|
<format id="fmtinfo" format="[%Level] [%Time] %Msg%n"/>
|
||||||
|
<format id="fmterror" format="[%LEVEL] [%Time] [%FuncShort @ %File.%Line] %Msg%n"/>
|
||||||
|
<format id="all" format="[%Level] [%Time] [@ %File.%Line] %Msg%n"/>
|
||||||
|
<format id="criticalemail" format="Critical error on our server!\n %Time %Date %RelFile %Func %Msg \nSent by Seelog"/>
|
||||||
|
</formats>
|
||||||
|
</seelog>
|
||||||
|
This config represents a logger with adaptive timeout between log messages (check logger types reference) which
|
||||||
|
logs to console, all.log, and errors.log depending on the log level. Its output formats also depend on log level. This logger will only
|
||||||
|
use log level 'debug' and higher (minlevel is set) for all files with names that don't start with 'test'. For files starting with 'test'
|
||||||
|
this logger prohibits all levels below 'error'.
|
||||||
|
|
||||||
|
Configuration using code
|
||||||
|
|
||||||
|
Although configuration using code is not recommended, it is sometimes needed and it is possible to do with seelog. Basically, what
|
||||||
|
you need to do to get started is to create constraints, exceptions and a dispatcher tree (same as with config). Most of the New*
|
||||||
|
functions in this package are used to provide such capabilities.
|
||||||
|
|
||||||
|
Here is an example of configuration in code, that demonstrates an async loop logger that logs to a simple split dispatcher with
|
||||||
|
a console receiver using a specified format and is filtered using a top-level min-max constraints and one expection for
|
||||||
|
the 'main.go' file. So, this is basically a demonstration of configuration of most of the features:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import log "github.com/cihub/seelog"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer log.Flush()
|
||||||
|
log.Info("Hello from Seelog!")
|
||||||
|
|
||||||
|
consoleWriter, _ := log.NewConsoleWriter()
|
||||||
|
formatter, _ := log.NewFormatter("%Level %Msg %File%n")
|
||||||
|
root, _ := log.NewSplitDispatcher(formatter, []interface{}{consoleWriter})
|
||||||
|
constraints, _ := log.NewMinMaxConstraints(log.TraceLvl, log.CriticalLvl)
|
||||||
|
specificConstraints, _ := log.NewListConstraints([]log.LogLevel{log.InfoLvl, log.ErrorLvl})
|
||||||
|
ex, _ := log.NewLogLevelException("*", "*main.go", specificConstraints)
|
||||||
|
exceptions := []*log.LogLevelException{ex}
|
||||||
|
|
||||||
|
logger := log.NewAsyncLoopLogger(log.NewLoggerConfig(constraints, exceptions, root))
|
||||||
|
log.ReplaceLogger(logger)
|
||||||
|
|
||||||
|
log.Trace("This should not be seen")
|
||||||
|
log.Debug("This should not be seen")
|
||||||
|
log.Info("Test")
|
||||||
|
log.Error("Test2")
|
||||||
|
}
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
To learn seelog features faster you should check the examples package: https://github.com/cihub/seelog-examples
|
||||||
|
It contains many example configs and usecases.
|
||||||
|
*/
|
||||||
|
package seelog
|
|
@ -0,0 +1,466 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatterSymbol is a special symbol used in config files to mark special format aliases.
|
||||||
|
const (
|
||||||
|
FormatterSymbol = '%'
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
formatterParameterStart = '('
|
||||||
|
formatterParameterEnd = ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time and date formats used for %Date and %Time aliases.
|
||||||
|
const (
|
||||||
|
DateDefaultFormat = "2006-01-02"
|
||||||
|
TimeFormat = "15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultMsgFormat = "%Ns [%Level] %Msg%n"
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultFormatter *formatter
|
||||||
|
msgonlyformatter *formatter
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
if DefaultFormatter, err = NewFormatter(DefaultMsgFormat); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("error during creating DefaultFormatter: %s", err))
|
||||||
|
}
|
||||||
|
if msgonlyformatter, err = NewFormatter("%Msg"); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("error during creating msgonlyformatter: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatterFunc represents one formatter object that starts with '%' sign in the 'format' attribute
|
||||||
|
// of the 'format' config item. These special symbols are replaced with context values or special
|
||||||
|
// strings when message is written to byte receiver.
|
||||||
|
//
|
||||||
|
// Check https://github.com/cihub/seelog/wiki/Formatting for details.
|
||||||
|
// Full list (with descriptions) of formatters: https://github.com/cihub/seelog/wiki/Format-reference
|
||||||
|
//
|
||||||
|
// FormatterFunc takes raw log message, level, log context and returns a string, number (of any type) or any object
|
||||||
|
// that can be evaluated as string.
|
||||||
|
type FormatterFunc func(message string, level LogLevel, context LogContextInterface) interface{}
|
||||||
|
|
||||||
|
// FormatterFuncCreator is a factory of FormatterFunc objects. It is used to generate parameterized
|
||||||
|
// formatters (such as %Date or %EscM) and custom user formatters.
|
||||||
|
type FormatterFuncCreator func(param string) FormatterFunc
|
||||||
|
|
||||||
|
var formatterFuncs = map[string]FormatterFunc{
|
||||||
|
"Level": formatterLevel,
|
||||||
|
"Lev": formatterLev,
|
||||||
|
"LEVEL": formatterLEVEL,
|
||||||
|
"LEV": formatterLEV,
|
||||||
|
"l": formatterl,
|
||||||
|
"Msg": formatterMsg,
|
||||||
|
"FullPath": formatterFullPath,
|
||||||
|
"File": formatterFile,
|
||||||
|
"RelFile": formatterRelFile,
|
||||||
|
"Func": FormatterFunction,
|
||||||
|
"FuncShort": FormatterFunctionShort,
|
||||||
|
"Line": formatterLine,
|
||||||
|
"Time": formatterTime,
|
||||||
|
"UTCTime": formatterUTCTime,
|
||||||
|
"Ns": formatterNs,
|
||||||
|
"UTCNs": formatterUTCNs,
|
||||||
|
"r": formatterr,
|
||||||
|
"n": formattern,
|
||||||
|
"t": formattert,
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatterFuncsParameterized = map[string]FormatterFuncCreator{
|
||||||
|
"Date": createDateTimeFormatterFunc,
|
||||||
|
"UTCDate": createUTCDateTimeFormatterFunc,
|
||||||
|
"EscM": createANSIEscapeFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorAliasReserved(name string) error {
|
||||||
|
return fmt.Errorf("cannot use '%s' as custom formatter name. Name is reserved", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCustomFormatter registers a new custom formatter factory with a given name. If returned error is nil,
|
||||||
|
// then this name (prepended by '%' symbol) can be used in 'format' attributes in configuration and
|
||||||
|
// it will be treated like the standard parameterized formatter identifiers.
|
||||||
|
//
|
||||||
|
// RegisterCustomFormatter needs to be called before creating a logger for it to take effect. The general recommendation
|
||||||
|
// is to call it once in 'init' func of your application or any initializer func.
|
||||||
|
//
|
||||||
|
// For usage examples, check https://github.com/cihub/seelog/wiki/Custom-formatters.
|
||||||
|
//
|
||||||
|
// Name must only consist of letters (unicode.IsLetter).
|
||||||
|
//
|
||||||
|
// Name must not be one of the already registered standard formatter names
|
||||||
|
// (https://github.com/cihub/seelog/wiki/Format-reference) and previously registered
|
||||||
|
// custom format names. To avoid any potential name conflicts (in future releases), it is recommended
|
||||||
|
// to start your custom formatter name with a namespace (e.g. 'MyCompanySomething') or a 'Custom' keyword.
|
||||||
|
func RegisterCustomFormatter(name string, creator FormatterFuncCreator) error {
|
||||||
|
if _, ok := formatterFuncs[name]; ok {
|
||||||
|
return errorAliasReserved(name)
|
||||||
|
}
|
||||||
|
if _, ok := formatterFuncsParameterized[name]; ok {
|
||||||
|
return errorAliasReserved(name)
|
||||||
|
}
|
||||||
|
formatterFuncsParameterized[name] = creator
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatter is used to write messages in a specific format, inserting such additional data
|
||||||
|
// as log level, date/time, etc.
|
||||||
|
type formatter struct {
|
||||||
|
fmtStringOriginal string
|
||||||
|
fmtString string
|
||||||
|
formatterFuncs []FormatterFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormatter creates a new formatter using a format string
|
||||||
|
func NewFormatter(formatString string) (*formatter, error) {
|
||||||
|
fmtr := new(formatter)
|
||||||
|
fmtr.fmtStringOriginal = formatString
|
||||||
|
if err := buildFormatterFuncs(fmtr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fmtr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFormatterFuncs(formatter *formatter) error {
|
||||||
|
var (
|
||||||
|
fsbuf = new(bytes.Buffer)
|
||||||
|
fsolm1 = len(formatter.fmtStringOriginal) - 1
|
||||||
|
)
|
||||||
|
for i := 0; i <= fsolm1; i++ {
|
||||||
|
if char := formatter.fmtStringOriginal[i]; char != FormatterSymbol {
|
||||||
|
fsbuf.WriteByte(char)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check if the index is at the end of the string.
|
||||||
|
if i == fsolm1 {
|
||||||
|
return fmt.Errorf("format error: %c cannot be last symbol", FormatterSymbol)
|
||||||
|
}
|
||||||
|
// Check if the formatter symbol is doubled and skip it as nonmatching.
|
||||||
|
if formatter.fmtStringOriginal[i+1] == FormatterSymbol {
|
||||||
|
fsbuf.WriteRune(FormatterSymbol)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
function, ni, err := formatter.extractFormatterFunc(i + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Append formatting string "%v".
|
||||||
|
fsbuf.Write([]byte{37, 118})
|
||||||
|
i = ni
|
||||||
|
formatter.formatterFuncs = append(formatter.formatterFuncs, function)
|
||||||
|
}
|
||||||
|
formatter.fmtString = fsbuf.String()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) extractFormatterFunc(index int) (FormatterFunc, int, error) {
|
||||||
|
letterSequence := formatter.extractLetterSequence(index)
|
||||||
|
if len(letterSequence) == 0 {
|
||||||
|
return nil, 0, fmt.Errorf("format error: lack of formatter after %c at %d", FormatterSymbol, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function, formatterLength, ok := formatter.findFormatterFunc(letterSequence)
|
||||||
|
if ok {
|
||||||
|
return function, index + formatterLength - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function, formatterLength, ok, err := formatter.findFormatterFuncParametrized(letterSequence, index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return function, index + formatterLength - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, errors.New("format error: unrecognized formatter at " + strconv.Itoa(index) + ": " + letterSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) extractLetterSequence(index int) string {
|
||||||
|
letters := ""
|
||||||
|
|
||||||
|
bytesToParse := []byte(formatter.fmtStringOriginal[index:])
|
||||||
|
runeCount := utf8.RuneCount(bytesToParse)
|
||||||
|
for i := 0; i < runeCount; i++ {
|
||||||
|
rune, runeSize := utf8.DecodeRune(bytesToParse)
|
||||||
|
bytesToParse = bytesToParse[runeSize:]
|
||||||
|
|
||||||
|
if unicode.IsLetter(rune) {
|
||||||
|
letters += string(rune)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return letters
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findFormatterFunc(letters string) (FormatterFunc, int, bool) {
|
||||||
|
currentVerb := letters
|
||||||
|
for i := 0; i < len(letters); i++ {
|
||||||
|
function, ok := formatterFuncs[currentVerb]
|
||||||
|
if ok {
|
||||||
|
return function, len(currentVerb), ok
|
||||||
|
}
|
||||||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findFormatterFuncParametrized(letters string, lettersStartIndex int) (FormatterFunc, int, bool, error) {
|
||||||
|
currentVerb := letters
|
||||||
|
for i := 0; i < len(letters); i++ {
|
||||||
|
functionCreator, ok := formatterFuncsParameterized[currentVerb]
|
||||||
|
if ok {
|
||||||
|
parameter := ""
|
||||||
|
parameterLen := 0
|
||||||
|
isVerbEqualsLetters := i == 0 // if not, then letter goes after formatter, and formatter is parameterless
|
||||||
|
if isVerbEqualsLetters {
|
||||||
|
userParameter := ""
|
||||||
|
var err error
|
||||||
|
userParameter, parameterLen, ok, err = formatter.findparameter(lettersStartIndex + len(currentVerb))
|
||||||
|
if ok {
|
||||||
|
parameter = userParameter
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, 0, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return functionCreator(parameter), len(currentVerb) + parameterLen, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVerb = currentVerb[:len(currentVerb)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) findparameter(startIndex int) (string, int, bool, error) {
|
||||||
|
if len(formatter.fmtStringOriginal) == startIndex || formatter.fmtStringOriginal[startIndex] != formatterParameterStart {
|
||||||
|
return "", 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endIndex := strings.Index(formatter.fmtStringOriginal[startIndex:], string(formatterParameterEnd))
|
||||||
|
if endIndex == -1 {
|
||||||
|
return "", 0, false, fmt.Errorf("Unmatched parenthesis or invalid parameter at %d: %s",
|
||||||
|
startIndex, formatter.fmtStringOriginal[startIndex:])
|
||||||
|
}
|
||||||
|
endIndex += startIndex
|
||||||
|
|
||||||
|
length := endIndex - startIndex + 1
|
||||||
|
|
||||||
|
return formatter.fmtStringOriginal[startIndex+1 : endIndex], length, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format processes a message with special formatters, log level, and context. Returns formatted string
|
||||||
|
// with all formatter identifiers changed to appropriate values.
|
||||||
|
func (formatter *formatter) Format(message string, level LogLevel, context LogContextInterface) string {
|
||||||
|
if len(formatter.formatterFuncs) == 0 {
|
||||||
|
return formatter.fmtString
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make([]interface{}, len(formatter.formatterFuncs))
|
||||||
|
for i, function := range formatter.formatterFuncs {
|
||||||
|
params[i] = function(message, level, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(formatter.fmtString, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formatter *formatter) String() string {
|
||||||
|
return formatter.fmtStringOriginal
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================================================
|
||||||
|
|
||||||
|
const (
|
||||||
|
wrongLogLevel = "WRONG_LOGLEVEL"
|
||||||
|
wrongEscapeCode = "WRONG_ESCAPE"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelToString = map[LogLevel]string{
|
||||||
|
TraceLvl: "Trace",
|
||||||
|
DebugLvl: "Debug",
|
||||||
|
InfoLvl: "Info",
|
||||||
|
WarnLvl: "Warn",
|
||||||
|
ErrorLvl: "Error",
|
||||||
|
CriticalLvl: "Critical",
|
||||||
|
Off: "Off",
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelToShortString = map[LogLevel]string{
|
||||||
|
TraceLvl: "Trc",
|
||||||
|
DebugLvl: "Dbg",
|
||||||
|
InfoLvl: "Inf",
|
||||||
|
WarnLvl: "Wrn",
|
||||||
|
ErrorLvl: "Err",
|
||||||
|
CriticalLvl: "Crt",
|
||||||
|
Off: "Off",
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelToShortestString = map[LogLevel]string{
|
||||||
|
TraceLvl: "t",
|
||||||
|
DebugLvl: "d",
|
||||||
|
InfoLvl: "i",
|
||||||
|
WarnLvl: "w",
|
||||||
|
ErrorLvl: "e",
|
||||||
|
CriticalLvl: "c",
|
||||||
|
Off: "o",
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLevel(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLev(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToShortString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLEVEL(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return strings.ToTitle(formatterLevel(message, level, context).(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLEV(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return strings.ToTitle(formatterLev(message, level, context).(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterl(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
levelStr, ok := levelToShortestString[level]
|
||||||
|
if !ok {
|
||||||
|
return wrongLogLevel
|
||||||
|
}
|
||||||
|
return levelStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterMsg(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterFullPath(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.FullPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.FileName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterRelFile(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.ShortPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatterFunction(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.Func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormatterFunctionShort(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
f := context.Func()
|
||||||
|
spl := strings.Split(f, ".")
|
||||||
|
return spl[len(spl)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterLine(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.Line()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().Format(TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterUTCTime(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().Format(TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterUTCNs(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatterr(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\r"
|
||||||
|
}
|
||||||
|
|
||||||
|
func formattern(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func formattert(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return "\t"
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||||
|
format := dateTimeFormat
|
||||||
|
if format == "" {
|
||||||
|
format = DateDefaultFormat
|
||||||
|
}
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().Format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUTCDateTimeFormatterFunc(dateTimeFormat string) FormatterFunc {
|
||||||
|
format := dateTimeFormat
|
||||||
|
if format == "" {
|
||||||
|
format = DateDefaultFormat
|
||||||
|
}
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
return context.CallTime().UTC().Format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createANSIEscapeFunc(escapeCodeString string) FormatterFunc {
|
||||||
|
return func(message string, level LogLevel, context LogContextInterface) interface{} {
|
||||||
|
if len(escapeCodeString) == 0 {
|
||||||
|
return wrongEscapeCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%c[%sm", 0x1B, escapeCodeString)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
// Base struct for custom errors.
|
||||||
|
type baseError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be baseError) Error() string {
|
||||||
|
return be.message
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File and directory permitions.
|
||||||
|
const (
|
||||||
|
defaultFilePermissions = 0666
|
||||||
|
defaultDirectoryPermissions = 0767
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Max number of directories can be read asynchronously.
|
||||||
|
maxDirNumberReadAsync = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
type cannotOpenFileError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCannotOpenFileError(fname string) *cannotOpenFileError {
|
||||||
|
return &cannotOpenFileError{baseError{message: "Cannot open file: " + fname}}
|
||||||
|
}
|
||||||
|
|
||||||
|
type notDirectoryError struct {
|
||||||
|
baseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNotDirectoryError(dname string) *notDirectoryError {
|
||||||
|
return ¬DirectoryError{baseError{message: dname + " is not directory"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileFilter is a filtering criteria function for '*os.File'.
|
||||||
|
// Must return 'false' to set aside the given file.
|
||||||
|
type fileFilter func(os.FileInfo, *os.File) bool
|
||||||
|
|
||||||
|
// filePathFilter is a filtering creteria function for file path.
|
||||||
|
// Must return 'false' to set aside the given file.
|
||||||
|
type filePathFilter func(filePath string) bool
|
||||||
|
|
||||||
|
// GetSubdirNames returns a list of directories found in
|
||||||
|
// the given one with dirPath.
|
||||||
|
func getSubdirNames(dirPath string) ([]string, error) {
|
||||||
|
fi, err := os.Stat(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return nil, newNotDirectoryError(dirPath)
|
||||||
|
}
|
||||||
|
dd, err := os.Open(dirPath)
|
||||||
|
// Cannot open file.
|
||||||
|
if err != nil {
|
||||||
|
if dd != nil {
|
||||||
|
dd.Close()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer dd.Close()
|
||||||
|
// TODO: Improve performance by buffering reading.
|
||||||
|
allEntities, err := dd.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
subDirs := []string{}
|
||||||
|
for _, entity := range allEntities {
|
||||||
|
if entity.IsDir() {
|
||||||
|
subDirs = append(subDirs, entity.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subDirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubdirAbsPaths recursively visit all the subdirectories
|
||||||
|
// starting from the given directory and returns absolute paths for them.
|
||||||
|
func getAllSubdirAbsPaths(dirPath string) (res []string, err error) {
|
||||||
|
dps, err := getSubdirAbsPaths(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
res = []string{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res = append(res, dps...)
|
||||||
|
for _, dp := range dps {
|
||||||
|
sdps, err := getAllSubdirAbsPaths(dp)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
res = append(res, sdps...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubdirAbsPaths supplies absolute paths for all subdirectiries in a given directory.
|
||||||
|
// Input: (I1) dirPath - absolute path of a directory in question.
|
||||||
|
// Out: (O1) - slice of subdir asbolute paths; (O2) - error of the operation.
|
||||||
|
// Remark: If error (O2) is non-nil then (O1) is nil and vice versa.
|
||||||
|
func getSubdirAbsPaths(dirPath string) ([]string, error) {
|
||||||
|
sdns, err := getSubdirNames(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rsdns := []string{}
|
||||||
|
for _, sdn := range sdns {
|
||||||
|
rsdns = append(rsdns, filepath.Join(dirPath, sdn))
|
||||||
|
}
|
||||||
|
return rsdns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpenFilesInDir supplies a slice of os.File pointers to files located in the directory.
|
||||||
|
// Remark: Ignores files for which fileFilter returns false
|
||||||
|
func getOpenFilesInDir(dirPath string, fFilter fileFilter) ([]*os.File, error) {
|
||||||
|
dfi, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||||
|
}
|
||||||
|
defer dfi.Close()
|
||||||
|
// Size of read buffer (i.e. chunk of items read at a time).
|
||||||
|
rbs := 64
|
||||||
|
resFiles := []*os.File{}
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
// Read directory entities by reasonable chuncks
|
||||||
|
// to prevent overflows on big number of files.
|
||||||
|
fis, e := dfi.Readdir(rbs)
|
||||||
|
switch e {
|
||||||
|
// It's OK.
|
||||||
|
case nil:
|
||||||
|
// Do nothing, just continue cycle.
|
||||||
|
case io.EOF:
|
||||||
|
break L
|
||||||
|
// Something went wrong.
|
||||||
|
default:
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// THINK: Maybe, use async running.
|
||||||
|
for _, fi := range fis {
|
||||||
|
// NB: On Linux this could be a problem as
|
||||||
|
// there are lots of file types available.
|
||||||
|
if !fi.IsDir() {
|
||||||
|
f, e := os.Open(filepath.Join(dirPath, fi.Name()))
|
||||||
|
if e != nil {
|
||||||
|
if f != nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
// THINK: Add nil as indicator that a problem occurred.
|
||||||
|
resFiles = append(resFiles, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check filter condition.
|
||||||
|
if fFilter != nil && !fFilter(fi, f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resFiles = append(resFiles, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRegular(m os.FileMode) bool {
|
||||||
|
return m&os.ModeType == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDirFilePaths return full paths of the files located in the directory.
|
||||||
|
// Remark: Ignores files for which fileFilter returns false.
|
||||||
|
func getDirFilePaths(dirPath string, fpFilter filePathFilter, pathIsName bool) ([]string, error) {
|
||||||
|
dfi, err := os.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newCannotOpenFileError("Cannot open directory " + dirPath)
|
||||||
|
}
|
||||||
|
defer dfi.Close()
|
||||||
|
|
||||||
|
var absDirPath string
|
||||||
|
if !filepath.IsAbs(dirPath) {
|
||||||
|
absDirPath, err = filepath.Abs(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get absolute path of directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
absDirPath = dirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if dirPath is really directory.
|
||||||
|
// Size of read buffer (i.e. chunk of items read at a time).
|
||||||
|
rbs := 2 << 5
|
||||||
|
filePaths := []string{}
|
||||||
|
|
||||||
|
var fp string
|
||||||
|
L:
|
||||||
|
for {
|
||||||
|
// Read directory entities by reasonable chuncks
|
||||||
|
// to prevent overflows on big number of files.
|
||||||
|
fis, e := dfi.Readdir(rbs)
|
||||||
|
switch e {
|
||||||
|
// It's OK.
|
||||||
|
case nil:
|
||||||
|
// Do nothing, just continue cycle.
|
||||||
|
case io.EOF:
|
||||||
|
break L
|
||||||
|
// Indicate that something went wrong.
|
||||||
|
default:
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
// THINK: Maybe, use async running.
|
||||||
|
for _, fi := range fis {
|
||||||
|
// NB: Should work on every Windows and non-Windows OS.
|
||||||
|
if isRegular(fi.Mode()) {
|
||||||
|
if pathIsName {
|
||||||
|
fp = fi.Name()
|
||||||
|
} else {
|
||||||
|
// Build full path of a file.
|
||||||
|
fp = filepath.Join(absDirPath, fi.Name())
|
||||||
|
}
|
||||||
|
// Check filter condition.
|
||||||
|
if fpFilter != nil && !fpFilter(fp) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filePaths = append(filePaths, fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filePaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOpenFilesByDirectoryAsync runs async reading directories 'dirPaths' and inserts pairs
|
||||||
|
// in map 'filesInDirMap': Key - directory name, value - *os.File slice.
|
||||||
|
func getOpenFilesByDirectoryAsync(
|
||||||
|
dirPaths []string,
|
||||||
|
fFilter fileFilter,
|
||||||
|
filesInDirMap map[string][]*os.File,
|
||||||
|
) error {
|
||||||
|
n := len(dirPaths)
|
||||||
|
if n > maxDirNumberReadAsync {
|
||||||
|
return fmt.Errorf("number of input directories to be read exceeded max value %d", maxDirNumberReadAsync)
|
||||||
|
}
|
||||||
|
type filesInDirResult struct {
|
||||||
|
DirName string
|
||||||
|
Files []*os.File
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
dirFilesChan := make(chan *filesInDirResult, n)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
// Register n goroutines which are going to do work.
|
||||||
|
wg.Add(n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
// Launch asynchronously the piece of work.
|
||||||
|
go func(dirPath string) {
|
||||||
|
fs, e := getOpenFilesInDir(dirPath, fFilter)
|
||||||
|
dirFilesChan <- &filesInDirResult{filepath.Base(dirPath), fs, e}
|
||||||
|
// Mark the current goroutine as finished (work is done).
|
||||||
|
wg.Done()
|
||||||
|
}(dirPaths[i])
|
||||||
|
}
|
||||||
|
// Wait for all goroutines to finish their work.
|
||||||
|
wg.Wait()
|
||||||
|
// Close the error channel to let for-range clause
|
||||||
|
// get all the buffered values without blocking and quit in the end.
|
||||||
|
close(dirFilesChan)
|
||||||
|
for fidr := range dirFilesChan {
|
||||||
|
if fidr.Error == nil {
|
||||||
|
// THINK: What will happen if the key is already present?
|
||||||
|
filesInDirMap[fidr.DirName] = fidr.Files
|
||||||
|
} else {
|
||||||
|
return fidr.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileExists return flag whether a given file exists
|
||||||
|
// and operation error if an unclassified failure occurs.
|
||||||
|
func fileExists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDirectory makes directory with a given name
|
||||||
|
// making all parent directories if necessary.
|
||||||
|
func createDirectory(dirPath string) error {
|
||||||
|
var dPath string
|
||||||
|
var err error
|
||||||
|
if !filepath.IsAbs(dirPath) {
|
||||||
|
dPath, err = filepath.Abs(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dPath = dirPath
|
||||||
|
}
|
||||||
|
exists, err := fileExists(dPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return os.MkdirAll(dPath, os.ModeDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryRemoveFile gives a try removing the file
|
||||||
|
// only ignoring an error when the file does not exist.
|
||||||
|
func tryRemoveFile(filePath string) (err error) {
|
||||||
|
err = os.Remove(filePath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlNode struct {
|
||||||
|
name string
|
||||||
|
attributes map[string]string
|
||||||
|
children []*xmlNode
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode() *xmlNode {
|
||||||
|
node := new(xmlNode)
|
||||||
|
node.children = make([]*xmlNode, 0)
|
||||||
|
node.attributes = make(map[string]string)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) String() string {
|
||||||
|
str := fmt.Sprintf("<%s", node.name)
|
||||||
|
|
||||||
|
for attrName, attrVal := range node.attributes {
|
||||||
|
str += fmt.Sprintf(" %s=\"%s\"", attrName, attrVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
str += ">"
|
||||||
|
str += node.value
|
||||||
|
|
||||||
|
if len(node.children) != 0 {
|
||||||
|
for _, child := range node.children {
|
||||||
|
str += fmt.Sprintf("%s", child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str += fmt.Sprintf("</%s>", node.name)
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) unmarshal(startEl xml.StartElement) error {
|
||||||
|
node.name = startEl.Name.Local
|
||||||
|
|
||||||
|
for _, v := range startEl.Attr {
|
||||||
|
_, alreadyExists := node.attributes[v.Name.Local]
|
||||||
|
if alreadyExists {
|
||||||
|
return errors.New("tag '" + node.name + "' has duplicated attribute: '" + v.Name.Local + "'")
|
||||||
|
}
|
||||||
|
node.attributes[v.Name.Local] = v.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) add(child *xmlNode) {
|
||||||
|
if node.children == nil {
|
||||||
|
node.children = make([]*xmlNode, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.children = append(node.children, child)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *xmlNode) hasChildren() bool {
|
||||||
|
return node.children != nil && len(node.children) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================
|
||||||
|
|
||||||
|
func unmarshalConfig(reader io.Reader) (*xmlNode, error) {
|
||||||
|
xmlParser := xml.NewDecoder(reader)
|
||||||
|
|
||||||
|
config, err := unmarshalNode(xmlParser, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("xml has no content")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextConfigEntry, err := unmarshalNode(xmlParser, nil)
|
||||||
|
if nextConfigEntry != nil {
|
||||||
|
return nil, errors.New("xml contains more than one root element")
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalNode(xmlParser *xml.Decoder, curToken xml.Token) (node *xmlNode, err error) {
|
||||||
|
firstLoop := true
|
||||||
|
for {
|
||||||
|
var tok xml.Token
|
||||||
|
if firstLoop && curToken != nil {
|
||||||
|
tok = curToken
|
||||||
|
firstLoop = false
|
||||||
|
} else {
|
||||||
|
tok, err = getNextToken(xmlParser)
|
||||||
|
if err != nil || tok == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := tok.(type) {
|
||||||
|
case xml.SyntaxError:
|
||||||
|
err = errors.New(tt.Error())
|
||||||
|
return
|
||||||
|
case xml.CharData:
|
||||||
|
value := strings.TrimSpace(string([]byte(tt)))
|
||||||
|
if node != nil {
|
||||||
|
node.value += value
|
||||||
|
}
|
||||||
|
case xml.StartElement:
|
||||||
|
if node == nil {
|
||||||
|
node = newNode()
|
||||||
|
err := node.unmarshal(tt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
childNode, childErr := unmarshalNode(xmlParser, tok)
|
||||||
|
if childErr != nil {
|
||||||
|
return nil, childErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if childNode != nil {
|
||||||
|
node.add(childNode)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNextToken(xmlParser *xml.Decoder) (tok xml.Token, err error) {
|
||||||
|
if tok, err = xmlParser.Token(); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,307 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
staticFuncCallDepth = 3 // See 'commonLogger.log' method comments
|
||||||
|
loggerFuncCallDepth = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// Current is the logger used in all package level convenience funcs like 'Trace', 'Debug', 'Flush', etc.
|
||||||
|
var Current LoggerInterface
|
||||||
|
|
||||||
|
// Default logger that is created from an empty config: "<seelog/>". It is not closed by a ReplaceLogger call.
|
||||||
|
var Default LoggerInterface
|
||||||
|
|
||||||
|
// Disabled logger that doesn't produce any output in any circumstances. It is neither closed nor flushed by a ReplaceLogger call.
|
||||||
|
var Disabled LoggerInterface
|
||||||
|
|
||||||
|
var pkgOperationsMutex *sync.Mutex
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pkgOperationsMutex = new(sync.Mutex)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if Default == nil {
|
||||||
|
Default, err = LoggerFromConfigAsBytes([]byte("<seelog />"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if Disabled == nil {
|
||||||
|
Disabled, err = LoggerFromConfigAsBytes([]byte("<seelog levels=\"off\"/>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Seelog couldn't start. Error: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = Default
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLoggerFromFullConfig(config *configForParsing) (LoggerInterface, error) {
|
||||||
|
if config.LogType == syncloggerTypeFromString {
|
||||||
|
return NewSyncLogger(&config.logConfig), nil
|
||||||
|
} else if config.LogType == asyncLooploggerTypeFromString {
|
||||||
|
return NewAsyncLoopLogger(&config.logConfig), nil
|
||||||
|
} else if config.LogType == asyncTimerloggerTypeFromString {
|
||||||
|
logData := config.LoggerData
|
||||||
|
if logData == nil {
|
||||||
|
return nil, errors.New("async timer data not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncInt, ok := logData.(asyncTimerLoggerData)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid async timer data")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := NewAsyncTimerLogger(&config.logConfig, time.Duration(asyncInt.AsyncInterval))
|
||||||
|
if !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger, nil
|
||||||
|
} else if config.LogType == adaptiveLoggerTypeFromString {
|
||||||
|
logData := config.LoggerData
|
||||||
|
if logData == nil {
|
||||||
|
return nil, errors.New("adaptive logger parameters not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
adaptData, ok := logData.(adaptiveLoggerData)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid adaptive logger parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger, err := NewAsyncAdaptiveLogger(
|
||||||
|
&config.logConfig,
|
||||||
|
time.Duration(adaptData.MinInterval),
|
||||||
|
time.Duration(adaptData.MaxInterval),
|
||||||
|
adaptData.CriticalMsgCount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid config log type/data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger sets the 'Current' package level logger variable to the specified value.
|
||||||
|
// This variable is used in all Trace/Debug/... package level convenience funcs.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// after calling
|
||||||
|
// seelog.UseLogger(somelogger)
|
||||||
|
// the following:
|
||||||
|
// seelog.Debug("abc")
|
||||||
|
// will be equal to
|
||||||
|
// somelogger.Debug("abc")
|
||||||
|
//
|
||||||
|
// IMPORTANT: UseLogger do NOT close the previous logger (only flushes it). So if
|
||||||
|
// you constantly use it to replace loggers and don't close them in other code, you'll
|
||||||
|
// end up having memory leaks.
|
||||||
|
//
|
||||||
|
// To safely replace loggers, use ReplaceLogger.
|
||||||
|
func UseLogger(logger LoggerInterface) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
|
||||||
|
oldLogger := Current
|
||||||
|
Current = logger
|
||||||
|
|
||||||
|
if oldLogger != nil {
|
||||||
|
oldLogger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceLogger acts as UseLogger but the logger that was previously
|
||||||
|
// used is disposed (except Default and Disabled loggers).
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// import log "github.com/cihub/seelog"
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// logger, err := log.LoggerFromConfigAsFile("seelog.xml")
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.ReplaceLogger(logger)
|
||||||
|
// defer log.Flush()
|
||||||
|
//
|
||||||
|
// log.Trace("test")
|
||||||
|
// log.Debugf("var = %s", "abc")
|
||||||
|
// }
|
||||||
|
func ReplaceLogger(logger LoggerInterface) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("recovered from panic during ReplaceLogger: %s", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if Current == Default {
|
||||||
|
Current.Flush()
|
||||||
|
} else if Current != nil && !Current.Closed() && Current != Disabled {
|
||||||
|
Current.Flush()
|
||||||
|
Current.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = logger
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracef formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Trace.
|
||||||
|
func Tracef(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.traceWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Debug.
|
||||||
|
func Debugf(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.debugWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof formats message according to format specifier
|
||||||
|
// and writes to default logger with log level = Info.
|
||||||
|
func Infof(format string, params ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.infoWithCallDepth(staticFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf formats message according to format specifier and writes to default logger with log level = Warn
|
||||||
|
func Warnf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf formats message according to format specifier and writes to default logger with log level = Error
|
||||||
|
func Errorf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criticalf formats message according to format specifier and writes to default logger with log level = Critical
|
||||||
|
func Criticalf(format string, params ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace formats message using the default formats for its operands and writes to default logger with log level = Trace
|
||||||
|
func Trace(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.traceWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug formats message using the default formats for its operands and writes to default logger with log level = Debug
|
||||||
|
func Debug(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.debugWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info formats message using the default formats for its operands and writes to default logger with log level = Info
|
||||||
|
func Info(v ...interface{}) {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.infoWithCallDepth(staticFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn formats message using the default formats for its operands and writes to default logger with log level = Warn
|
||||||
|
func Warn(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.warnWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error formats message using the default formats for its operands and writes to default logger with log level = Error
|
||||||
|
func Error(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.errorWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical formats message using the default formats for its operands and writes to default logger with log level = Critical
|
||||||
|
func Critical(v ...interface{}) error {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
message := newLogMessage(v)
|
||||||
|
Current.criticalWithCallDepth(staticFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush immediately processes all currently queued messages and all currently buffered messages.
|
||||||
|
// It is a blocking call which returns only after the queue is empty and all the buffers are empty.
|
||||||
|
//
|
||||||
|
// If Flush is called for a synchronous logger (type='sync'), it only flushes buffers (e.g. '<buffered>' receivers)
|
||||||
|
// , because there is no queue.
|
||||||
|
//
|
||||||
|
// Call this method when your app is going to shut down not to lose any log messages.
|
||||||
|
func Flush() {
|
||||||
|
pkgOperationsMutex.Lock()
|
||||||
|
defer pkgOperationsMutex.Unlock()
|
||||||
|
Current.Flush()
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func reportInternalError(err error) {
|
||||||
|
fmt.Fprintf(os.Stderr, "seelog internal error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerInterface represents structs capable of logging Seelog messages
|
||||||
|
type LoggerInterface interface {
|
||||||
|
|
||||||
|
// Tracef formats message according to format specifier
|
||||||
|
// and writes to log with level = Trace.
|
||||||
|
Tracef(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Debugf formats message according to format specifier
|
||||||
|
// and writes to log with level = Debug.
|
||||||
|
Debugf(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Infof formats message according to format specifier
|
||||||
|
// and writes to log with level = Info.
|
||||||
|
Infof(format string, params ...interface{})
|
||||||
|
|
||||||
|
// Warnf formats message according to format specifier
|
||||||
|
// and writes to log with level = Warn.
|
||||||
|
Warnf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Errorf formats message according to format specifier
|
||||||
|
// and writes to log with level = Error.
|
||||||
|
Errorf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Criticalf formats message according to format specifier
|
||||||
|
// and writes to log with level = Critical.
|
||||||
|
Criticalf(format string, params ...interface{}) error
|
||||||
|
|
||||||
|
// Trace formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Trace
|
||||||
|
Trace(v ...interface{})
|
||||||
|
|
||||||
|
// Debug formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Debug
|
||||||
|
Debug(v ...interface{})
|
||||||
|
|
||||||
|
// Info formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Info
|
||||||
|
Info(v ...interface{})
|
||||||
|
|
||||||
|
// Warn formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Warn
|
||||||
|
Warn(v ...interface{}) error
|
||||||
|
|
||||||
|
// Error formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Error
|
||||||
|
Error(v ...interface{}) error
|
||||||
|
|
||||||
|
// Critical formats message using the default formats for its operands
|
||||||
|
// and writes to log with level = Critical
|
||||||
|
Critical(v ...interface{}) error
|
||||||
|
|
||||||
|
traceWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
debugWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
infoWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
warnWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
errorWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
criticalWithCallDepth(callDepth int, message fmt.Stringer)
|
||||||
|
|
||||||
|
// Close flushes all the messages in the logger and closes it. It cannot be used after this operation.
|
||||||
|
Close()
|
||||||
|
|
||||||
|
// Flush flushes all the messages in the logger.
|
||||||
|
Flush()
|
||||||
|
|
||||||
|
// Closed returns true if the logger was previously closed.
|
||||||
|
Closed() bool
|
||||||
|
|
||||||
|
// SetAdditionalStackDepth sets the additional number of frames to skip by runtime.Caller
|
||||||
|
// when getting function information needed to print seelog format identifiers such as %Func or %File.
|
||||||
|
//
|
||||||
|
// This func may be used when you wrap seelog funcs and want to print caller info of you own
|
||||||
|
// wrappers instead of seelog func callers. In this case you should set depth = 1. If you then
|
||||||
|
// wrap your wrapper, you should set depth = 2, etc.
|
||||||
|
//
|
||||||
|
// NOTE: Incorrect depth value may lead to errors in runtime.Caller evaluation or incorrect
|
||||||
|
// function/file names in log files. Do not use it if you are not going to wrap seelog funcs.
|
||||||
|
// You may reset the value to default using a SetAdditionalStackDepth(0) call.
|
||||||
|
SetAdditionalStackDepth(depth int) error
|
||||||
|
|
||||||
|
// Sets logger context that can be used in formatter funcs and custom receivers
|
||||||
|
SetContext(context interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// innerLoggerInterface is an internal logging interface
|
||||||
|
type innerLoggerInterface interface {
|
||||||
|
innerLog(level LogLevel, context LogContextInterface, message fmt.Stringer)
|
||||||
|
Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// [file path][func name][level] -> [allowed]
|
||||||
|
type allowedContextCache map[string]map[string]map[LogLevel]bool
|
||||||
|
|
||||||
|
// commonLogger contains all common data needed for logging and contains methods used to log messages.
|
||||||
|
type commonLogger struct {
|
||||||
|
config *logConfig // Config used for logging
|
||||||
|
contextCache allowedContextCache // Caches whether log is enabled for specific "full path-func name-level" sets
|
||||||
|
closed bool // 'true' when all writers are closed, all data is flushed, logger is unusable. Must be accessed while holding closedM
|
||||||
|
closedM sync.RWMutex
|
||||||
|
m sync.Mutex // Mutex for main operations
|
||||||
|
unusedLevels []bool
|
||||||
|
innerLogger innerLoggerInterface
|
||||||
|
addStackDepth int // Additional stack depth needed for correct seelog caller context detection
|
||||||
|
customContext interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommonLogger(config *logConfig, internalLogger innerLoggerInterface) *commonLogger {
|
||||||
|
cLogger := new(commonLogger)
|
||||||
|
|
||||||
|
cLogger.config = config
|
||||||
|
cLogger.contextCache = make(allowedContextCache)
|
||||||
|
cLogger.unusedLevels = make([]bool, Off)
|
||||||
|
cLogger.fillUnusedLevels()
|
||||||
|
cLogger.innerLogger = internalLogger
|
||||||
|
|
||||||
|
return cLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) SetAdditionalStackDepth(depth int) error {
|
||||||
|
if depth < 0 {
|
||||||
|
return fmt.Errorf("negative depth: %d", depth)
|
||||||
|
}
|
||||||
|
cLogger.m.Lock()
|
||||||
|
cLogger.addStackDepth = depth
|
||||||
|
cLogger.m.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Tracef(format string, params ...interface{}) {
|
||||||
|
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Debugf(format string, params ...interface{}) {
|
||||||
|
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Infof(format string, params ...interface{}) {
|
||||||
|
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogFormattedMessage(format, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Warnf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Errorf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Criticalf(format string, params ...interface{}) error {
|
||||||
|
message := newLogFormattedMessage(format, params)
|
||||||
|
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Trace(v ...interface{}) {
|
||||||
|
cLogger.traceWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Debug(v ...interface{}) {
|
||||||
|
cLogger.debugWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Info(v ...interface{}) {
|
||||||
|
cLogger.infoWithCallDepth(loggerFuncCallDepth, newLogMessage(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Warn(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.warnWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Error(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.errorWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Critical(v ...interface{}) error {
|
||||||
|
message := newLogMessage(v)
|
||||||
|
cLogger.criticalWithCallDepth(loggerFuncCallDepth, message)
|
||||||
|
return errors.New(message.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) SetContext(c interface{}) {
|
||||||
|
cLogger.customContext = c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) traceWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(TraceLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) debugWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(DebugLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) infoWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(InfoLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) warnWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(WarnLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) errorWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(ErrorLvl, message, callDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) criticalWithCallDepth(callDepth int, message fmt.Stringer) {
|
||||||
|
cLogger.log(CriticalLvl, message, callDepth)
|
||||||
|
cLogger.innerLogger.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) Closed() bool {
|
||||||
|
cLogger.closedM.RLock()
|
||||||
|
defer cLogger.closedM.RUnlock()
|
||||||
|
return cLogger.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) fillUnusedLevels() {
|
||||||
|
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||||
|
cLogger.unusedLevels[i] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
cLogger.fillUnusedLevelsByContraint(cLogger.config.Constraints)
|
||||||
|
|
||||||
|
for _, exception := range cLogger.config.Exceptions {
|
||||||
|
cLogger.fillUnusedLevelsByContraint(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) fillUnusedLevelsByContraint(constraint logLevelConstraints) {
|
||||||
|
for i := 0; i < len(cLogger.unusedLevels); i++ {
|
||||||
|
if constraint.IsAllowed(LogLevel(i)) {
|
||||||
|
cLogger.unusedLevels[i] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stackCallDepth is used to indicate the call depth of 'log' func.
|
||||||
|
// This depth level is used in the runtime.Caller(...) call. See
|
||||||
|
// common_context.go -> specifyContext, extractCallerInfo for details.
|
||||||
|
func (cLogger *commonLogger) log(level LogLevel, message fmt.Stringer, stackCallDepth int) {
|
||||||
|
if cLogger.unusedLevels[level] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cLogger.m.Lock()
|
||||||
|
defer cLogger.m.Unlock()
|
||||||
|
|
||||||
|
if cLogger.Closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context, _ := specifyContext(stackCallDepth+cLogger.addStackDepth, cLogger.customContext)
|
||||||
|
// Context errors are not reported because there are situations
|
||||||
|
// in which context errors are normal Seelog usage cases. For
|
||||||
|
// example in executables with stripped symbols.
|
||||||
|
// Error contexts are returned instead. See common_context.go.
|
||||||
|
/*if err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
return
|
||||||
|
}*/
|
||||||
|
cLogger.innerLogger.innerLog(level, context, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) processLogMsg(level LogLevel, message fmt.Stringer, context LogContextInterface) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
reportInternalError(fmt.Errorf("recovered from panic during message processing: %s", err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if cLogger.config.IsAllowed(level, context) {
|
||||||
|
cLogger.config.RootDispatcher.Dispatch(message.String(), level, context, reportInternalError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cLogger *commonLogger) isAllowed(level LogLevel, context LogContextInterface) bool {
|
||||||
|
funcMap, ok := cLogger.contextCache[context.FullPath()]
|
||||||
|
if !ok {
|
||||||
|
funcMap = make(map[string]map[LogLevel]bool, 0)
|
||||||
|
cLogger.contextCache[context.FullPath()] = funcMap
|
||||||
|
}
|
||||||
|
|
||||||
|
levelMap, ok := funcMap[context.Func()]
|
||||||
|
if !ok {
|
||||||
|
levelMap = make(map[LogLevel]bool, 0)
|
||||||
|
funcMap[context.Func()] = levelMap
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllowValue, ok := levelMap[level]
|
||||||
|
if !ok {
|
||||||
|
isAllowValue = cLogger.config.IsAllowed(level, context)
|
||||||
|
levelMap[level] = isAllowValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAllowValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type logMessage struct {
|
||||||
|
params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type logFormattedMessage struct {
|
||||||
|
format string
|
||||||
|
params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogMessage(params []interface{}) fmt.Stringer {
|
||||||
|
message := new(logMessage)
|
||||||
|
|
||||||
|
message.params = params
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogFormattedMessage(format string, params []interface{}) *logFormattedMessage {
|
||||||
|
message := new(logFormattedMessage)
|
||||||
|
|
||||||
|
message.params = params
|
||||||
|
message.format = format
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (message *logMessage) String() string {
|
||||||
|
return fmt.Sprint(message.params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (message *logFormattedMessage) String() string {
|
||||||
|
return fmt.Sprintf(message.format, message.params...)
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bufferedWriter stores data in memory and flushes it every flushPeriod or when buffer is full
|
||||||
|
type bufferedWriter struct {
|
||||||
|
flushPeriod time.Duration // data flushes interval (in microseconds)
|
||||||
|
bufferMutex *sync.Mutex // mutex for buffer operations syncronization
|
||||||
|
innerWriter io.Writer // inner writer
|
||||||
|
buffer *bufio.Writer // buffered wrapper for inner writer
|
||||||
|
bufferSize int // max size of data chunk in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBufferedWriter creates a new buffered writer struct.
|
||||||
|
// bufferSize -- size of memory buffer in bytes
|
||||||
|
// flushPeriod -- period in which data flushes from memory buffer in milliseconds. 0 - turn off this functionality
|
||||||
|
func NewBufferedWriter(innerWriter io.Writer, bufferSize int, flushPeriod time.Duration) (*bufferedWriter, error) {
|
||||||
|
|
||||||
|
if innerWriter == nil {
|
||||||
|
return nil, errors.New("argument is nil: innerWriter")
|
||||||
|
}
|
||||||
|
if flushPeriod < 0 {
|
||||||
|
return nil, fmt.Errorf("flushPeriod can not be less than 0. Got: %d", flushPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bufferSize <= 0 {
|
||||||
|
return nil, fmt.Errorf("bufferSize can not be less or equal to 0. Got: %d", bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bufio.NewWriterSize(innerWriter, bufferSize)
|
||||||
|
|
||||||
|
/*if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}*/
|
||||||
|
|
||||||
|
newWriter := new(bufferedWriter)
|
||||||
|
|
||||||
|
newWriter.innerWriter = innerWriter
|
||||||
|
newWriter.buffer = buffer
|
||||||
|
newWriter.bufferSize = bufferSize
|
||||||
|
newWriter.flushPeriod = flushPeriod * 1e6
|
||||||
|
newWriter.bufferMutex = new(sync.Mutex)
|
||||||
|
|
||||||
|
if flushPeriod != 0 {
|
||||||
|
go newWriter.flushPeriodically()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) writeBigChunk(bytes []byte) (n int, err error) {
|
||||||
|
bufferedLen := bufWriter.buffer.Buffered()
|
||||||
|
|
||||||
|
n, err = bufWriter.flushInner()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
written, writeErr := bufWriter.innerWriter.Write(bytes)
|
||||||
|
return bufferedLen + written, writeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends data to buffer manager. Waits until all buffers are full.
|
||||||
|
func (bufWriter *bufferedWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bytesLen := len(bytes)
|
||||||
|
|
||||||
|
if bytesLen > bufWriter.bufferSize {
|
||||||
|
return bufWriter.writeBigChunk(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytesLen > bufWriter.buffer.Available() {
|
||||||
|
n, err = bufWriter.flushInner()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bufWriter.buffer.Write(bytes)
|
||||||
|
|
||||||
|
return len(bytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) Close() error {
|
||||||
|
closer, ok := bufWriter.innerWriter.(io.Closer)
|
||||||
|
if ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) Flush() {
|
||||||
|
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bufWriter.flushInner()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushInner() (n int, err error) {
|
||||||
|
bufferedLen := bufWriter.buffer.Buffered()
|
||||||
|
flushErr := bufWriter.buffer.Flush()
|
||||||
|
|
||||||
|
return bufWriter.buffer.Buffered() - bufferedLen, flushErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushBuffer() {
|
||||||
|
bufWriter.bufferMutex.Lock()
|
||||||
|
defer bufWriter.bufferMutex.Unlock()
|
||||||
|
|
||||||
|
bufWriter.buffer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) flushPeriodically() {
|
||||||
|
if bufWriter.flushPeriod > 0 {
|
||||||
|
ticker := time.NewTicker(bufWriter.flushPeriod)
|
||||||
|
for {
|
||||||
|
<-ticker.C
|
||||||
|
bufWriter.flushBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bufWriter *bufferedWriter) String() string {
|
||||||
|
return fmt.Sprintf("bufferedWriter size: %d, flushPeriod: %d", bufWriter.bufferSize, bufWriter.flushPeriod)
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connWriter is used to write to a stream-oriented network connection.
|
||||||
|
type connWriter struct {
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
reconnectOnMsg bool
|
||||||
|
reconnect bool
|
||||||
|
net string
|
||||||
|
addr string
|
||||||
|
useTLS bool
|
||||||
|
configTLS *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates writer to the address addr on the network netName.
|
||||||
|
// Connection will be opened on each write if reconnectOnMsg = true
|
||||||
|
func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter {
|
||||||
|
newWriter := new(connWriter)
|
||||||
|
|
||||||
|
newWriter.net = netName
|
||||||
|
newWriter.addr = addr
|
||||||
|
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||||
|
|
||||||
|
return newWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a writer that uses SSL/TLS
|
||||||
|
func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter {
|
||||||
|
newWriter := new(connWriter)
|
||||||
|
|
||||||
|
newWriter.net = netName
|
||||||
|
newWriter.addr = addr
|
||||||
|
newWriter.reconnectOnMsg = reconnectOnMsg
|
||||||
|
newWriter.useTLS = true
|
||||||
|
newWriter.configTLS = config
|
||||||
|
|
||||||
|
return newWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) Close() error {
|
||||||
|
if connWriter.innerWriter == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return connWriter.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
if connWriter.neededConnectOnMsg() {
|
||||||
|
err = connWriter.connect()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.reconnectOnMsg {
|
||||||
|
defer connWriter.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = connWriter.innerWriter.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
connWriter.reconnect = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) String() string {
|
||||||
|
return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) connect() error {
|
||||||
|
if connWriter.innerWriter != nil {
|
||||||
|
connWriter.innerWriter.Close()
|
||||||
|
connWriter.innerWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.useTLS {
|
||||||
|
conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
connWriter.innerWriter = conn
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial(connWriter.net, connWriter.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn, ok := conn.(*net.TCPConn)
|
||||||
|
if ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
connWriter.innerWriter = conn
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (connWriter *connWriter) neededConnectOnMsg() bool {
|
||||||
|
if connWriter.reconnect {
|
||||||
|
connWriter.reconnect = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if connWriter.innerWriter == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return connWriter.reconnectOnMsg
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// consoleWriter is used to write to console
|
||||||
|
type consoleWriter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new console writer. Returns error, if the console writer couldn't be created.
|
||||||
|
func NewConsoleWriter() (writer *consoleWriter, err error) {
|
||||||
|
newWriter := new(consoleWriter)
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create folder and file on WriteLog/Write first call
|
||||||
|
func (console *consoleWriter) Write(bytes []byte) (int, error) {
|
||||||
|
return fmt.Print(string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (console *consoleWriter) String() string {
|
||||||
|
return "Console writer"
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileWriter is used to write to a file.
|
||||||
|
type fileWriter struct {
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
fileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new file and a corresponding writer. Returns error, if the file couldn't be created.
|
||||||
|
func NewFileWriter(fileName string) (writer *fileWriter, err error) {
|
||||||
|
newWriter := new(fileWriter)
|
||||||
|
newWriter.fileName = fileName
|
||||||
|
|
||||||
|
return newWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) Close() error {
|
||||||
|
if fw.innerWriter != nil {
|
||||||
|
err := fw.innerWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fw.innerWriter = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create folder and file on WriteLog/Write first call
|
||||||
|
func (fw *fileWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
if fw.innerWriter == nil {
|
||||||
|
if err := fw.createFile(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fw.innerWriter.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) createFile() error {
|
||||||
|
folder, _ := filepath.Split(fw.fileName)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if 0 != len(folder) {
|
||||||
|
err = os.MkdirAll(folder, defaultDirectoryPermissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exists
|
||||||
|
fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *fileWriter) String() string {
|
||||||
|
return fmt.Sprintf("File writer: %s", fw.fileName)
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type formattedWriter struct {
|
||||||
|
writer io.Writer
|
||||||
|
formatter *formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) {
|
||||||
|
if formatter == nil {
|
||||||
|
return nil, errors.New("formatter can not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &formattedWriter{writer, formatter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error {
|
||||||
|
str := formattedWriter.formatter.Format(message, level, context)
|
||||||
|
_, err := formattedWriter.writer.Write([]byte(str))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) String() string {
|
||||||
|
return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Writer() io.Writer {
|
||||||
|
return formattedWriter.writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formattedWriter *formattedWriter) Format() *formatter {
|
||||||
|
return formattedWriter.formatter
|
||||||
|
}
|
|
@ -0,0 +1,763 @@
|
||||||
|
// Copyright (c) 2013 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cihub/seelog/archive"
|
||||||
|
"github.com/cihub/seelog/archive/gzip"
|
||||||
|
"github.com/cihub/seelog/archive/tar"
|
||||||
|
"github.com/cihub/seelog/archive/zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Common constants
|
||||||
|
const (
|
||||||
|
rollingLogHistoryDelimiter = "."
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of the rolling writer: roll by date, by time, etc.
|
||||||
|
type rollingType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingTypeSize = iota
|
||||||
|
rollingTypeTime
|
||||||
|
)
|
||||||
|
|
||||||
|
// Types of the rolled file naming mode: prefix, postfix, etc.
|
||||||
|
type rollingNameMode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingNameModePostfix = iota
|
||||||
|
rollingNameModePrefix
|
||||||
|
)
|
||||||
|
|
||||||
|
var rollingNameModesStringRepresentation = map[rollingNameMode]string{
|
||||||
|
rollingNameModePostfix: "postfix",
|
||||||
|
rollingNameModePrefix: "prefix",
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingNameModeFromString(rollingNameStr string) (rollingNameMode, bool) {
|
||||||
|
for tp, tpStr := range rollingNameModesStringRepresentation {
|
||||||
|
if tpStr == rollingNameStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var rollingTypesStringRepresentation = map[rollingType]string{
|
||||||
|
rollingTypeSize: "size",
|
||||||
|
rollingTypeTime: "date",
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingTypeFromString(rollingTypeStr string) (rollingType, bool) {
|
||||||
|
for tp, tpStr := range rollingTypesStringRepresentation {
|
||||||
|
if tpStr == rollingTypeStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old logs archivation type.
|
||||||
|
type rollingArchiveType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
rollingArchiveNone = iota
|
||||||
|
rollingArchiveZip
|
||||||
|
rollingArchiveGzip
|
||||||
|
)
|
||||||
|
|
||||||
|
var rollingArchiveTypesStringRepresentation = map[rollingArchiveType]string{
|
||||||
|
rollingArchiveNone: "none",
|
||||||
|
rollingArchiveZip: "zip",
|
||||||
|
rollingArchiveGzip: "gzip",
|
||||||
|
}
|
||||||
|
|
||||||
|
type archiver func(f *os.File, exploded bool) archive.WriteCloser
|
||||||
|
|
||||||
|
type unarchiver func(f *os.File) (archive.ReadCloser, error)
|
||||||
|
|
||||||
|
type compressionType struct {
|
||||||
|
extension string
|
||||||
|
handleMultipleEntries bool
|
||||||
|
archiver archiver
|
||||||
|
unarchiver unarchiver
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressionTypes = map[rollingArchiveType]compressionType{
|
||||||
|
rollingArchiveZip: {
|
||||||
|
extension: ".zip",
|
||||||
|
handleMultipleEntries: true,
|
||||||
|
archiver: func(f *os.File, _ bool) archive.WriteCloser {
|
||||||
|
return zip.NewWriter(f)
|
||||||
|
},
|
||||||
|
unarchiver: func(f *os.File) (archive.ReadCloser, error) {
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r, err := zip.NewReader(f, fi.Size())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return archive.NopCloser(r), nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rollingArchiveGzip: {
|
||||||
|
extension: ".gz",
|
||||||
|
handleMultipleEntries: false,
|
||||||
|
archiver: func(f *os.File, exploded bool) archive.WriteCloser {
|
||||||
|
gw := gzip.NewWriter(f)
|
||||||
|
if exploded {
|
||||||
|
return gw
|
||||||
|
}
|
||||||
|
return tar.NewWriteMultiCloser(gw, gw)
|
||||||
|
},
|
||||||
|
unarchiver: func(f *os.File) (archive.ReadCloser, error) {
|
||||||
|
gr, err := gzip.NewReader(f, f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if the gzip is a tar
|
||||||
|
tr := tar.NewReader(gr)
|
||||||
|
_, err = tr.Next()
|
||||||
|
isTar := err == nil
|
||||||
|
|
||||||
|
// Reset to beginning of file
|
||||||
|
if _, err := f.Seek(0, os.SEEK_SET); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gr.Reset(f)
|
||||||
|
|
||||||
|
if isTar {
|
||||||
|
return archive.NopCloser(tar.NewReader(gr)), nil
|
||||||
|
}
|
||||||
|
return gr, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (compressionType *compressionType) rollingArchiveTypeName(name string, exploded bool) string {
|
||||||
|
if !compressionType.handleMultipleEntries && !exploded {
|
||||||
|
return name + ".tar" + compressionType.extension
|
||||||
|
} else {
|
||||||
|
return name + compressionType.extension
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func rollingArchiveTypeFromString(rollingArchiveTypeStr string) (rollingArchiveType, bool) {
|
||||||
|
for tp, tpStr := range rollingArchiveTypesStringRepresentation {
|
||||||
|
if tpStr == rollingArchiveTypeStr {
|
||||||
|
return tp, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default names for different archive types
|
||||||
|
var rollingArchiveDefaultExplodedName = "old"
|
||||||
|
|
||||||
|
func rollingArchiveTypeDefaultName(archiveType rollingArchiveType, exploded bool) (string, error) {
|
||||||
|
compressionType, ok := compressionTypes[archiveType]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("cannot get default filename for archive type = %v", archiveType)
|
||||||
|
}
|
||||||
|
return compressionType.rollingArchiveTypeName("log", exploded), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollerVirtual is an interface that represents all virtual funcs that are
|
||||||
|
// called in different rolling writer subtypes.
|
||||||
|
type rollerVirtual interface {
|
||||||
|
needsToRoll() bool // Returns true if needs to switch to another file.
|
||||||
|
isFileRollNameValid(rname string) bool // Returns true if logger roll file name (postfix/prefix/etc.) is ok.
|
||||||
|
sortFileRollNamesAsc(fs []string) ([]string, error) // Sorts logger roll file names in ascending order of their creation by logger.
|
||||||
|
|
||||||
|
// getNewHistoryRollFileName is called whenever we are about to roll the
|
||||||
|
// current log file. It returns the name the current log file should be
|
||||||
|
// rolled to.
|
||||||
|
getNewHistoryRollFileName(otherHistoryFiles []string) string
|
||||||
|
|
||||||
|
getCurrentFileName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollingFileWriter writes received messages to a file, until time interval passes
|
||||||
|
// or file exceeds a specified limit. After that the current log file is renamed
|
||||||
|
// and writer starts to log into a new file. You can set a limit for such renamed
|
||||||
|
// files count, if you want, and then the rolling writer would delete older ones when
|
||||||
|
// the files count exceed the specified limit.
|
||||||
|
type rollingFileWriter struct {
|
||||||
|
fileName string // log file name
|
||||||
|
currentDirPath string
|
||||||
|
currentFile *os.File
|
||||||
|
currentName string
|
||||||
|
currentFileSize int64
|
||||||
|
rollingType rollingType // Rolling mode (Files roll by size/date/...)
|
||||||
|
archiveType rollingArchiveType
|
||||||
|
archivePath string
|
||||||
|
archiveExploded bool
|
||||||
|
fullName bool
|
||||||
|
maxRolls int
|
||||||
|
nameMode rollingNameMode
|
||||||
|
self rollerVirtual // Used for virtual calls
|
||||||
|
rollLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRollingFileWriter(fpath string, rtype rollingType, atype rollingArchiveType, apath string, maxr int, namemode rollingNameMode,
|
||||||
|
archiveExploded bool, fullName bool) (*rollingFileWriter, error) {
|
||||||
|
rw := new(rollingFileWriter)
|
||||||
|
rw.currentDirPath, rw.fileName = filepath.Split(fpath)
|
||||||
|
if len(rw.currentDirPath) == 0 {
|
||||||
|
rw.currentDirPath = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.rollingType = rtype
|
||||||
|
rw.archiveType = atype
|
||||||
|
rw.archivePath = apath
|
||||||
|
rw.nameMode = namemode
|
||||||
|
rw.maxRolls = maxr
|
||||||
|
rw.archiveExploded = archiveExploded
|
||||||
|
rw.fullName = fullName
|
||||||
|
return rw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) hasRollName(file string) bool {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
rname := rw.fileName + rollingLogHistoryDelimiter
|
||||||
|
return strings.HasPrefix(file, rname)
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
rname := rollingLogHistoryDelimiter + rw.fileName
|
||||||
|
return strings.HasSuffix(file, rname)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) createFullFileName(originalName, rollname string) string {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
return originalName + rollingLogHistoryDelimiter + rollname
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
return rollname + rollingLogHistoryDelimiter + originalName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) getSortedLogHistory() ([]string, error) {
|
||||||
|
files, err := getDirFilePaths(rw.currentDirPath, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var validRollNames []string
|
||||||
|
for _, file := range files {
|
||||||
|
if rw.hasRollName(file) {
|
||||||
|
rname := rw.getFileRollName(file)
|
||||||
|
if rw.self.isFileRollNameValid(rname) {
|
||||||
|
validRollNames = append(validRollNames, rname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sortedTails, err := rw.self.sortFileRollNamesAsc(validRollNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
validSortedFiles := make([]string, len(sortedTails))
|
||||||
|
for i, v := range sortedTails {
|
||||||
|
validSortedFiles[i] = rw.createFullFileName(rw.fileName, v)
|
||||||
|
}
|
||||||
|
return validSortedFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) createFileAndFolderIfNeeded(first bool) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(rw.currentDirPath) != 0 {
|
||||||
|
err = os.MkdirAll(rw.currentDirPath, defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rw.currentName = rw.self.getCurrentFileName()
|
||||||
|
filePath := filepath.Join(rw.currentDirPath, rw.currentName)
|
||||||
|
|
||||||
|
// This will either open the existing file (without truncating it) or
|
||||||
|
// create if necessary. Append mode avoids any race conditions.
|
||||||
|
rw.currentFile, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := rw.currentFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
rw.currentFile.Close()
|
||||||
|
rw.currentFile = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.currentFileSize = stat.Size()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) archiveExplodedLogs(logFilename string, compressionType compressionType) (err error) {
|
||||||
|
closeWithError := func(c io.Closer) {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rollPath := filepath.Join(rw.currentDirPath, logFilename)
|
||||||
|
src, err := os.Open(rollPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
|
||||||
|
// Buffer to a temporary file on the same partition
|
||||||
|
// Note: archivePath is a path to a directory when handling exploded logs
|
||||||
|
dst, err := rw.tempArchiveFile(rw.archivePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeWithError(dst)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize archive by swapping the buffered archive into place
|
||||||
|
err = os.Rename(dst.Name(), filepath.Join(rw.archivePath,
|
||||||
|
compressionType.rollingArchiveTypeName(logFilename, true)))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// archive entry
|
||||||
|
w := compressionType.archiver(dst, true)
|
||||||
|
defer closeWithError(w)
|
||||||
|
fi, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.NextFile(logFilename, fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(w, src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) archiveUnexplodedLogs(compressionType compressionType, rollsToDelete int, history []string) (err error) {
|
||||||
|
closeWithError := func(c io.Closer) {
|
||||||
|
if cerr := c.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer to a temporary file on the same partition
|
||||||
|
// Note: archivePath is a path to a file when handling unexploded logs
|
||||||
|
dst, err := rw.tempArchiveFile(filepath.Dir(rw.archivePath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeWithError(dst)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst.Name()) // Can't do anything when we fail to remove temp file
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize archive by moving the buffered archive into place
|
||||||
|
err = os.Rename(dst.Name(), rw.archivePath)
|
||||||
|
}()
|
||||||
|
|
||||||
|
w := compressionType.archiver(dst, false)
|
||||||
|
defer closeWithError(w)
|
||||||
|
|
||||||
|
src, err := os.Open(rw.archivePath)
|
||||||
|
switch {
|
||||||
|
// Archive exists
|
||||||
|
case err == nil:
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
|
||||||
|
r, err := compressionType.unarchiver(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close() // Read-only
|
||||||
|
|
||||||
|
if err := archive.Copy(w, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed to stat
|
||||||
|
case !os.IsNotExist(err):
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new files to the archive
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
rollPath := filepath.Join(rw.currentDirPath, history[i])
|
||||||
|
src, err := os.Open(rollPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer src.Close() // Read-only
|
||||||
|
fi, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.NextFile(src.Name(), fi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) deleteOldRolls(history []string) error {
|
||||||
|
if rw.maxRolls <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rollsToDelete := len(history) - rw.maxRolls
|
||||||
|
if rollsToDelete <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rw.archiveType != rollingArchiveNone {
|
||||||
|
if rw.archiveExploded {
|
||||||
|
os.MkdirAll(rw.archivePath, defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
// Archive logs
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
rw.archiveExplodedLogs(history[i], compressionTypes[rw.archiveType])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.MkdirAll(filepath.Dir(rw.archivePath), defaultDirectoryPermissions)
|
||||||
|
|
||||||
|
rw.archiveUnexplodedLogs(compressionTypes[rw.archiveType], rollsToDelete, history)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// In all cases (archive files or not) the files should be deleted.
|
||||||
|
for i := 0; i < rollsToDelete; i++ {
|
||||||
|
// Try best to delete files without breaking the loop.
|
||||||
|
if err = tryRemoveFile(filepath.Join(rw.currentDirPath, history[i])); err != nil {
|
||||||
|
reportInternalError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) getFileRollName(fileName string) string {
|
||||||
|
switch rw.nameMode {
|
||||||
|
case rollingNameModePostfix:
|
||||||
|
return fileName[len(rw.fileName+rollingLogHistoryDelimiter):]
|
||||||
|
case rollingNameModePrefix:
|
||||||
|
return fileName[:len(fileName)-len(rw.fileName+rollingLogHistoryDelimiter)]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) roll() error {
|
||||||
|
// First, close current file.
|
||||||
|
err := rw.currentFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rw.currentFile = nil
|
||||||
|
|
||||||
|
// Current history of all previous log files.
|
||||||
|
// For file roller it may be like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.4
|
||||||
|
// * file.log.5
|
||||||
|
// * file.log.6
|
||||||
|
//
|
||||||
|
// For date roller it may look like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.11.Aug.13
|
||||||
|
// * file.log.15.Aug.13
|
||||||
|
// * file.log.16.Aug.13
|
||||||
|
// Sorted log history does NOT include current file.
|
||||||
|
history, err := rw.getSortedLogHistory()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Renames current file to create a new roll history entry
|
||||||
|
// For file roller it may be like this:
|
||||||
|
// * ...
|
||||||
|
// * file.log.4
|
||||||
|
// * file.log.5
|
||||||
|
// * file.log.6
|
||||||
|
// n file.log.7 <---- RENAMED (from file.log)
|
||||||
|
newHistoryName := rw.createFullFileName(rw.fileName,
|
||||||
|
rw.self.getNewHistoryRollFileName(history))
|
||||||
|
|
||||||
|
err = os.Rename(filepath.Join(rw.currentDirPath, rw.currentName), filepath.Join(rw.currentDirPath, newHistoryName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, add the newly added history file to the history archive
|
||||||
|
// and, if after that the archive exceeds the allowed max limit, older rolls
|
||||||
|
// must the removed/archived.
|
||||||
|
history = append(history, newHistoryName)
|
||||||
|
if len(history) > rw.maxRolls {
|
||||||
|
err = rw.deleteOldRolls(history)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) Write(bytes []byte) (n int, err error) {
|
||||||
|
rw.rollLock.Lock()
|
||||||
|
defer rw.rollLock.Unlock()
|
||||||
|
|
||||||
|
if rw.self.needsToRoll() {
|
||||||
|
if err := rw.roll(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rw.currentFile == nil {
|
||||||
|
err := rw.createFileAndFolderIfNeeded(true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = rw.currentFile.Write(bytes)
|
||||||
|
rw.currentFileSize += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) Close() error {
|
||||||
|
if rw.currentFile != nil {
|
||||||
|
e := rw.currentFile.Close()
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
rw.currentFile = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *rollingFileWriter) tempArchiveFile(archiveDir string) (*os.File, error) {
|
||||||
|
tmp := filepath.Join(archiveDir, ".seelog_tmp")
|
||||||
|
if err := os.MkdirAll(tmp, defaultDirectoryPermissions); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ioutil.TempFile(tmp, "archived_logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================================
|
||||||
|
// Different types of rolling writers
|
||||||
|
// =============================================================================================
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Rolling writer by SIZE
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// rollingFileWriterSize performs roll when file exceeds a specified limit.
|
||||||
|
type rollingFileWriterSize struct {
|
||||||
|
*rollingFileWriter
|
||||||
|
maxFileSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRollingFileWriterSize(fpath string, atype rollingArchiveType, apath string, maxSize int64, maxRolls int, namemode rollingNameMode, archiveExploded bool) (*rollingFileWriterSize, error) {
|
||||||
|
rw, err := newRollingFileWriter(fpath, rollingTypeSize, atype, apath, maxRolls, namemode, archiveExploded, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rws := &rollingFileWriterSize{rw, maxSize}
|
||||||
|
rws.self = rws
|
||||||
|
return rws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) needsToRoll() bool {
|
||||||
|
return rws.currentFileSize >= rws.maxFileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) isFileRollNameValid(rname string) bool {
|
||||||
|
if len(rname) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := strconv.Atoi(rname)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rollSizeFileTailsSlice []string
|
||||||
|
|
||||||
|
func (p rollSizeFileTailsSlice) Len() int {
|
||||||
|
return len(p)
|
||||||
|
}
|
||||||
|
func (p rollSizeFileTailsSlice) Less(i, j int) bool {
|
||||||
|
v1, _ := strconv.Atoi(p[i])
|
||||||
|
v2, _ := strconv.Atoi(p[j])
|
||||||
|
return v1 < v2
|
||||||
|
}
|
||||||
|
func (p rollSizeFileTailsSlice) Swap(i, j int) {
|
||||||
|
p[i], p[j] = p[j], p[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||||
|
ss := rollSizeFileTailsSlice(fs)
|
||||||
|
sort.Sort(ss)
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) getNewHistoryRollFileName(otherLogFiles []string) string {
|
||||||
|
v := 0
|
||||||
|
if len(otherLogFiles) != 0 {
|
||||||
|
latest := otherLogFiles[len(otherLogFiles)-1]
|
||||||
|
v, _ = strconv.Atoi(rws.getFileRollName(latest))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d", v+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) getCurrentFileName() string {
|
||||||
|
return rws.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rws *rollingFileWriterSize) String() string {
|
||||||
|
return fmt.Sprintf("Rolling file writer (By SIZE): filename: %s, archive: %s, archivefile: %s, maxFileSize: %v, maxRolls: %v",
|
||||||
|
rws.fileName,
|
||||||
|
rollingArchiveTypesStringRepresentation[rws.archiveType],
|
||||||
|
rws.archivePath,
|
||||||
|
rws.maxFileSize,
|
||||||
|
rws.maxRolls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// Rolling writer by TIME
|
||||||
|
// --------------------------------------------------
|
||||||
|
|
||||||
|
// rollingFileWriterTime performs roll when a specified time interval has passed.
|
||||||
|
type rollingFileWriterTime struct {
|
||||||
|
*rollingFileWriter
|
||||||
|
timePattern string
|
||||||
|
currentTimeFileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRollingFileWriterTime(fpath string, atype rollingArchiveType, apath string, maxr int,
|
||||||
|
timePattern string, namemode rollingNameMode, archiveExploded bool, fullName bool) (*rollingFileWriterTime, error) {
|
||||||
|
|
||||||
|
rw, err := newRollingFileWriter(fpath, rollingTypeTime, atype, apath, maxr, namemode, archiveExploded, fullName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rws := &rollingFileWriterTime{rw, timePattern, ""}
|
||||||
|
rws.self = rws
|
||||||
|
return rws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) needsToRoll() bool {
|
||||||
|
newName := time.Now().Format(rwt.timePattern)
|
||||||
|
|
||||||
|
if rwt.currentTimeFileName == "" {
|
||||||
|
// first run; capture the current name
|
||||||
|
rwt.currentTimeFileName = newName
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return newName != rwt.currentTimeFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) isFileRollNameValid(rname string) bool {
|
||||||
|
if len(rname) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := time.ParseInLocation(rwt.timePattern, rname, time.Local)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type rollTimeFileTailsSlice struct {
|
||||||
|
data []string
|
||||||
|
pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Len() int {
|
||||||
|
return len(p.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Less(i, j int) bool {
|
||||||
|
t1, _ := time.ParseInLocation(p.pattern, p.data[i], time.Local)
|
||||||
|
t2, _ := time.ParseInLocation(p.pattern, p.data[j], time.Local)
|
||||||
|
return t1.Before(t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p rollTimeFileTailsSlice) Swap(i, j int) {
|
||||||
|
p.data[i], p.data[j] = p.data[j], p.data[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) sortFileRollNamesAsc(fs []string) ([]string, error) {
|
||||||
|
ss := rollTimeFileTailsSlice{data: fs, pattern: rwt.timePattern}
|
||||||
|
sort.Sort(ss)
|
||||||
|
return ss.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) getNewHistoryRollFileName(_ []string) string {
|
||||||
|
newFileName := rwt.currentTimeFileName
|
||||||
|
rwt.currentTimeFileName = time.Now().Format(rwt.timePattern)
|
||||||
|
return newFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) getCurrentFileName() string {
|
||||||
|
if rwt.fullName {
|
||||||
|
return rwt.createFullFileName(rwt.fileName, time.Now().Format(rwt.timePattern))
|
||||||
|
}
|
||||||
|
return rwt.fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwt *rollingFileWriterTime) String() string {
|
||||||
|
return fmt.Sprintf("Rolling file writer (By TIME): filename: %s, archive: %s, archivefile: %s, pattern: %s, maxRolls: %v",
|
||||||
|
rwt.fileName,
|
||||||
|
rollingArchiveTypesStringRepresentation[rwt.archiveType],
|
||||||
|
rwt.archivePath,
|
||||||
|
rwt.timePattern,
|
||||||
|
rwt.maxRolls)
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
// Copyright (c) 2012 - Cloud Instruments Co., Ltd.
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package seelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/smtp"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Default subject phrase for sending emails.
|
||||||
|
DefaultSubjectPhrase = "Diagnostic message from server: "
|
||||||
|
|
||||||
|
// Message subject pattern composed according to RFC 5321.
|
||||||
|
rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// smtpWriter is used to send emails via given SMTP-server.
|
||||||
|
type smtpWriter struct {
|
||||||
|
auth smtp.Auth
|
||||||
|
hostName string
|
||||||
|
hostPort string
|
||||||
|
hostNameWithPort string
|
||||||
|
senderAddress string
|
||||||
|
senderName string
|
||||||
|
recipientAddresses []string
|
||||||
|
caCertDirPaths []string
|
||||||
|
mailHeaders []string
|
||||||
|
subject string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPWriter returns a new SMTP-writer.
|
||||||
|
func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter {
|
||||||
|
return &smtpWriter{
|
||||||
|
auth: smtp.PlainAuth("", un, pwd, hn),
|
||||||
|
hostName: hn,
|
||||||
|
hostPort: hp,
|
||||||
|
hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp),
|
||||||
|
senderAddress: sa,
|
||||||
|
senderName: sn,
|
||||||
|
recipientAddresses: ras,
|
||||||
|
caCertDirPaths: cacdps,
|
||||||
|
subject: subj,
|
||||||
|
mailHeaders: headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte {
|
||||||
|
headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject)
|
||||||
|
// Build header lines if configured.
|
||||||
|
if headers != nil && len(headers) > 0 {
|
||||||
|
headerLines += strings.Join(headers, "\n")
|
||||||
|
headerLines += "\n"
|
||||||
|
}
|
||||||
|
return append([]byte(headerLines), body...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTLSConfig gets paths of PEM files with certificates,
|
||||||
|
// host server name and tries to create an appropriate TLS.Config.
|
||||||
|
func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) {
|
||||||
|
if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 {
|
||||||
|
err = errors.New("invalid PEM file paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pemEncodedContent := []byte{}
|
||||||
|
var (
|
||||||
|
e error
|
||||||
|
bytes []byte
|
||||||
|
)
|
||||||
|
// Create a file-filter-by-extension, set aside non-pem files.
|
||||||
|
pemFilePathFilter := func(fp string) bool {
|
||||||
|
if filepath.Ext(fp) == ".pem" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, pemFileDirPath := range pemFileDirPaths {
|
||||||
|
pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put together all the PEM files to decode them as a whole byte slice.
|
||||||
|
for _, pfp := range pemFilePaths {
|
||||||
|
if bytes, e = ioutil.ReadFile(pfp); e == nil {
|
||||||
|
pemEncodedContent = append(pemEncodedContent, bytes...)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName}
|
||||||
|
isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent)
|
||||||
|
if !isAppended {
|
||||||
|
// Extract this into a separate error.
|
||||||
|
err = errors.New("invalid PEM content")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMail accepts TLS configuration, connects to the server at addr,
|
||||||
|
// switches to TLS if possible, authenticates with mechanism a if possible,
|
||||||
|
// and then sends an email from address from, to addresses to, with message msg.
|
||||||
|
func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error {
|
||||||
|
c, err := smtp.Dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if the server supports STARTTLS extension.
|
||||||
|
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||||
|
if err = c.StartTLS(config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if the server supports AUTH extension and use given smtp.Auth.
|
||||||
|
if a != nil {
|
||||||
|
if isSupported, _ := c.Extension("AUTH"); isSupported {
|
||||||
|
if err = c.Auth(a); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Portion of code from the official smtp.SendMail function,
|
||||||
|
// see http://golang.org/src/pkg/net/smtp/smtp.go.
|
||||||
|
if err = c.Mail(from); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, addr := range to {
|
||||||
|
if err = c.Rcpt(addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write pushes a text message properly composed according to RFC 5321
|
||||||
|
// to a post server, which sends it to the recipients.
|
||||||
|
func (smtpw *smtpWriter) Write(data []byte) (int, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if smtpw.caCertDirPaths == nil {
|
||||||
|
err = smtp.SendMail(
|
||||||
|
smtpw.hostNameWithPort,
|
||||||
|
smtpw.auth,
|
||||||
|
smtpw.senderAddress,
|
||||||
|
smtpw.recipientAddresses,
|
||||||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName)
|
||||||
|
if e != nil {
|
||||||
|
return 0, e
|
||||||
|
}
|
||||||
|
err = sendMailWithTLSConfig(
|
||||||
|
config,
|
||||||
|
smtpw.hostNameWithPort,
|
||||||
|
smtpw.auth,
|
||||||
|
smtpw.senderAddress,
|
||||||
|
smtpw.recipientAddresses,
|
||||||
|
prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes down SMTP-connection.
|
||||||
|
func (smtpw *smtpWriter) Close() error {
|
||||||
|
// Do nothing as Write method opens and closes connection automatically.
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
go-logger 是golang 的日志库 ,基于对golang内置log的封装。
|
||||||
|
用法类似java日志工具包log4j
|
||||||
|
|
||||||
|
打印日志有5个方法 Debug,Info,Warn, Error ,Fatal 日志级别由低到高
|
||||||
|
|
||||||
|
设置日志级别的方法为:logger.SetLevel() 如:logger.SetLevel(logger.WARN)
|
||||||
|
则:logger.Debug(....),logger.Info(...) 日志不会打出,而
|
||||||
|
logger.Warn(...),logger.Error(...),logger.Fatal(...)日志会打出。
|
||||||
|
设置日志级别的参数有7个,分别为:ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF
|
||||||
|
其中 ALL表示所有调用打印日志的方法都会打出,而OFF则表示都不会打出。
|
||||||
|
|
||||||
|
|
||||||
|
日志文件切割有两种类型:1为按日期切分。2为按日志大小切分。
|
||||||
|
按日期切分时:每天一个备份日志文件,后缀为 .yyyy-MM-dd
|
||||||
|
过0点是生成前一天备份文件
|
||||||
|
|
||||||
|
按大小切分是需要3个参数,1为文件大小,2为单位,3为文件数量
|
||||||
|
文件增长到指定限值时,生成备份文件,结尾为依次递增的自然数。
|
||||||
|
文件数量增长到指定限制时,新生成的日志文件将覆盖前面生成的同名的备份日志文件。
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
//指定是否控制台打印,默认为true
|
||||||
|
logger.SetConsole(true)
|
||||||
|
//指定日志文件备份方式为文件大小的方式
|
||||||
|
//第一个参数为日志文件存放目录
|
||||||
|
//第二个参数为日志文件命名
|
||||||
|
//第三个参数为备份文件最大数量
|
||||||
|
//第四个参数为备份文件大小
|
||||||
|
//第五个参数为文件大小的单位 KB,MB,GB TB
|
||||||
|
//logger.SetRollingFile("d:/logtest", "test.log", 10, 5, logger.KB)
|
||||||
|
|
||||||
|
//指定日志文件备份方式为日期的方式
|
||||||
|
//第一个参数为日志文件存放目录
|
||||||
|
//第二个参数为日志文件命名
|
||||||
|
logger.SetRollingDaily("d:/logtest", "test.log")
|
||||||
|
|
||||||
|
//指定日志级别 ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF 级别由低到高
|
||||||
|
//一般习惯是测试阶段为debug,生成环境为info以上
|
||||||
|
logger.SetLevel(logger.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
打印日志:
|
||||||
|
func log(i int) {
|
||||||
|
logger.Debug("Debug>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i))
|
||||||
|
logger.Info("Info>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i))
|
||||||
|
logger.Warn("Warn>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i))
|
||||||
|
logger.Error("Error>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i))
|
||||||
|
logger.Fatal("Fatal>>>>>>>>>>>>>>>>>>>>>>>>>" + strconv.Itoa(i))
|
||||||
|
}
|
|
@ -8,6 +8,42 @@
|
||||||
"revision": "0db4a625e949e956314d7d1adea9bf82384cc10c",
|
"revision": "0db4a625e949e956314d7d1adea9bf82384cc10c",
|
||||||
"revisionTime": "2017-02-13T07:20:14Z"
|
"revisionTime": "2017-02-13T07:20:14Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "1bK29RcAjCAMCYS2HS3O18w4m8k=",
|
||||||
|
"path": "github.com/cihub/seelog",
|
||||||
|
"revision": "f561c5e57575bb1e0a2167028b7339b3a8d16fb4",
|
||||||
|
"revisionTime": "2017-01-30T13:45:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "iMU6SM/jawaCOJnyvR5/w8C/C1w=",
|
||||||
|
"path": "github.com/cihub/seelog/archive",
|
||||||
|
"revision": "f561c5e57575bb1e0a2167028b7339b3a8d16fb4",
|
||||||
|
"revisionTime": "2017-01-30T13:45:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "oSd7EVei192qS3uYyCAMx9zV3Y8=",
|
||||||
|
"path": "github.com/cihub/seelog/archive/gzip",
|
||||||
|
"revision": "f561c5e57575bb1e0a2167028b7339b3a8d16fb4",
|
||||||
|
"revisionTime": "2017-01-30T13:45:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "emesF9mCsIQC59VUE2lvjZYpAHU=",
|
||||||
|
"path": "github.com/cihub/seelog/archive/tar",
|
||||||
|
"revision": "f561c5e57575bb1e0a2167028b7339b3a8d16fb4",
|
||||||
|
"revisionTime": "2017-01-30T13:45:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "TXLSkZsRlMifg3mg9+6QgEZJ6m4=",
|
||||||
|
"path": "github.com/cihub/seelog/archive/zip",
|
||||||
|
"revision": "f561c5e57575bb1e0a2167028b7339b3a8d16fb4",
|
||||||
|
"revisionTime": "2017-01-30T13:45:32Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "foY4H6dyBE6AmY0uTZ2rfH+34Q4=",
|
||||||
|
"path": "github.com/donnie4w/go-logger",
|
||||||
|
"revision": "34aa9bcfff225db756df097fc49ffd35b68412bb",
|
||||||
|
"revisionTime": "2016-05-29T15:11:07Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "OkqfwXeTVoiIxNMDA7HKvmrCDw8=",
|
"checksumSHA1": "OkqfwXeTVoiIxNMDA7HKvmrCDw8=",
|
||||||
"path": "github.com/go-macaron/binding",
|
"path": "github.com/go-macaron/binding",
|
||||||
|
@ -60,10 +96,6 @@
|
||||||
"revision": "e8fbd41c16b9c0468dbae8db2fe0161a21265b8a",
|
"revision": "e8fbd41c16b9c0468dbae8db2fe0161a21265b8a",
|
||||||
"revisionTime": "2017-02-21T11:08:50Z"
|
"revisionTime": "2017-02-21T11:08:50Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "github.com/go-yaml/yaml",
|
|
||||||
"revision": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"checksumSHA1": "iKPMvbAueGfdyHcWCgzwKzm8WVo=",
|
"checksumSHA1": "iKPMvbAueGfdyHcWCgzwKzm8WVo=",
|
||||||
"path": "github.com/klauspost/cpuid",
|
"path": "github.com/klauspost/cpuid",
|
||||||
|
|
Loading…
Reference in New Issue