diff --git a/cmd/web.go b/cmd/web.go index 2e0d894..aebade6 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -26,10 +26,16 @@ var CmdWeb = cli.Command{ Value: DefaultPort, Usage: "bind port number", }, + cli.StringFlag{ + Name: "env,e", + Value: "dev", + Usage: "runtime environment, dev|test|prod", + }, }, } func run(ctx *cli.Context) { + setEnvironment(ctx) app.InitEnv() m := macaron.Classic() // 注册路由 @@ -64,7 +70,7 @@ func registerMiddleware(m *macaron.Macaron) { // 解析端口 func parsePort(ctx *cli.Context) int { - var port int + var port int = DefaultPort if ctx.IsSet("port") { port = ctx.Int("port") } @@ -74,3 +80,14 @@ func parsePort(ctx *cli.Context) int { 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 + } +} \ No newline at end of file diff --git a/modules/ansible/host.go b/modules/ansible/host.go index ea1d108..a32d1f4 100644 --- a/modules/ansible/host.go +++ b/modules/ansible/host.go @@ -3,10 +3,10 @@ package ansible import ( "bytes" "github.com/ouqiang/cron-scheduler/models" - "github.com/ouqiang/cron-scheduler/modules/utils" "io/ioutil" "strconv" "sync" + "github.com/ouqiang/cron-scheduler/modules/logger" ) // 主机名 @@ -37,11 +37,11 @@ func (h *Hosts) Write() { host := new(models.Host) hostModels, err := host.List() if err != nil { - utils.RecordLog(err) + logger.Error(err) return } if len(hostModels) == 0 { - utils.RecordLog("hosts内容为空") + logger.Info("hosts内容为空") return } buffer := bytes.Buffer{} diff --git a/modules/app/app.go b/modules/app/app.go index ce39ade..eba87ed 100644 --- a/modules/app/app.go +++ b/modules/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/ouqiang/cron-scheduler/modules/utils" "github.com/ouqiang/cron-scheduler/service" "github.com/ouqiang/cron-scheduler/modules/setting" + "github.com/ouqiang/cron-scheduler/modules/logger" ) var ( @@ -23,6 +24,7 @@ var ( ) func InitEnv() { + logger.InitLogger() CheckEnv() wd, err := os.Getwd() if err != nil { @@ -58,15 +60,11 @@ func IsInstalled() bool { func CheckEnv() { // ansible不支持安装在windows上, windows只能作为被控机 if runtime.GOOS == "windows" { - panic("不支持在windows上运行") + logger.Fatal("不支持在windows上运行") } _, err := utils.ExecShell("ansible", "--version") if err != nil { - panic(err) - } - _, err = utils.ExecShell("ansible-playbook", "--version") - if err != nil { - panic(err) + logger.Fatal(err) } } @@ -74,7 +72,7 @@ func CheckEnv() { func CreateInstallLock() error { _, err := os.Create(ConfDir + "/install.lock") if err != nil { - utils.RecordLog("创建安装锁文件失败") + logger.Error("创建安装锁文件失败") } return err @@ -101,10 +99,10 @@ func checkDirExists(path ...string) { for _, value := range path { _, err := os.Stat(value) if os.IsNotExist(err) { - panic(value + "目录不存在") + logger.Fatal(value + "目录不存在") } if os.IsPermission(err) { - panic(value + "目录无权限操作") + logger.Fatal(value + "目录无权限操作") } } } @@ -113,11 +111,11 @@ func checkDirExists(path ...string) { func getDbConfig(configFile string) map[string]string { config, err := setting.Read(configFile) if err != nil { - panic(err) + logger.Fatal(err) } section := config.Section("db") if err != nil { - panic(err) + logger.Fatal(err) } var db map[string]string = make(map[string]string) db["user"] = section.Key("user").String() @@ -130,4 +128,4 @@ func getDbConfig(configFile string) map[string]string { db["engine"] = section.Key("engine").String() return db -} \ No newline at end of file +} diff --git a/modules/logger/logger.go b/modules/logger/logger.go new file mode 100644 index 0000000..b9e1e83 --- /dev/null +++ b/modules/logger/logger.go @@ -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 := ` + + + %s + + + + + + + + ` + + consoleConfig := "" + if macaron.Env == macaron.DEV { + consoleConfig = + ` + + + + ` + } + config = fmt.Sprintf(config, consoleConfig) + + return config +} diff --git a/modules/utils/json.go b/modules/utils/json.go index d89aaee..2058889 100644 --- a/modules/utils/json.go +++ b/modules/utils/json.go @@ -1,6 +1,9 @@ package utils -import "encoding/json" +import ( + "encoding/json" + "github.com/ouqiang/cron-scheduler/modules/logger" +) // json 格式输出 @@ -32,7 +35,7 @@ func (j *Json) response(code int, message string, data interface{}) string { result, err := json.Marshal(resp) if err != nil { - RecordLog(err) + logger.Error(err) } return string(result) diff --git a/modules/utils/utils.go b/modules/utils/utils.go index 7db3f36..7854fe0 100644 --- a/modules/utils/utils.go +++ b/modules/utils/utils.go @@ -3,7 +3,6 @@ package utils import ( "crypto/md5" "encoding/hex" - "log" "math/rand" "os/exec" "time" @@ -43,10 +42,4 @@ func RandNumber(max int) int { r := rand.New(rand.NewSource(time.Now().UnixNano())) return r.Intn(max) -} - -// 日志记录 -// todo 保存到哪里 文件,数据库还是elasticsearch?,暂时输出到终端 -func RecordLog(v ...interface{}) { - log.Println(v) -} +} \ No newline at end of file diff --git a/routers/install/install.go b/routers/install/install.go index 9329ebb..56e7038 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -7,6 +7,7 @@ import ( "github.com/ouqiang/cron-scheduler/modules/utils" "gopkg.in/macaron.v1" "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 { json := utils.Json{} if app.Installed { + logger.Warn("系统重复安装") return json.Failure(utils.ResponseFailure, "系统已安装!") } err := testDbConnection(form) if err != nil { - utils.RecordLog(err) + logger.Error(err) return json.Failure(utils.ResponseFailure, "数据库连接失败") } // 写入数据库配置 err = writeConfig(form) if err != nil { - utils.RecordLog(err) + logger.Error(err) return json.Failure(utils.ResponseFailure, "数据库配置写入文件失败") } @@ -57,21 +59,21 @@ func Install(ctx *macaron.Context, form InstallForm) string { migration := new(models.Migration) err = migration.Exec(form.DbName) if err != nil { - utils.RecordLog(err) + logger.Error(err) return json.Failure(utils.ResponseFailure, "创建数据库表失败") } // 创建管理员账号 err = createAdminUser(form) if err != nil { - utils.RecordLog(err) + logger.Error(err) return json.Failure(utils.ResponseFailure, "创建管理员账号失败") } // 创建安装锁 err = app.CreateInstallLock() if err != nil { - utils.RecordLog(err) + logger.Error(err) return json.Failure(utils.ResponseFailure, "创建文件安装锁失败") } @@ -122,10 +124,13 @@ func testDbConnection(form InstallForm) error { dbConfig["password"] = form.DbPassword dbConfig["charset"] = "utf8" db, err := models.CreateTmpDb(dbConfig) - if err == nil { - db.Close() + if err != nil { + return err } + defer db.Close() + err = db.Ping() + return err } \ No newline at end of file diff --git a/service/task.go b/service/task.go index c640048..4437ed2 100644 --- a/service/task.go +++ b/service/task.go @@ -5,12 +5,12 @@ import ( "github.com/ouqiang/cron-scheduler/models" "github.com/ouqiang/cron-scheduler/modules/ansible" "github.com/ouqiang/cron-scheduler/modules/crontask" - "github.com/ouqiang/cron-scheduler/modules/utils" "github.com/robfig/cron" "io/ioutil" "net/http" "strconv" "time" + "github.com/ouqiang/cron-scheduler/modules/logger" ) type Task struct{} @@ -20,11 +20,11 @@ func (task *Task) Initialize() { taskModel := new(models.Task) taskList, err := taskModel.ActiveList() if err != nil { - utils.RecordLog("获取任务列表错误-", err.Error()) + logger.Error("获取任务列表错误-", err.Error()) return } if len(taskList) == 0 { - utils.RecordLog("任务列表为空") + logger.Debug("任务列表为空") return } for _, item := range taskList { @@ -36,14 +36,14 @@ func (task *Task) Initialize() { func (task *Task) Add(taskModel models.Task) { taskFunc := createHandlerJob(taskModel) if taskFunc == nil { - utils.RecordLog("添加任务#不存在的任务协议编号", taskModel.Protocol) + logger.Error("添加任务#不存在的任务协议编号", taskModel.Protocol) return } // 定时任务 if taskModel.Type == models.Timing { err := crontask.DefaultCronTask.AddOrReplace(strconv.Itoa(taskModel.Id), taskModel.Spec, taskFunc) if err != nil { - utils.RecordLog(err) + logger.Error(err) } } 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) if err != nil { - utils.RecordLog("创建HTTP请求错误-", err.Error()) + logger.Error("创建HTTP请求错误-", err.Error()) return } 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 { - utils.RecordLog("HTTP请求错误-", err.Error()) + logger.Error("HTTP请求错误-", err.Error()) return } body, err := ioutil.ReadAll(resp.Body) if err != nil { - utils.RecordLog("读取HTTP请求返回值失败-", err.Error()) + logger.Error("读取HTTP请求返回值失败-", err.Error()) } return string(body), err @@ -160,14 +160,14 @@ func createHandlerJob(taskModel models.Task) cron.FuncJob { taskFunc := func() { taskLogId, err := createTaskLog(taskModel.Id) if err != nil { - utils.RecordLog("写入任务日志失败-", err) + logger.Error("写入任务日志失败-", err) return } // err != nil 执行失败 result, err := handler.Run(taskModel) _, err = updateTaskLog(int(taskLogId), result, err) if err != nil { - utils.RecordLog("更新任务日志失败-", err) + logger.Error("更新任务日志失败-", err) } } diff --git a/vendor/github.com/cihub/seelog/LICENSE.txt b/vendor/github.com/cihub/seelog/LICENSE.txt new file mode 100644 index 0000000..8c70681 --- /dev/null +++ b/vendor/github.com/cihub/seelog/LICENSE.txt @@ -0,0 +1,24 @@ +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: + * 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 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. diff --git a/vendor/github.com/cihub/seelog/README.markdown b/vendor/github.com/cihub/seelog/README.markdown new file mode 100644 index 0000000..7dd1ab3 --- /dev/null +++ b/vendor/github.com/cihub/seelog/README.markdown @@ -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 + + + diff --git a/vendor/github.com/cihub/seelog/archive/archive.go b/vendor/github.com/cihub/seelog/archive/archive.go new file mode 100644 index 0000000..923036f --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/archive.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/archive/gzip/gzip.go b/vendor/github.com/cihub/seelog/archive/gzip/gzip.go new file mode 100644 index 0000000..ea12101 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/gzip/gzip.go @@ -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)} +} diff --git a/vendor/github.com/cihub/seelog/archive/tar/tar.go b/vendor/github.com/cihub/seelog/archive/tar/tar.go new file mode 100644 index 0000000..8dd87f5 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/tar/tar.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/archive/zip/zip.go b/vendor/github.com/cihub/seelog/archive/zip/zip.go new file mode 100644 index 0000000..4210b03 --- /dev/null +++ b/vendor/github.com/cihub/seelog/archive/zip/zip.go @@ -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) +} diff --git a/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go b/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go new file mode 100644 index 0000000..0c640ca --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_adaptivelogger.go @@ -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) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynclogger.go b/vendor/github.com/cihub/seelog/behavior_asynclogger.go new file mode 100644 index 0000000..7523106 --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynclogger.go @@ -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) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go b/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go new file mode 100644 index 0000000..972467b --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynclooplogger.go @@ -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 + } + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go b/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go new file mode 100644 index 0000000..8118f20 --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_asynctimerlogger.go @@ -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) + } +} diff --git a/vendor/github.com/cihub/seelog/behavior_synclogger.go b/vendor/github.com/cihub/seelog/behavior_synclogger.go new file mode 100644 index 0000000..5a022eb --- /dev/null +++ b/vendor/github.com/cihub/seelog/behavior_synclogger.go @@ -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() + } +} diff --git a/vendor/github.com/cihub/seelog/cfg_config.go b/vendor/github.com/cihub/seelog/cfg_config.go new file mode 100644 index 0000000..76554fc --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_config.go @@ -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 + } +} diff --git a/vendor/github.com/cihub/seelog/cfg_errors.go b/vendor/github.com/cihub/seelog/cfg_errors.go new file mode 100644 index 0000000..c1fb4d1 --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_errors.go @@ -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}} +} diff --git a/vendor/github.com/cihub/seelog/cfg_logconfig.go b/vendor/github.com/cihub/seelog/cfg_logconfig.go new file mode 100644 index 0000000..6ba6f9a --- /dev/null +++ b/vendor/github.com/cihub/seelog/cfg_logconfig.go @@ -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 ' element. It takes the 'name' attribute + // of the element and tries to find a match in two places: + // 1) CfgParseParams.CustomReceiverProducers map + // 2) Global type map, filled by RegisterReceiver + // + // If a match is found in the CustomReceiverProducers map, parser calls the corresponding producer func + // passing the init args to it. The func takes exactly the same args as CustomReceiver.AfterParse. + // The producer func must return a correct receiver or an error. If case of error, seelog will behave + // in the same way as with any other config error. + // + // You may use this param to set custom producers in case you need to pass some context when instantiating + // a custom receiver or if you frequently change custom receivers with different parameters or in any other + // situation where package-level registering (RegisterReceiver) is not an option for you. + CustomReceiverProducers map[string]CustomReceiverProducer +} + +func (cfg *CfgParseParams) String() string { + return fmt.Sprintf("CfgParams: {custom_recs=%d}", len(cfg.CustomReceiverProducers)) +} + +type elementMapEntry struct { + constructor func(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) +} + +var elementMap map[string]elementMapEntry +var predefinedFormats map[string]*formatter + +func init() { + elementMap = map[string]elementMapEntry{ + fileWriterID: {createfileWriter}, + splitterDispatcherID: {createSplitter}, + customReceiverID: {createCustomReceiver}, + filterDispatcherID: {createFilter}, + consoleWriterID: {createConsoleWriter}, + rollingfileWriterID: {createRollingFileWriter}, + bufferedWriterID: {createbufferedWriter}, + smtpWriterID: {createSMTPWriter}, + connWriterID: {createconnWriter}, + } + + err := fillPredefinedFormats() + if err != nil { + panic(fmt.Sprintf("Seelog couldn't start: predefined formats creation failed. Error: %s", err.Error())) + } +} + +func fillPredefinedFormats() error { + predefinedFormatsWithoutPrefix := map[string]string{ + "xml-debug": `%Lev%Msg%RelFile%Func%Line`, + "xml-debug-short": `%Ns%l%Msg

%RelFile

%Func`, + "xml": `%Lev%Msg`, + "xml-short": `%Ns%l%Msg`, + + "json-debug": `{"time":%Ns,"lev":"%Lev","msg":"%Msg","path":"%RelFile","func":"%Func","line":"%Line"}`, + "json-debug-short": `{"t":%Ns,"l":"%Lev","m":"%Msg","p":"%RelFile","f":"%Func"}`, + "json": `{"time":%Ns,"lev":"%Lev","msg":"%Msg"}`, + "json-short": `{"t":%Ns,"l":"%Lev","m":"%Msg"}`, + + "debug": `[%LEVEL] %RelFile:%Func.%Line %Date %Time %Msg%n`, + "debug-short": `[%LEVEL] %Date %Time %Msg%n`, + "fast": `%Ns %l %Msg%n`, + } + + predefinedFormats = make(map[string]*formatter) + + for formatKey, format := range predefinedFormatsWithoutPrefix { + formatter, err := NewFormatter(format) + if err != nil { + return err + } + + predefinedFormats[predefinedPrefix+formatKey] = formatter + } + + return nil +} + +// configFromXMLDecoder parses data from a given XML decoder. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (*configForParsing, error) { + return configFromXMLDecoderWithConfig(xmlParser, rootNode, nil) +} + +// configFromXMLDecoderWithConfig parses data from a given XML decoder. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromXMLDecoderWithConfig(xmlParser *xml.Decoder, rootNode xml.Token, cfg *CfgParseParams) (*configForParsing, error) { + _, ok := rootNode.(xml.StartElement) + if !ok { + return nil, errors.New("rootNode must be XML startElement") + } + + config, err := unmarshalNode(xmlParser, rootNode) + if err != nil { + return nil, err + } + if config == nil { + return nil, errors.New("xml has no content") + } + + return configFromXMLNodeWithConfig(config, cfg) +} + +// configFromReader parses data from a given reader. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromReader(reader io.Reader) (*configForParsing, error) { + return configFromReaderWithConfig(reader, nil) +} + +// configFromReaderWithConfig parses data from a given reader. +// Returns parsed config which can be used to create logger in case no errors occured. +// Returns error if format is incorrect or anything happened. +func configFromReaderWithConfig(reader io.Reader, cfg *CfgParseParams) (*configForParsing, error) { + config, err := unmarshalConfig(reader) + if err != nil { + return nil, err + } + + if config.name != seelogConfigID { + return nil, errors.New("root xml tag must be '" + seelogConfigID + "'") + } + + return configFromXMLNodeWithConfig(config, cfg) +} + +func configFromXMLNodeWithConfig(config *xmlNode, cfg *CfgParseParams) (*configForParsing, error) { + err := checkUnexpectedAttribute( + config, + minLevelID, + maxLevelID, + levelsID, + loggerTypeFromStringAttr, + asyncLoggerIntervalAttr, + adaptLoggerMinIntervalAttr, + adaptLoggerMaxIntervalAttr, + adaptLoggerCriticalMsgCountAttr, + ) + if err != nil { + return nil, err + } + + err = checkExpectedElements(config, optionalElement(outputsID), optionalElement(formatsID), optionalElement(exceptionsID)) + if err != nil { + return nil, err + } + + constraints, err := getConstraints(config) + if err != nil { + return nil, err + } + + exceptions, err := getExceptions(config) + if err != nil { + return nil, err + } + err = checkDistinctExceptions(exceptions) + if err != nil { + return nil, err + } + + formats, err := getFormats(config) + if err != nil { + return nil, err + } + + dispatcher, err := getOutputsTree(config, formats, cfg) + if err != nil { + // If we open several files, but then fail to parse the config, we should close + // those files before reporting that config is invalid. + if dispatcher != nil { + dispatcher.Close() + } + + return nil, err + } + + loggerType, logData, err := getloggerTypeFromStringData(config) + if err != nil { + return nil, err + } + + return newFullLoggerConfig(constraints, exceptions, dispatcher, loggerType, logData, cfg) +} + +func getConstraints(node *xmlNode) (logLevelConstraints, error) { + minLevelStr, isMinLevel := node.attributes[minLevelID] + maxLevelStr, isMaxLevel := node.attributes[maxLevelID] + levelsStr, isLevels := node.attributes[levelsID] + + if isLevels && (isMinLevel && isMaxLevel) { + return nil, errors.New("for level declaration use '" + levelsID + "'' OR '" + minLevelID + + "', '" + maxLevelID + "'") + } + + offString := LogLevel(Off).String() + + if (isLevels && strings.TrimSpace(levelsStr) == offString) || + (isMinLevel && !isMaxLevel && minLevelStr == offString) { + + return NewOffConstraints() + } + + if isLevels { + levels, err := parseLevels(levelsStr) + if err != nil { + return nil, err + } + return NewListConstraints(levels) + } + + var minLevel = LogLevel(TraceLvl) + if isMinLevel { + found := true + minLevel, found = LogLevelFromString(minLevelStr) + if !found { + return nil, errors.New("declared " + minLevelID + " not found: " + minLevelStr) + } + } + + var maxLevel = LogLevel(CriticalLvl) + if isMaxLevel { + found := true + maxLevel, found = LogLevelFromString(maxLevelStr) + if !found { + return nil, errors.New("declared " + maxLevelID + " not found: " + maxLevelStr) + } + } + + return NewMinMaxConstraints(minLevel, maxLevel) +} + +func parseLevels(str string) ([]LogLevel, error) { + levelsStrArr := strings.Split(strings.Replace(str, " ", "", -1), ",") + var levels []LogLevel + for _, levelStr := range levelsStrArr { + level, found := LogLevelFromString(levelStr) + if !found { + return nil, errors.New("declared level not found: " + levelStr) + } + + levels = append(levels, level) + } + + return levels, nil +} + +func getExceptions(config *xmlNode) ([]*LogLevelException, error) { + var exceptions []*LogLevelException + + var exceptionsNode *xmlNode + for _, child := range config.children { + if child.name == exceptionsID { + exceptionsNode = child + break + } + } + + if exceptionsNode == nil { + return exceptions, nil + } + + err := checkUnexpectedAttribute(exceptionsNode) + if err != nil { + return nil, err + } + + err = checkExpectedElements(exceptionsNode, multipleMandatoryElements("exception")) + if err != nil { + return nil, err + } + + for _, exceptionNode := range exceptionsNode.children { + if exceptionNode.name != exceptionID { + return nil, errors.New("incorrect nested element in exceptions section: " + exceptionNode.name) + } + + err := checkUnexpectedAttribute(exceptionNode, minLevelID, maxLevelID, levelsID, funcPatternID, filePatternID) + if err != nil { + return nil, err + } + + constraints, err := getConstraints(exceptionNode) + if err != nil { + return nil, errors.New("incorrect " + exceptionsID + " node: " + err.Error()) + } + + funcPattern, isFuncPattern := exceptionNode.attributes[funcPatternID] + filePattern, isFilePattern := exceptionNode.attributes[filePatternID] + if !isFuncPattern { + funcPattern = "*" + } + if !isFilePattern { + filePattern = "*" + } + + exception, err := NewLogLevelException(funcPattern, filePattern, constraints) + if err != nil { + return nil, errors.New("incorrect exception node: " + err.Error()) + } + + exceptions = append(exceptions, exception) + } + + return exceptions, nil +} + +func checkDistinctExceptions(exceptions []*LogLevelException) error { + for i, exception := range exceptions { + for j, exception1 := range exceptions { + if i == j { + continue + } + + if exception.FuncPattern() == exception1.FuncPattern() && + exception.FilePattern() == exception1.FilePattern() { + + return fmt.Errorf("there are two or more duplicate exceptions. Func: %v, file %v", + exception.FuncPattern(), exception.FilePattern()) + } + } + } + + return nil +} + +func getFormats(config *xmlNode) (map[string]*formatter, error) { + formats := make(map[string]*formatter, 0) + + var formatsNode *xmlNode + for _, child := range config.children { + if child.name == formatsID { + formatsNode = child + break + } + } + + if formatsNode == nil { + return formats, nil + } + + err := checkUnexpectedAttribute(formatsNode) + if err != nil { + return nil, err + } + + err = checkExpectedElements(formatsNode, multipleMandatoryElements("format")) + if err != nil { + return nil, err + } + + for _, formatNode := range formatsNode.children { + if formatNode.name != formatID { + return nil, errors.New("incorrect nested element in " + formatsID + " section: " + formatNode.name) + } + + err := checkUnexpectedAttribute(formatNode, formatKeyAttrID, formatID) + if err != nil { + return nil, err + } + + id, isID := formatNode.attributes[formatKeyAttrID] + formatStr, isFormat := formatNode.attributes[formatAttrID] + if !isID { + return nil, errors.New("format has no '" + formatKeyAttrID + "' attribute") + } + if !isFormat { + return nil, errors.New("format[" + id + "] has no '" + formatAttrID + "' attribute") + } + + formatter, err := NewFormatter(formatStr) + if err != nil { + return nil, err + } + + formats[id] = formatter + } + + return formats, nil +} + +func getloggerTypeFromStringData(config *xmlNode) (logType loggerTypeFromString, logData interface{}, err error) { + logTypeStr, loggerTypeExists := config.attributes[loggerTypeFromStringAttr] + + if !loggerTypeExists { + return defaultloggerTypeFromString, nil, nil + } + + logType, found := getLoggerTypeFromString(logTypeStr) + + if !found { + return 0, nil, fmt.Errorf("unknown logger type: %s", logTypeStr) + } + + if logType == asyncTimerloggerTypeFromString { + intervalStr, intervalExists := config.attributes[asyncLoggerIntervalAttr] + if !intervalExists { + return 0, nil, newMissingArgumentError(config.name, asyncLoggerIntervalAttr) + } + + interval, err := strconv.ParseUint(intervalStr, 10, 32) + if err != nil { + return 0, nil, err + } + + logData = asyncTimerLoggerData{uint32(interval)} + } else if logType == adaptiveLoggerTypeFromString { + + // Min interval + minIntStr, minIntExists := config.attributes[adaptLoggerMinIntervalAttr] + if !minIntExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerMinIntervalAttr) + } + minInterval, err := strconv.ParseUint(minIntStr, 10, 32) + if err != nil { + return 0, nil, err + } + + // Max interval + maxIntStr, maxIntExists := config.attributes[adaptLoggerMaxIntervalAttr] + if !maxIntExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerMaxIntervalAttr) + } + maxInterval, err := strconv.ParseUint(maxIntStr, 10, 32) + if err != nil { + return 0, nil, err + } + + // Critical msg count + criticalMsgCountStr, criticalMsgCountExists := config.attributes[adaptLoggerCriticalMsgCountAttr] + if !criticalMsgCountExists { + return 0, nil, newMissingArgumentError(config.name, adaptLoggerCriticalMsgCountAttr) + } + criticalMsgCount, err := strconv.ParseUint(criticalMsgCountStr, 10, 32) + if err != nil { + return 0, nil, err + } + + logData = adaptiveLoggerData{uint32(minInterval), uint32(maxInterval), uint32(criticalMsgCount)} + } + + return logType, logData, nil +} + +func getOutputsTree(config *xmlNode, formats map[string]*formatter, cfg *CfgParseParams) (dispatcherInterface, error) { + var outputsNode *xmlNode + for _, child := range config.children { + if child.name == outputsID { + outputsNode = child + break + } + } + + if outputsNode != nil { + err := checkUnexpectedAttribute(outputsNode, outputFormatID) + if err != nil { + return nil, err + } + + formatter, err := getCurrentFormat(outputsNode, DefaultFormatter, formats) + if err != nil { + return nil, err + } + + output, err := createSplitter(outputsNode, formatter, formats, cfg) + if err != nil { + return nil, err + } + + dispatcher, ok := output.(dispatcherInterface) + if ok { + return dispatcher, nil + } + } + + console, err := NewConsoleWriter() + if err != nil { + return nil, err + } + return NewSplitDispatcher(DefaultFormatter, []interface{}{console}) +} + +func getCurrentFormat(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter) (*formatter, error) { + formatID, isFormatID := node.attributes[outputFormatID] + if !isFormatID { + return formatFromParent, nil + } + + format, ok := formats[formatID] + if ok { + return format, nil + } + + // Test for predefined format match + pdFormat, pdOk := predefinedFormats[formatID] + + if !pdOk { + return nil, errors.New("formatid = '" + formatID + "' doesn't exist") + } + + return pdFormat, nil +} + +func createInnerReceivers(node *xmlNode, format *formatter, formats map[string]*formatter, cfg *CfgParseParams) ([]interface{}, error) { + var outputs []interface{} + for _, childNode := range node.children { + entry, ok := elementMap[childNode.name] + if !ok { + return nil, errors.New("unnknown tag '" + childNode.name + "' in outputs section") + } + + output, err := entry.constructor(childNode, format, formats, cfg) + if err != nil { + return nil, err + } + + outputs = append(outputs, output) + } + + return outputs, nil +} + +func createSplitter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + return NewSplitDispatcher(currentFormat, receivers) +} + +func createCustomReceiver(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + dataCustomPrefixes := make(map[string]string) + // Expecting only 'formatid', 'name' and 'data-' attrs + for attr, attrval := range node.attributes { + isExpected := false + if attr == outputFormatID || + attr == customNameAttrID { + isExpected = true + } + if strings.HasPrefix(attr, customNameDataAttrPrefix) { + dataCustomPrefixes[attr[len(customNameDataAttrPrefix):]] = attrval + isExpected = true + } + if !isExpected { + return nil, newUnexpectedAttributeError(node.name, attr) + } + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + customName, hasCustomName := node.attributes[customNameAttrID] + if !hasCustomName { + return nil, newMissingArgumentError(node.name, customNameAttrID) + } + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + args := CustomReceiverInitArgs{ + XmlCustomAttrs: dataCustomPrefixes, + } + + if cfg != nil && cfg.CustomReceiverProducers != nil { + if prod, ok := cfg.CustomReceiverProducers[customName]; ok { + rec, err := prod(args) + if err != nil { + return nil, err + } + creceiver, err := NewCustomReceiverDispatcherByValue(currentFormat, rec, customName, args) + if err != nil { + return nil, err + } + err = rec.AfterParse(args) + if err != nil { + return nil, err + } + return creceiver, nil + } + } + + return NewCustomReceiverDispatcher(currentFormat, customName, args) +} + +func createFilter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, filterLevelsAttrID) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + levelsStr, isLevels := node.attributes[filterLevelsAttrID] + if !isLevels { + return nil, newMissingArgumentError(node.name, filterLevelsAttrID) + } + + levels, err := parseLevels(levelsStr) + if err != nil { + return nil, err + } + + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + return NewFilterDispatcher(currentFormat, receivers, levels...) +} + +func createfileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, pathID) + if err != nil { + return nil, err + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + path, isPath := node.attributes[pathID] + if !isPath { + return nil, newMissingArgumentError(node.name, pathID) + } + + fileWriter, err := NewFileWriter(path) + if err != nil { + return nil, err + } + + return NewFormattedWriter(fileWriter, currentFormat) +} + +// Creates new SMTP writer if encountered in the config file. +func createSMTPWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, senderaddressID, senderNameID, hostNameID, hostPortID, userNameID, userPassID, subjectID) + if err != nil { + return nil, err + } + // Node must have children. + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + senderAddress, ok := node.attributes[senderaddressID] + if !ok { + return nil, newMissingArgumentError(node.name, senderaddressID) + } + senderName, ok := node.attributes[senderNameID] + if !ok { + return nil, newMissingArgumentError(node.name, senderNameID) + } + // Process child nodes scanning for recipient email addresses and/or CA certificate paths. + var recipientAddresses []string + var caCertDirPaths []string + var mailHeaders []string + for _, childNode := range node.children { + switch childNode.name { + // Extract recipient address from child nodes. + case recipientID: + address, ok := childNode.attributes[addressID] + if !ok { + return nil, newMissingArgumentError(childNode.name, addressID) + } + recipientAddresses = append(recipientAddresses, address) + // Extract CA certificate file path from child nodes. + case cACertDirpathID: + path, ok := childNode.attributes[pathID] + if !ok { + return nil, newMissingArgumentError(childNode.name, pathID) + } + caCertDirPaths = append(caCertDirPaths, path) + + // Extract email headers from child nodes. + case mailHeaderID: + headerName, ok := childNode.attributes[mailHeaderNameID] + if !ok { + return nil, newMissingArgumentError(childNode.name, mailHeaderNameID) + } + + headerValue, ok := childNode.attributes[mailHeaderValueID] + if !ok { + return nil, newMissingArgumentError(childNode.name, mailHeaderValueID) + } + + // Build header line + mailHeaders = append(mailHeaders, fmt.Sprintf("%s: %s", headerName, headerValue)) + default: + return nil, newUnexpectedChildElementError(childNode.name) + } + } + hostName, ok := node.attributes[hostNameID] + if !ok { + return nil, newMissingArgumentError(node.name, hostNameID) + } + + hostPort, ok := node.attributes[hostPortID] + if !ok { + return nil, newMissingArgumentError(node.name, hostPortID) + } + + // Check if the string can really be converted into int. + if _, err := strconv.Atoi(hostPort); err != nil { + return nil, errors.New("invalid host port number") + } + + userName, ok := node.attributes[userNameID] + if !ok { + return nil, newMissingArgumentError(node.name, userNameID) + } + + userPass, ok := node.attributes[userPassID] + if !ok { + return nil, newMissingArgumentError(node.name, userPassID) + } + + // subject is optionally set by configuration. + // default value is defined by DefaultSubjectPhrase constant in the writers_smtpwriter.go + var subjectPhrase = DefaultSubjectPhrase + + subject, ok := node.attributes[subjectID] + if ok { + subjectPhrase = subject + } + + smtpWriter := NewSMTPWriter( + senderAddress, + senderName, + recipientAddresses, + hostName, + hostPort, + userName, + userPass, + caCertDirPaths, + subjectPhrase, + mailHeaders, + ) + + return NewFormattedWriter(smtpWriter, currentFormat) +} + +func createConsoleWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID) + if err != nil { + return nil, err + } + + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + consoleWriter, err := NewConsoleWriter() + if err != nil { + return nil, err + } + + return NewFormattedWriter(consoleWriter, currentFormat) +} + +func createconnWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + err := checkUnexpectedAttribute(node, outputFormatID, connWriterAddrAttr, connWriterNetAttr, connWriterReconnectOnMsgAttr, connWriterUseTLSAttr, connWriterInsecureSkipVerifyAttr) + if err != nil { + return nil, err + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + addr, isAddr := node.attributes[connWriterAddrAttr] + if !isAddr { + return nil, newMissingArgumentError(node.name, connWriterAddrAttr) + } + + net, isNet := node.attributes[connWriterNetAttr] + if !isNet { + return nil, newMissingArgumentError(node.name, connWriterNetAttr) + } + + reconnectOnMsg := false + reconnectOnMsgStr, isReconnectOnMsgStr := node.attributes[connWriterReconnectOnMsgAttr] + if isReconnectOnMsgStr { + if reconnectOnMsgStr == "true" { + reconnectOnMsg = true + } else if reconnectOnMsgStr == "false" { + reconnectOnMsg = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterReconnectOnMsgAttr + "' attribute value") + } + } + + useTLS := false + useTLSStr, isUseTLSStr := node.attributes[connWriterUseTLSAttr] + if isUseTLSStr { + if useTLSStr == "true" { + useTLS = true + } else if useTLSStr == "false" { + useTLS = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterUseTLSAttr + "' attribute value") + } + if useTLS { + insecureSkipVerify := false + insecureSkipVerifyStr, isInsecureSkipVerify := node.attributes[connWriterInsecureSkipVerifyAttr] + if isInsecureSkipVerify { + if insecureSkipVerifyStr == "true" { + insecureSkipVerify = true + } else if insecureSkipVerifyStr == "false" { + insecureSkipVerify = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + connWriterInsecureSkipVerifyAttr + "' attribute value") + } + } + config := tls.Config{InsecureSkipVerify: insecureSkipVerify} + connWriter := newTLSWriter(net, addr, reconnectOnMsg, &config) + return NewFormattedWriter(connWriter, currentFormat) + } + } + + connWriter := NewConnWriter(net, addr, reconnectOnMsg) + + return NewFormattedWriter(connWriter, currentFormat) +} + +func createRollingFileWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + if node.hasChildren() { + return nil, errNodeCannotHaveChildren + } + + rollingTypeStr, isRollingType := node.attributes[rollingFileTypeAttr] + if !isRollingType { + return nil, newMissingArgumentError(node.name, rollingFileTypeAttr) + } + + rollingType, ok := rollingTypeFromString(rollingTypeStr) + if !ok { + return nil, errors.New("unknown rolling file type: " + rollingTypeStr) + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + path, isPath := node.attributes[rollingFilePathAttr] + if !isPath { + return nil, newMissingArgumentError(node.name, rollingFilePathAttr) + } + + rollingArchiveStr, archiveAttrExists := node.attributes[rollingFileArchiveAttr] + + var rArchiveType rollingArchiveType + var rArchivePath string + var rArchiveExploded bool = false + if !archiveAttrExists { + rArchiveType = rollingArchiveNone + rArchivePath = "" + } else { + rArchiveType, ok = rollingArchiveTypeFromString(rollingArchiveStr) + if !ok { + return nil, errors.New("unknown rolling archive type: " + rollingArchiveStr) + } + + if rArchiveType == rollingArchiveNone { + rArchivePath = "" + } else { + if rArchiveExplodedAttr, ok := node.attributes[rollingFileArchiveExplodedAttr]; ok { + if rArchiveExploded, err = strconv.ParseBool(rArchiveExplodedAttr); err != nil { + return nil, fmt.Errorf("archive exploded should be true or false, but was %v", + rArchiveExploded) + } + } + + rArchivePath, ok = node.attributes[rollingFileArchivePathAttr] + if ok { + if rArchivePath == "" { + return nil, fmt.Errorf("empty archive path is not supported") + } + } else { + if rArchiveExploded { + rArchivePath = rollingArchiveDefaultExplodedName + + } else { + rArchivePath, err = rollingArchiveTypeDefaultName(rArchiveType, false) + if err != nil { + return nil, err + } + } + } + } + } + + nameMode := rollingNameMode(rollingNameModePostfix) + nameModeStr, ok := node.attributes[rollingFileNameModeAttr] + if ok { + mode, found := rollingNameModeFromString(nameModeStr) + if !found { + return nil, errors.New("unknown rolling filename mode: " + nameModeStr) + } else { + nameMode = mode + } + } + + if rollingType == rollingTypeSize { + err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr, + rollingFileMaxSizeAttr, rollingFileMaxRollsAttr, rollingFileArchiveAttr, + rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr) + if err != nil { + return nil, err + } + + maxSizeStr, ok := node.attributes[rollingFileMaxSizeAttr] + if !ok { + return nil, newMissingArgumentError(node.name, rollingFileMaxSizeAttr) + } + + maxSize, err := strconv.ParseInt(maxSizeStr, 10, 64) + if err != nil { + return nil, err + } + + maxRolls := 0 + maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr] + if ok { + maxRolls, err = strconv.Atoi(maxRollsStr) + if err != nil { + return nil, err + } + } + + rollingWriter, err := NewRollingFileWriterSize(path, rArchiveType, rArchivePath, maxSize, maxRolls, nameMode, rArchiveExploded) + if err != nil { + return nil, err + } + + return NewFormattedWriter(rollingWriter, currentFormat) + + } else if rollingType == rollingTypeTime { + err := checkUnexpectedAttribute(node, outputFormatID, rollingFileTypeAttr, rollingFilePathAttr, + rollingFileDataPatternAttr, rollingFileArchiveAttr, rollingFileMaxRollsAttr, + rollingFileArchivePathAttr, rollingFileArchiveExplodedAttr, rollingFileNameModeAttr, + rollingFileFullNameAttr) + if err != nil { + return nil, err + } + + maxRolls := 0 + maxRollsStr, ok := node.attributes[rollingFileMaxRollsAttr] + if ok { + maxRolls, err = strconv.Atoi(maxRollsStr) + if err != nil { + return nil, err + } + } + + fullName := false + fn, ok := node.attributes[rollingFileFullNameAttr] + if ok { + if fn == "true" { + fullName = true + } else if fn == "false" { + fullName = false + } else { + return nil, errors.New("node '" + node.name + "' has incorrect '" + rollingFileFullNameAttr + "' attribute value") + } + } + + dataPattern, ok := node.attributes[rollingFileDataPatternAttr] + if !ok { + return nil, newMissingArgumentError(node.name, rollingFileDataPatternAttr) + } + + rollingWriter, err := NewRollingFileWriterTime(path, rArchiveType, rArchivePath, maxRolls, dataPattern, nameMode, rArchiveExploded, fullName) + if err != nil { + return nil, err + } + + return NewFormattedWriter(rollingWriter, currentFormat) + } + + return nil, errors.New("incorrect rolling writer type " + rollingTypeStr) +} + +func createbufferedWriter(node *xmlNode, formatFromParent *formatter, formats map[string]*formatter, cfg *CfgParseParams) (interface{}, error) { + err := checkUnexpectedAttribute(node, outputFormatID, bufferedSizeAttr, bufferedFlushPeriodAttr) + if err != nil { + return nil, err + } + + if !node.hasChildren() { + return nil, errNodeMustHaveChildren + } + + currentFormat, err := getCurrentFormat(node, formatFromParent, formats) + if err != nil { + return nil, err + } + + sizeStr, isSize := node.attributes[bufferedSizeAttr] + if !isSize { + return nil, newMissingArgumentError(node.name, bufferedSizeAttr) + } + + size, err := strconv.Atoi(sizeStr) + if err != nil { + return nil, err + } + + flushPeriod := 0 + flushPeriodStr, isFlushPeriod := node.attributes[bufferedFlushPeriodAttr] + if isFlushPeriod { + flushPeriod, err = strconv.Atoi(flushPeriodStr) + if err != nil { + return nil, err + } + } + + // Inner writer couldn't have its own format, so we pass 'currentFormat' as its parent format + receivers, err := createInnerReceivers(node, currentFormat, formats, cfg) + if err != nil { + return nil, err + } + + formattedWriter, ok := receivers[0].(*formattedWriter) + if !ok { + return nil, errors.New("buffered writer's child is not writer") + } + + // ... and then we check that it hasn't changed + if formattedWriter.Format() != currentFormat { + return nil, errors.New("inner writer cannot have his own format") + } + + bufferedWriter, err := NewBufferedWriter(formattedWriter.Writer(), size, time.Duration(flushPeriod)) + if err != nil { + return nil, err + } + + return NewFormattedWriter(bufferedWriter, currentFormat) +} + +// Returns an error if node has any attributes not listed in expectedAttrs. +func checkUnexpectedAttribute(node *xmlNode, expectedAttrs ...string) error { + for attr := range node.attributes { + isExpected := false + for _, expected := range expectedAttrs { + if attr == expected { + isExpected = true + break + } + } + if !isExpected { + return newUnexpectedAttributeError(node.name, attr) + } + } + + return nil +} + +type expectedElementInfo struct { + name string + mandatory bool + multiple bool +} + +func optionalElement(name string) expectedElementInfo { + return expectedElementInfo{name, false, false} +} +func mandatoryElement(name string) expectedElementInfo { + return expectedElementInfo{name, true, false} +} +func multipleElements(name string) expectedElementInfo { + return expectedElementInfo{name, false, true} +} +func multipleMandatoryElements(name string) expectedElementInfo { + return expectedElementInfo{name, true, true} +} + +func checkExpectedElements(node *xmlNode, elements ...expectedElementInfo) error { + for _, element := range elements { + count := 0 + for _, child := range node.children { + if child.name == element.name { + count++ + } + } + + if count == 0 && element.mandatory { + return errors.New(node.name + " does not have mandatory subnode - " + element.name) + } + if count > 1 && !element.multiple { + return errors.New(node.name + " has more then one subnode - " + element.name) + } + } + + for _, child := range node.children { + isExpected := false + for _, element := range elements { + if child.name == element.name { + isExpected = true + } + } + + if !isExpected { + return errors.New(node.name + " has unexpected child: " + child.name) + } + } + + return nil +} diff --git a/vendor/github.com/cihub/seelog/common_closer.go b/vendor/github.com/cihub/seelog/common_closer.go new file mode 100644 index 0000000..1319c22 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_closer.go @@ -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 diff --git a/vendor/github.com/cihub/seelog/common_constraints.go b/vendor/github.com/cihub/seelog/common_constraints.go new file mode 100644 index 0000000..7ec2fe5 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_constraints.go @@ -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" +} diff --git a/vendor/github.com/cihub/seelog/common_context.go b/vendor/github.com/cihub/seelog/common_context.go new file mode 100644 index 0000000..230a76c --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_context.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/common_exception.go b/vendor/github.com/cihub/seelog/common_exception.go new file mode 100644 index 0000000..9acc275 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_exception.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/common_flusher.go b/vendor/github.com/cihub/seelog/common_flusher.go new file mode 100644 index 0000000..0ef077c --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_flusher.go @@ -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() +} diff --git a/vendor/github.com/cihub/seelog/common_loglevel.go b/vendor/github.com/cihub/seelog/common_loglevel.go new file mode 100644 index 0000000..d54ecf2 --- /dev/null +++ b/vendor/github.com/cihub/seelog/common_loglevel.go @@ -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 "" +} diff --git a/vendor/github.com/cihub/seelog/dispatch_custom.go b/vendor/github.com/cihub/seelog/dispatch_custom.go new file mode 100644 index 0000000..383a770 --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_custom.go @@ -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 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 '' 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 ( items). Basically it is just the way +// you tell seelog config parser what should it do when it meets a +// 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 '' 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 '' + // 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 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 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 +} diff --git a/vendor/github.com/cihub/seelog/dispatch_dispatcher.go b/vendor/github.com/cihub/seelog/dispatch_dispatcher.go new file mode 100644 index 0000000..2bd3b4a --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_dispatcher.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go b/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go new file mode 100644 index 0000000..9de8a72 --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_filterdispatcher.go @@ -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) +} diff --git a/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go b/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go new file mode 100644 index 0000000..1d0fe7e --- /dev/null +++ b/vendor/github.com/cihub/seelog/dispatch_splitdispatcher.go @@ -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()) +} diff --git a/vendor/github.com/cihub/seelog/doc.go b/vendor/github.com/cihub/seelog/doc.go new file mode 100644 index 0000000..2734c9c --- /dev/null +++ b/vendor/github.com/cihub/seelog/doc.go @@ -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: + + + + + + + + + + + + + + + + + + + + + +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 diff --git a/vendor/github.com/cihub/seelog/format.go b/vendor/github.com/cihub/seelog/format.go new file mode 100644 index 0000000..ec47b45 --- /dev/null +++ b/vendor/github.com/cihub/seelog/format.go @@ -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) + } +} diff --git a/vendor/github.com/cihub/seelog/internals_baseerror.go b/vendor/github.com/cihub/seelog/internals_baseerror.go new file mode 100644 index 0000000..c0b271d --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_baseerror.go @@ -0,0 +1,10 @@ +package seelog + +// Base struct for custom errors. +type baseError struct { + message string +} + +func (be baseError) Error() string { + return be.message +} diff --git a/vendor/github.com/cihub/seelog/internals_fsutils.go b/vendor/github.com/cihub/seelog/internals_fsutils.go new file mode 100644 index 0000000..c0a0e0e --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_fsutils.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/internals_xmlnode.go b/vendor/github.com/cihub/seelog/internals_xmlnode.go new file mode 100644 index 0000000..9858849 --- /dev/null +++ b/vendor/github.com/cihub/seelog/internals_xmlnode.go @@ -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("", 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 +} diff --git a/vendor/github.com/cihub/seelog/log.go b/vendor/github.com/cihub/seelog/log.go new file mode 100644 index 0000000..f775e1f --- /dev/null +++ b/vendor/github.com/cihub/seelog/log.go @@ -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: "". 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("")) + } + + if Disabled == nil { + Disabled, err = LoggerFromConfigAsBytes([]byte("")) + } + + 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. '' 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() +} diff --git a/vendor/github.com/cihub/seelog/logger.go b/vendor/github.com/cihub/seelog/logger.go new file mode 100644 index 0000000..fc96aed --- /dev/null +++ b/vendor/github.com/cihub/seelog/logger.go @@ -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...) +} diff --git a/vendor/github.com/cihub/seelog/writers_bufferedwriter.go b/vendor/github.com/cihub/seelog/writers_bufferedwriter.go new file mode 100644 index 0000000..37d75c8 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_bufferedwriter.go @@ -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) +} diff --git a/vendor/github.com/cihub/seelog/writers_connwriter.go b/vendor/github.com/cihub/seelog/writers_connwriter.go new file mode 100644 index 0000000..d199894 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_connwriter.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/writers_consolewriter.go b/vendor/github.com/cihub/seelog/writers_consolewriter.go new file mode 100644 index 0000000..3eb79af --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_consolewriter.go @@ -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" +} diff --git a/vendor/github.com/cihub/seelog/writers_filewriter.go b/vendor/github.com/cihub/seelog/writers_filewriter.go new file mode 100644 index 0000000..8d3ae27 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_filewriter.go @@ -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) +} diff --git a/vendor/github.com/cihub/seelog/writers_formattedwriter.go b/vendor/github.com/cihub/seelog/writers_formattedwriter.go new file mode 100644 index 0000000..bf44a41 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_formattedwriter.go @@ -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 +} diff --git a/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go b/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go new file mode 100644 index 0000000..9535a57 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_rollingfilewriter.go @@ -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) +} diff --git a/vendor/github.com/cihub/seelog/writers_smtpwriter.go b/vendor/github.com/cihub/seelog/writers_smtpwriter.go new file mode 100644 index 0000000..31b7943 --- /dev/null +++ b/vendor/github.com/cihub/seelog/writers_smtpwriter.go @@ -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 +} diff --git a/vendor/github.com/donnie4w/go-logger/readme.txt b/vendor/github.com/donnie4w/go-logger/readme.txt new file mode 100644 index 0000000..fec42e0 --- /dev/null +++ b/vendor/github.com/donnie4w/go-logger/readme.txt @@ -0,0 +1,50 @@ +go-logger golang ־ ڶgolanglogķװ +÷java־߰log4j + +ӡ־5 DebugInfoWarn, Error ,Fatal ־ɵ͵ + +־ķΪlogger.SetLevel() 磺logger.SetLevel(logger.WARN) +logger.Debug(....),logger.Info(...) ־ + logger.Warn(...),logger.Error(...),logger.Fatal(...)־ +־IJ7ֱΪALLDEBUGINFOWARNERRORFATALOFF + ALLʾеôӡ־ķOFFʾ + + +־ļиͣ1Ϊз֡2Ϊ־Сз֡ +зʱÿһ־ļ׺Ϊ .yyyy-MM-dd +0ǰһ챸ļ + +СзҪ31ΪļС2Ϊλ3Ϊļ +ļֵָʱɱļβΪεȻ +ļָʱɵ־ļǰɵͬı־ļ + +ʾ + + //ָǷ̨ӡĬΪtrue + logger.SetConsole(true) + //ָ־ļݷʽΪļСķʽ + //һΪ־ļĿ¼ + //ڶΪ־ļ + //Ϊļ + //ĸΪļС + //ΪļСĵλ KBMBGB TB + //logger.SetRollingFile("d:/logtest", "test.log", 10, 5, logger.KB) + + //ָ־ļݷʽΪڵķʽ + //һΪ־ļĿ¼ + //ڶΪ־ļ + logger.SetRollingDaily("d:/logtest", "test.log") + + //ָ־ ALLDEBUGINFOWARNERRORFATALOFF ɵ͵ + //һϰDzԽ׶Ϊ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)) +} \ No newline at end of file diff --git a/vendor/vendor.json b/vendor/vendor.json index 7d10e82..2208047 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -8,6 +8,42 @@ "revision": "0db4a625e949e956314d7d1adea9bf82384cc10c", "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=", "path": "github.com/go-macaron/binding", @@ -60,10 +96,6 @@ "revision": "e8fbd41c16b9c0468dbae8db2fe0161a21265b8a", "revisionTime": "2017-02-21T11:08:50Z" }, - { - "path": "github.com/go-yaml/yaml", - "revision": "" - }, { "checksumSHA1": "iKPMvbAueGfdyHcWCgzwKzm8WVo=", "path": "github.com/klauspost/cpuid",