├── .travis.yml ├── LICENSE.md ├── Makefile ├── README.md ├── appenders ├── appender.go ├── console.go ├── console_test.go ├── fluentd.go ├── multiple_appender.go ├── rollingfile.go └── rollingfile_test.go ├── layout ├── basic.go ├── basic_test.go ├── layout.go ├── pattern.go └── pattern_test.go ├── levels └── levels.go ├── log ├── log.go └── log_test.go ├── logger ├── logger.go └── logger_test.go └── version.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: make test-deps 3 | go: 4 | - 1.1 5 | - 1.2 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ian Kent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEPS = $(go list -f '{{range .TestImports}}{{.}} {{end}}' ./...) 2 | 3 | test: test-deps 4 | go list ./... | xargs -n1 go test 5 | 6 | test-deps: 7 | go get github.com/stretchr/testify 8 | go get github.com/t-k/fluent-logger-golang/fluent 9 | 10 | release-deps: 11 | go get github.com/mitchellh/gox 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go-Log 2 | ====== 3 | 4 | [![Build Status](https://travis-ci.org/ian-kent/go-log.svg?branch=master)](https://travis-ci.org/ian-kent/go-log) 5 | 6 | A logger, for Go! 7 | 8 | It's sort of ```log``` and ```code.google.com/p/log4go``` compatible, so in most cases 9 | can be used without any code changes. 10 | 11 | #### Breaking change 12 | 13 | go-log was inconsistent with the default Go 'log' package, and log.Fatal calls didn't trigger an os.Exit(1). 14 | 15 | This has been fixed in the current release of go-log, which might break backwards compatibility. 16 | 17 | You can disable the fix by setting ExitOnFatal to false, e.g. 18 | 19 | log.Logger().ExitOnFatal = false 20 | 21 | ### Getting started 22 | 23 | Install go-log: 24 | 25 | ``` 26 | go get github.com/ian-kent/go-log/log 27 | ``` 28 | 29 | Use the logger in your application: 30 | 31 | ``` 32 | import( 33 | "github.com/ian-kent/go-log/log" 34 | ) 35 | 36 | // Pass a log message and arguments directly 37 | log.Debug("Example log message: %s", "example arg") 38 | 39 | // Pass a function which returns a log message and arguments 40 | log.Debug(func(){[]interface{}{"Example log message: %s", "example arg"}}) 41 | log.Debug(func(i ...interface{}){[]interface{}{"Example log message: %s", "example arg"}}) 42 | ``` 43 | 44 | You can also get the logger instance: 45 | ``` 46 | logger := log.Logger() 47 | logger.Debug("Yey!") 48 | ``` 49 | 50 | Or get a named logger instance: 51 | 52 | ``` 53 | logger := log.Logger("foo.bar") 54 | ``` 55 | 56 | ### Log levels 57 | 58 | The default log level is DEBUG. 59 | 60 | To get the current log level: 61 | 62 | ``` 63 | level := logger.Level() 64 | ``` 65 | 66 | Or to set the log level: 67 | 68 | ``` 69 | // From a LogLevel 70 | logger.SetLevel(levels.TRACE) 71 | 72 | // From a string 73 | logger.SetLevel(log.Stol("TRACE")) 74 | ``` 75 | 76 | ### Log appenders 77 | 78 | The default log appender is ```appenders.Console()```, which logs 79 | the raw message to STDOUT. 80 | 81 | To get the current log appender: 82 | 83 | ``` 84 | appender := logger.Appender() 85 | ``` 86 | 87 | If the appender is ```nil```, the parent loggers appender will be used 88 | instead. 89 | 90 | If the appender eventually resolves to ```nil```, log data will be 91 | silently dropped. 92 | 93 | You can set the log appender: 94 | 95 | ``` 96 | logger.SetAppender(appenders.Console()) 97 | ``` 98 | 99 | #### Rolling file appender 100 | 101 | Similar to log4j's rolling file appender, you can use 102 | 103 | ``` 104 | // Append to (or create) file 105 | logger.SetAppender(appenders.RollingFile("filename.log", true)) 106 | 107 | // Truncate (or create) file 108 | logger.SetAppender(appenders.RollingFile("filename.log", false)) 109 | ``` 110 | 111 | You can also control the number of log files which are kept: 112 | ``` 113 | r := appenders.RollingFile("filename.log", true) 114 | r.MaxBackupIndex = 2 // filename.log, filename.log.1, filename.log.2 115 | ``` 116 | 117 | And the maximum log file size (in bytes): 118 | ``` 119 | r := appenders.RollingFile("filename.log", true) 120 | r.MaxFileSize = 1024 // 1KB, defaults to 100MB 121 | ``` 122 | 123 | #### Fluentd appender 124 | 125 | The fluentd appender lets you write log data directly to fluentd: 126 | 127 | ``` 128 | logger.SetAppender(appenders.Fluentd(fluent.Config{})) 129 | ``` 130 | 131 | It uses ```github.com/t-k/fluent-logger-golang```. 132 | 133 | The tag is currently fixed to 'go-log', and the data structure sent 134 | to fluentd is simple: 135 | 136 | ``` 137 | { 138 | message: "" 139 | } 140 | 141 | ``` 142 | 143 | ### Layouts 144 | 145 | Each appender has its own layout. This allows the log data to be transformed 146 | as it is written to the appender. 147 | 148 | The default layout is ```layout.Basic()```, which passes the log message 149 | and its arguments through ```fmt.Sprintf```. 150 | 151 | To get the current log appender layout: 152 | ``` 153 | appender := logger.Appender() 154 | layout := appender.Layout() 155 | ``` 156 | 157 | To set the log appender layout: 158 | ``` 159 | appender.SetLayout(layout.Basic()) 160 | ``` 161 | 162 | You can also use ```layout.Pattern(pattern string)```, which accepts a 163 | pattern format similar to log4j: 164 | 165 | | Code | Description 166 | | ---- | ----------- 167 | | %c | The package the log statement is in 168 | | %C | Currently also the package the log statement is in 169 | | %d | The current date/time, using ```time.Now().String()``` 170 | | %F | The filename the log statement is in 171 | | %l | The location of the log statement, e.g. ```package/somefile.go:12``` 172 | | %L | The line number the log statement is on 173 | | %m | The log message and its arguments formatted with ```fmt.Sprintf``` 174 | | %n | A new-line character 175 | | %p | Priority - the log level 176 | | %r | ms since logger was created 177 | 178 | ### Logger inheritance 179 | 180 | Loggers are namespaced with a ```.```, following similar rules to Log4j. 181 | 182 | If you create a logger named ```foo```, it will automatically inherit the 183 | log settings (levels and appender) of the root logger. 184 | 185 | If you then create a logger named ```foo.bar```, it will inherit the log 186 | settings of ```foo```, which in turn inherits the log settings from the 187 | root logger. 188 | 189 | You can break this by setting the log level or setting an appender on 190 | a child logger, e.g.: 191 | 192 | ``` 193 | logger := log.Logger("foo.bar") 194 | logger.SetLevel(levels.TRACE) 195 | logger.SetAppender(appenders.Console()) 196 | ``` 197 | 198 | If you then created a logger named ```foo.bar.qux```, it would inherit 199 | the trace level and console appender of the ```foo.bar``` logger. 200 | 201 | ### Roadmap 202 | 203 | * log4j configuration support 204 | * .properties 205 | * .xml 206 | * .json 207 | * layouts 208 | * fixmes/todos in pattern layout 209 | * appenders 210 | * add socket appender 211 | * fixmes/todos and tests for fluentd appender 212 | * optimise logger creation 213 | * collapse loggers when parent namespace is unused 214 | * reorganise loggers when new child tree is created 215 | * add godoc documentation 216 | 217 | ### Contributing 218 | 219 | Before submitting a pull request: 220 | 221 | * Format your code: ```go fmt ./...``` 222 | * Make sure tests pass: ```go test ./...``` 223 | 224 | ### Licence 225 | 226 | Copyright ©‎ 2014, Ian Kent (http://www.iankent.eu). 227 | 228 | Released under MIT license, see [LICENSE](LICENSE.md) for details. 229 | -------------------------------------------------------------------------------- /appenders/appender.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | /* 4 | 5 | Appenders control the flow of data from a logger to an output. 6 | 7 | For example, a Console appender outputs log data to stdout. 8 | 9 | Satisfy the Appender interface to implement your own log appender. 10 | 11 | */ 12 | 13 | import ( 14 | "github.com/ian-kent/go-log/layout" 15 | "github.com/ian-kent/go-log/levels" 16 | ) 17 | 18 | type Appender interface { 19 | Write(level levels.LogLevel, message string, args ...interface{}) 20 | Layout() layout.Layout 21 | SetLayout(layout.Layout) 22 | } 23 | -------------------------------------------------------------------------------- /appenders/console.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ian-kent/go-log/layout" 6 | "github.com/ian-kent/go-log/levels" 7 | ) 8 | 9 | type consoleAppender struct { 10 | Appender 11 | layout layout.Layout 12 | } 13 | 14 | func Console() *consoleAppender { 15 | a := &consoleAppender{ 16 | layout: layout.Default(), 17 | } 18 | return a 19 | } 20 | 21 | func (a *consoleAppender) Write(level levels.LogLevel, message string, args ...interface{}) { 22 | fmt.Println(a.Layout().Format(level, message, args...)) 23 | } 24 | 25 | func (a *consoleAppender) Layout() layout.Layout { 26 | return a.layout 27 | } 28 | 29 | func (a *consoleAppender) SetLayout(layout layout.Layout) { 30 | a.layout = layout 31 | } 32 | -------------------------------------------------------------------------------- /appenders/console_test.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | import ( 4 | "bytes" 5 | "github.com/ian-kent/go-log/levels" 6 | "github.com/stretchr/testify/assert" 7 | "io" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestConsole(t *testing.T) { 13 | a := Console() 14 | assert.NotNil(t, a) 15 | 16 | assert.Equal(t, captureOutput(t, func() { a.Write(levels.DEBUG, "Test message") }), "Test message\n") 17 | assert.Equal(t, captureOutput(t, func() { a.Write(levels.DEBUG, "Test message %s", "foo") }), "Test message foo\n") 18 | } 19 | 20 | func captureOutput(t *testing.T, f func()) string { 21 | stdout := os.Stdout 22 | r, w, _ := os.Pipe() 23 | os.Stdout = w 24 | 25 | f() 26 | 27 | outC := make(chan string) 28 | 29 | go func() { 30 | var buf bytes.Buffer 31 | io.Copy(&buf, r) 32 | outC <- buf.String() 33 | }() 34 | 35 | w.Close() 36 | os.Stdout = stdout 37 | 38 | out := <-outC 39 | 40 | return out 41 | } 42 | -------------------------------------------------------------------------------- /appenders/fluentd.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | // TODO add tests 4 | 5 | import ( 6 | "github.com/ian-kent/go-log/layout" 7 | "github.com/ian-kent/go-log/levels" 8 | "github.com/t-k/fluent-logger-golang/fluent" 9 | ) 10 | 11 | type fluentdAppender struct { 12 | Appender 13 | layout layout.Layout 14 | fluent *fluent.Fluent 15 | fluentConfig fluent.Config 16 | } 17 | 18 | func SafeFluentd(config fluent.Config) (*fluentdAppender, error) { 19 | a := &fluentdAppender{ 20 | layout: layout.Default(), 21 | fluentConfig: config, 22 | } 23 | if err := a.Open(); err != nil { 24 | return nil, err 25 | } 26 | return a, nil 27 | } 28 | 29 | func Fluentd(config fluent.Config) *fluentdAppender { 30 | a, _ := SafeFluentd(config) 31 | return a 32 | } 33 | 34 | func (a *fluentdAppender) Close() { 35 | a.fluent.Close() 36 | a.fluent = nil 37 | } 38 | 39 | func (a *fluentdAppender) Open() error { 40 | f, err := fluent.New(a.fluentConfig) 41 | if err != nil { 42 | return err 43 | } 44 | a.fluent = f 45 | return nil 46 | } 47 | 48 | func (a *fluentdAppender) Write(level levels.LogLevel, message string, args ...interface{}) { 49 | // FIXME 50 | // - use tag instead of "go-log" 51 | // - get layout to return the map 52 | var data = map[string]string{ 53 | "message": a.Layout().Format(level, message, args...), 54 | } 55 | a.fluent.Post("go-log", data) 56 | } 57 | 58 | func (a *fluentdAppender) Layout() layout.Layout { 59 | return a.layout 60 | } 61 | 62 | func (a *fluentdAppender) SetLayout(layout layout.Layout) { 63 | a.layout = layout 64 | } 65 | -------------------------------------------------------------------------------- /appenders/multiple_appender.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/layout" 5 | "github.com/ian-kent/go-log/levels" 6 | ) 7 | 8 | type multipleAppender struct { 9 | currentLayout layout.Layout 10 | listOfAppenders []Appender 11 | } 12 | 13 | func Multiple(layout layout.Layout, appenders ...Appender) Appender { 14 | return &multipleAppender{ 15 | listOfAppenders: appenders, 16 | currentLayout: layout, 17 | } 18 | } 19 | 20 | func (this *multipleAppender) Layout() layout.Layout { 21 | return this.currentLayout 22 | } 23 | 24 | func (this *multipleAppender) SetLayout(l layout.Layout) { 25 | this.currentLayout = l 26 | } 27 | 28 | func (this *multipleAppender) Write(level levels.LogLevel, message string, args ...interface{}) { 29 | for _, appender := range this.listOfAppenders { 30 | appender.Write(level, message, args...) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /appenders/rollingfile.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ian-kent/go-log/layout" 6 | "github.com/ian-kent/go-log/levels" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | type rollingFileAppender struct { 14 | Appender 15 | layout layout.Layout 16 | MaxFileSize int64 17 | MaxBackupIndex int 18 | 19 | filename string 20 | file *os.File 21 | append bool 22 | writeMutex sync.Mutex 23 | 24 | bytesWritten int64 25 | } 26 | 27 | func RollingFile(filename string, append bool) *rollingFileAppender { 28 | a := &rollingFileAppender{ 29 | layout: layout.Default(), 30 | MaxFileSize: 104857600, 31 | MaxBackupIndex: 1, 32 | append: append, 33 | bytesWritten: 0, 34 | } 35 | err := a.SetFilename(filename) 36 | if err != nil { 37 | fmt.Printf("Error opening file: %s\n", err) 38 | return nil 39 | } 40 | return a 41 | } 42 | 43 | func (a *rollingFileAppender) Close() { 44 | if a.file != nil { 45 | a.file.Close() 46 | a.file = nil 47 | } 48 | } 49 | 50 | func (a *rollingFileAppender) Write(level levels.LogLevel, message string, args ...interface{}) { 51 | m := a.Layout().Format(level, message, args...) 52 | if !strings.HasSuffix(m, "\n") { 53 | m += "\n" 54 | } 55 | 56 | a.writeMutex.Lock() 57 | a.file.Write([]byte(m)) 58 | 59 | a.bytesWritten += int64(len(m)) 60 | if a.bytesWritten >= a.MaxFileSize { 61 | a.bytesWritten = 0 62 | a.rotateFile() 63 | } 64 | 65 | a.writeMutex.Unlock() 66 | } 67 | 68 | func (a *rollingFileAppender) Layout() layout.Layout { 69 | return a.layout 70 | } 71 | 72 | func (a *rollingFileAppender) SetLayout(layout layout.Layout) { 73 | a.layout = layout 74 | } 75 | 76 | func (a *rollingFileAppender) Filename() string { 77 | return a.filename 78 | } 79 | 80 | func (a *rollingFileAppender) SetFilename(filename string) error { 81 | if a.filename != filename || a.file == nil { 82 | a.closeFile() 83 | a.filename = filename 84 | err := a.openFile() 85 | return err 86 | } 87 | return nil 88 | } 89 | 90 | func (a *rollingFileAppender) rotateFile() { 91 | a.closeFile() 92 | 93 | lastFile := a.filename + "." + strconv.Itoa(a.MaxBackupIndex) 94 | if _, err := os.Stat(lastFile); err == nil { 95 | os.Remove(lastFile) 96 | } 97 | 98 | for n := a.MaxBackupIndex; n > 0; n-- { 99 | f1 := a.filename + "." + strconv.Itoa(n) 100 | f2 := a.filename + "." + strconv.Itoa(n+1) 101 | os.Rename(f1, f2) 102 | } 103 | 104 | os.Rename(a.filename, a.filename+".1") 105 | 106 | a.openFile() 107 | } 108 | func (a *rollingFileAppender) closeFile() { 109 | if a.file != nil { 110 | a.file.Close() 111 | a.file = nil 112 | } 113 | } 114 | func (a *rollingFileAppender) openFile() error { 115 | mode := os.O_WRONLY | os.O_APPEND | os.O_CREATE 116 | if !a.append { 117 | mode = os.O_WRONLY | os.O_CREATE 118 | } 119 | f, err := os.OpenFile(a.filename, mode, 0666) 120 | a.file = f 121 | return err 122 | } 123 | -------------------------------------------------------------------------------- /appenders/rollingfile_test.go: -------------------------------------------------------------------------------- 1 | package appenders 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/stretchr/testify/assert" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestRollingFile(t *testing.T) { 11 | os.Remove("rollingfile_test.log") 12 | a := RollingFile("rollingfile_test.log", true) 13 | assert.NotNil(t, a) 14 | _, err := os.Stat("rollingfile_test.log") 15 | assert.Nil(t, err) 16 | 17 | a.Write(levels.DEBUG, "Test message") 18 | f, err := os.Open("rollingfile_test.log") 19 | assert.Nil(t, err) 20 | assert.NotNil(t, f) 21 | 22 | b := make([]byte, 13) 23 | n, err := f.Read(b) 24 | 25 | assert.Equal(t, n, 13) 26 | assert.Equal(t, string(b), "Test message\n") 27 | 28 | f.Close() 29 | } 30 | 31 | func TestRollingFileTruncate(t *testing.T) { 32 | os.Remove("rollingfile_test.log") 33 | a := RollingFile("rollingfile_test.log", true) 34 | assert.NotNil(t, a) 35 | _, err := os.Stat("rollingfile_test.log") 36 | assert.Nil(t, err) 37 | 38 | a.Write(levels.DEBUG, "Test message") 39 | f, err := os.Open("rollingfile_test.log") 40 | assert.Nil(t, err) 41 | assert.NotNil(t, f) 42 | 43 | b := make([]byte, 13) 44 | n, err := f.Read(b) 45 | 46 | assert.Equal(t, n, 13) 47 | assert.Equal(t, string(b), "Test message\n") 48 | 49 | f.Close() 50 | a.Close() 51 | 52 | a = RollingFile("rollingfile_test.log", false) 53 | assert.NotNil(t, a) 54 | _, err = os.Stat("rollingfile_test.log") 55 | assert.Nil(t, err) 56 | 57 | a.Write(levels.DEBUG, "Foo") 58 | f, err = os.Open("rollingfile_test.log") 59 | assert.Nil(t, err) 60 | assert.NotNil(t, f) 61 | 62 | b = make([]byte, 4) 63 | n, err = f.Read(b) 64 | 65 | assert.Equal(t, n, 4) 66 | assert.Equal(t, string(b), "Foo\n") 67 | 68 | f.Close() 69 | a.Close() 70 | } 71 | 72 | func TestRollingFileRotate(t *testing.T) { 73 | os.Remove("rollingfile_test.log") 74 | a := RollingFile("rollingfile_test.log", true) 75 | assert.NotNil(t, a) 76 | _, err := os.Stat("rollingfile_test.log") 77 | assert.Nil(t, err) 78 | 79 | a.MaxFileSize = 20 80 | 81 | a.Write(levels.DEBUG, "Test message") 82 | 83 | f, err := os.Open("rollingfile_test.log") 84 | assert.Nil(t, err) 85 | assert.NotNil(t, f) 86 | 87 | b := make([]byte, 13) 88 | n, err := f.Read(b) 89 | 90 | assert.Equal(t, n, 13) 91 | assert.Equal(t, string(b), "Test message\n") 92 | 93 | a.Write(levels.DEBUG, "Another test") 94 | b = make([]byte, 13) 95 | n, err = f.Read(b) 96 | 97 | assert.Equal(t, n, 13) 98 | assert.Equal(t, string(b), "Another test\n") 99 | 100 | a.Write(levels.DEBUG, "Yet another test") 101 | 102 | f.Close() 103 | f, err = os.Open("rollingfile_test.log") 104 | assert.Nil(t, err) 105 | assert.NotNil(t, f) 106 | 107 | b = make([]byte, 17) 108 | n, err = f.Read(b) 109 | 110 | assert.Equal(t, n, 17) 111 | assert.Equal(t, string(b), "Yet another test\n") 112 | 113 | f.Close() 114 | 115 | f, err = os.Open("rollingfile_test.log.1") 116 | assert.Nil(t, err) 117 | assert.NotNil(t, f) 118 | 119 | b = make([]byte, 26) 120 | n, err = f.Read(b) 121 | 122 | assert.Equal(t, n, 26) 123 | assert.Equal(t, string(b), "Test message\nAnother test\n") 124 | 125 | f.Close() 126 | a.Close() 127 | } 128 | -------------------------------------------------------------------------------- /layout/basic.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import ( 4 | "fmt" 5 | "github.com/ian-kent/go-log/levels" 6 | ) 7 | 8 | type basicLayout struct { 9 | Layout 10 | } 11 | 12 | func Basic() *basicLayout { 13 | return &basicLayout{} 14 | } 15 | 16 | func (a *basicLayout) Format(level levels.LogLevel, message string, args ...interface{}) string { 17 | return fmt.Sprintf(message, args...) 18 | } 19 | -------------------------------------------------------------------------------- /layout/basic_test.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestBasic(t *testing.T) { 10 | b := Basic() 11 | assert.NotNil(t, b) 12 | 13 | assert.Equal(t, b.Format(levels.DEBUG, "Test message"), "Test message") 14 | assert.Equal(t, b.Format(levels.DEBUG, "Test message %s", "test"), "Test message test") 15 | } 16 | -------------------------------------------------------------------------------- /layout/layout.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | /* 4 | 5 | Layouts control the formatting of data into a printable log string. 6 | 7 | For example, the Basic layout passes the log message and arguments 8 | through fmt.Sprintf. 9 | 10 | Satisfy the Layout interface to implement your own log layout. 11 | 12 | */ 13 | 14 | import ( 15 | "github.com/ian-kent/go-log/levels" 16 | ) 17 | 18 | type Layout interface { 19 | Format(level levels.LogLevel, message string, args ...interface{}) string 20 | } 21 | 22 | func Default() Layout { 23 | return Basic() 24 | } 25 | -------------------------------------------------------------------------------- /layout/pattern.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "regexp" 7 | "runtime" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/ian-kent/go-log/levels" 13 | ) 14 | 15 | // http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html 16 | 17 | // DefaultTimeLayout is the default layout used by %d 18 | var DefaultTimeLayout = "2006-01-02 15:04:05.000000000 -0700 MST" 19 | 20 | // LegacyDefaultTimeLayout is the legacy (non-zero padded) time layout. 21 | // Set layout.DefaultTimeLayout = layout.LegacyDefaultTimeLayout to revert behaviour. 22 | var LegacyDefaultTimeLayout = "2006-01-02 15:04:05.999999999 -0700 MST" 23 | 24 | type patternLayout struct { 25 | Layout 26 | Pattern string 27 | created int64 28 | re *regexp.Regexp 29 | } 30 | 31 | type caller struct { 32 | pc uintptr 33 | file string 34 | line int 35 | ok bool 36 | pkg string 37 | fullpkg string 38 | filename string 39 | } 40 | 41 | func Pattern(pattern string) *patternLayout { 42 | return &patternLayout{ 43 | Pattern: pattern, 44 | re: regexp.MustCompile("%(\\w|%)(?:{([^}]+)})?"), 45 | created: time.Now().UnixNano(), 46 | } 47 | } 48 | 49 | func getCaller() *caller { 50 | pc, file, line, ok := runtime.Caller(2) 51 | 52 | // TODO feels nasty? 53 | dir, fn := filepath.Split(file) 54 | bits := strings.Split(dir, "/") 55 | pkg := bits[len(bits)-2] 56 | 57 | if ok { 58 | return &caller{pc, file, line, ok, pkg, pkg, fn} 59 | } 60 | return nil 61 | } 62 | 63 | func (a *patternLayout) Format(level levels.LogLevel, message string, args ...interface{}) string { 64 | 65 | // TODO 66 | // padding, e.g. %20c, %-20c, %.30c, %20.30c, %-20.30c 67 | // %t - thread name 68 | // %M - function name 69 | 70 | caller := getCaller() 71 | r := time.Now().UnixNano() 72 | 73 | msg := a.re.ReplaceAllStringFunc(a.Pattern, func(m string) string { 74 | parts := a.re.FindStringSubmatch(m) 75 | switch parts[1] { 76 | // FIXME 77 | // %c and %C should probably return the logger name, not the package 78 | // name, since that's how the logger is created in the first place! 79 | case "c": 80 | return caller.pkg 81 | case "C": 82 | return caller.pkg 83 | case "d": 84 | // FIXME specifier, e.g. %d{HH:mm:ss,SSS} 85 | return time.Now().Format(DefaultTimeLayout) 86 | case "F": 87 | return caller.file 88 | case "l": 89 | return fmt.Sprintf("%s/%s:%d", caller.pkg, caller.filename, caller.line) 90 | case "L": 91 | return strconv.Itoa(caller.line) 92 | case "m": 93 | return fmt.Sprintf(message, args...) 94 | case "n": 95 | // FIXME platform-specific? 96 | return "\n" 97 | case "p": 98 | return levels.LogLevelsToString[level] 99 | case "r": 100 | return strconv.FormatInt((r-a.created)/100000, 10) 101 | case "x": 102 | return "" // NDC 103 | case "X": 104 | return "" // MDC (must specify key) 105 | case "%": 106 | return "%" 107 | } 108 | return m 109 | }) 110 | 111 | return msg 112 | } 113 | -------------------------------------------------------------------------------- /layout/pattern_test.go: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | //"os" 8 | ) 9 | 10 | func TestPattern(t *testing.T) { 11 | p := Pattern("") 12 | assert.NotNil(t, p) 13 | 14 | assert.Equal(t, p.Format(levels.DEBUG, "Test message %s", "test"), "") 15 | assert.Equal(t, p.Format(levels.DEBUG, "%c %C %d"), "") 16 | 17 | p = Pattern("%c") 18 | assert.NotNil(t, p) 19 | assert.Equal(t, p.Format(levels.DEBUG, ""), "layout") 20 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "layout") 21 | 22 | p = Pattern("%C") 23 | assert.NotNil(t, p) 24 | assert.Equal(t, p.Format(levels.DEBUG, ""), "layout") 25 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "layout") 26 | 27 | p = Pattern("%d") 28 | assert.NotNil(t, p) 29 | // FIXME 30 | //assert.Equal(t, p.Format(levels.DEBUG, ""), time.Now().String()) 31 | //assert.Equal(t, p.Format(levels.DEBUG, "foo"), time.Now().String()) 32 | 33 | p = Pattern("%F") 34 | assert.NotNil(t, p) 35 | //assert.Equal(t, p.Format(levels.DEBUG, ""), os.Getenv("GOPATH") + "/src/github.com/ian-kent/go-log/layout/pattern_test.go") 36 | //assert.Equal(t, p.Format(levels.DEBUG, "foo"), os.Getenv("GOPATH") + "/src/github.com/ian-kent/go-log/layout/pattern_test.go") 37 | 38 | p = Pattern("%l") 39 | assert.NotNil(t, p) 40 | assert.Equal(t, p.Format(levels.DEBUG, ""), "layout/pattern_test.go:40") 41 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "layout/pattern_test.go:41") 42 | 43 | p = Pattern("%L") 44 | assert.NotNil(t, p) 45 | assert.Equal(t, p.Format(levels.DEBUG, ""), "45") 46 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "46") 47 | 48 | p = Pattern("%m") 49 | assert.NotNil(t, p) 50 | assert.Equal(t, p.Format(levels.DEBUG, "test message"), "test message") 51 | assert.Equal(t, p.Format(levels.DEBUG, "test message %s", "test"), "test message test") 52 | assert.Equal(t, p.Format(levels.DEBUG, "test message %d", 2), "test message 2") 53 | 54 | p = Pattern("%n") 55 | assert.NotNil(t, p) 56 | assert.Equal(t, p.Format(levels.DEBUG, ""), "\n") 57 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "\n") 58 | 59 | p = Pattern("%p") 60 | assert.NotNil(t, p) 61 | assert.Equal(t, p.Format(levels.DEBUG, ""), "DEBUG") 62 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "DEBUG") 63 | 64 | p = Pattern("%r") 65 | assert.NotNil(t, p) 66 | // FIXME 67 | //assert.Equal(t, p.Format(levels.DEBUG, ""), "FIXME") 68 | //assert.Equal(t, p.Format(levels.DEBUG, "foo"), "FIXME") 69 | 70 | p = Pattern("%x") 71 | assert.NotNil(t, p) 72 | // FIXME 73 | //assert.Equal(t, p.Format(levels.DEBUG, ""), "FIXME") 74 | //assert.Equal(t, p.Format(levels.DEBUG, "foo"), "FIXME") 75 | 76 | p = Pattern("%X") 77 | assert.NotNil(t, p) 78 | // FIXME 79 | //assert.Equal(t, p.Format(levels.DEBUG, ""), "FIXME") 80 | //assert.Equal(t, p.Format(levels.DEBUG, "foo"), "FIXME") 81 | 82 | p = Pattern("%%") 83 | assert.NotNil(t, p) 84 | assert.Equal(t, p.Format(levels.DEBUG, ""), "%") 85 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "%") 86 | 87 | p = Pattern("%c %C %l %L %p %m%n") 88 | assert.NotNil(t, p) 89 | assert.Equal(t, p.Format(levels.DEBUG, ""), "layout layout layout/pattern_test.go:89 89 DEBUG \n") 90 | assert.Equal(t, p.Format(levels.DEBUG, "foo"), "layout layout layout/pattern_test.go:90 90 DEBUG foo\n") 91 | assert.Equal(t, p.Format(levels.DEBUG, "foo=%s", "bar"), "layout layout layout/pattern_test.go:91 91 DEBUG foo=bar\n") 92 | } 93 | -------------------------------------------------------------------------------- /levels/levels.go: -------------------------------------------------------------------------------- 1 | package levels 2 | 3 | type LogLevel int 4 | 5 | const ( 6 | FATAL LogLevel = iota 7 | ERROR 8 | INFO 9 | WARN 10 | DEBUG 11 | TRACE 12 | INHERIT 13 | ) 14 | 15 | var StringToLogLevels = map[string]LogLevel{ 16 | "TRACE": TRACE, 17 | "DEBUG": DEBUG, 18 | "WARN": WARN, 19 | "INFO": INFO, 20 | "ERROR": ERROR, 21 | "FATAL": FATAL, 22 | "INHERIT": INHERIT, 23 | } 24 | 25 | var LogLevelsToString = map[LogLevel]string{ 26 | TRACE: "TRACE", 27 | DEBUG: "DEBUG", 28 | WARN: "WARN", 29 | INFO: "INFO", 30 | ERROR: "ERROR", 31 | FATAL: "FATAL", 32 | INHERIT: "INHERIT", 33 | } 34 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/ian-kent/go-log/logger" 6 | "strings" 7 | ) 8 | 9 | var global logger.Logger 10 | 11 | // Converts a string level (e.g. DEBUG) to a LogLevel 12 | func Stol(level string) levels.LogLevel { 13 | return levels.StringToLogLevels[strings.ToUpper(level)] 14 | } 15 | 16 | // Returns a Logger instance 17 | // 18 | // If no arguments are given, the global/root logger 19 | // instance will be returned. 20 | // 21 | // If at least one argument is given, the logger instance 22 | // for that namespace will be returned. 23 | func Logger(args ...string) logger.Logger { 24 | var name string 25 | if len(args) > 0 { 26 | name = args[0] 27 | } else { 28 | name = "" 29 | } 30 | 31 | if global == nil { 32 | global = logger.New("") 33 | global.SetLevel(levels.DEBUG) 34 | } 35 | 36 | l := global.GetLogger(name) 37 | 38 | return l 39 | } 40 | 41 | func Log(level levels.LogLevel, params ...interface{}) { 42 | Logger().Log(level, params...) 43 | } 44 | 45 | func Level(level levels.LogLevel) { Logger().Level() } 46 | func Debug(params ...interface{}) { Log(levels.DEBUG, params...) } 47 | func Info(params ...interface{}) { Log(levels.INFO, params...) } 48 | func Warn(params ...interface{}) { Log(levels.WARN, params...) } 49 | func Error(params ...interface{}) { Log(levels.ERROR, params...) } 50 | func Trace(params ...interface{}) { Log(levels.TRACE, params...) } 51 | func Fatal(params ...interface{}) { Log(levels.FATAL, params...) } 52 | func Printf(params ...interface{}) { Log(levels.INFO, params...) } 53 | func Println(params ...interface{}) { Log(levels.INFO, params...) } 54 | func Fatalf(params ...interface{}) { Log(levels.FATAL, params...) } 55 | -------------------------------------------------------------------------------- /log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestLogger(t *testing.T) { 10 | logger := Logger() 11 | assert.NotNil(t, logger) 12 | assert.Equal(t, logger.Level(), Stol("DEBUG")) 13 | assert.NotNil(t, logger.Name()) 14 | assert.Equal(t, logger.Name(), "") 15 | assert.Equal(t, logger.FullName(), "") 16 | 17 | logger = Logger("foo") 18 | assert.NotNil(t, logger) 19 | assert.Equal(t, logger.Level(), Stol("DEBUG")) 20 | assert.NotNil(t, logger.Name()) 21 | assert.Equal(t, logger.Name(), "foo") 22 | assert.Equal(t, logger.FullName(), "foo") 23 | 24 | logger = Logger("foo.bar") 25 | assert.NotNil(t, logger) 26 | assert.Equal(t, logger.Level(), Stol("DEBUG")) 27 | assert.NotNil(t, logger.Name()) 28 | assert.Equal(t, logger.Name(), "bar") 29 | assert.Equal(t, logger.FullName(), "foo.bar") 30 | } 31 | 32 | func TestLevel(t *testing.T) { 33 | for k, s := range levels.LogLevelsToString { 34 | assert.Equal(t, Stol(s), k) 35 | } 36 | } 37 | 38 | func TestLog(t *testing.T) { 39 | 40 | } 41 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/ian-kent/go-log/appenders" 9 | "github.com/ian-kent/go-log/layout" 10 | "github.com/ian-kent/go-log/levels" 11 | ) 12 | 13 | // Logger represents a logger 14 | type Logger interface { 15 | Level() levels.LogLevel 16 | Name() string 17 | FullName() string 18 | Enabled() map[levels.LogLevel]bool 19 | Appender() Appender 20 | Children() []Logger 21 | Parent() Logger 22 | GetLogger(string) Logger 23 | SetLevel(levels.LogLevel) 24 | Log(levels.LogLevel, ...interface{}) 25 | SetAppender(appender Appender) 26 | 27 | Debug(params ...interface{}) 28 | Info(params ...interface{}) 29 | Warn(params ...interface{}) 30 | Error(params ...interface{}) 31 | Trace(params ...interface{}) 32 | Printf(params ...interface{}) 33 | Println(params ...interface{}) 34 | Fatal(params ...interface{}) 35 | Fatalf(params ...interface{}) 36 | } 37 | 38 | type logger struct { 39 | level levels.LogLevel 40 | name string 41 | enabled map[levels.LogLevel]bool 42 | appender Appender 43 | children []Logger 44 | parent Logger 45 | ExitOnFatal bool 46 | } 47 | 48 | // Appender represents a log appender 49 | type Appender interface { 50 | Write(level levels.LogLevel, message string, args ...interface{}) 51 | SetLayout(layout layout.Layout) 52 | Layout() layout.Layout 53 | } 54 | 55 | // New returns a new Logger 56 | func New(name string) Logger { 57 | l := Logger(&logger{ 58 | level: levels.DEBUG, 59 | name: name, 60 | enabled: make(map[levels.LogLevel]bool), 61 | appender: appenders.Console(), 62 | children: make([]Logger, 0), 63 | parent: nil, 64 | ExitOnFatal: true, 65 | }) 66 | l.SetLevel(levels.DEBUG) 67 | return l 68 | } 69 | 70 | func unwrap(args ...interface{}) []interface{} { 71 | head := args[0] 72 | switch head.(type) { 73 | case func() (string, []interface{}): 74 | msg, args := head.(func() (string, []interface{}))() 75 | args = unwrap(args...) 76 | return append([]interface{}{msg}, args...) 77 | case func() []interface{}: 78 | args = unwrap(head.(func() []interface{})()...) 79 | case func(...interface{}) []interface{}: 80 | args = unwrap(head.(func(...interface{}) []interface{})(args[1:]...)...) 81 | } 82 | return args 83 | } 84 | 85 | func (l *logger) New(name string) Logger { 86 | lg := Logger(&logger{ 87 | level: levels.INHERIT, 88 | name: name, 89 | enabled: make(map[levels.LogLevel]bool), 90 | appender: nil, 91 | children: make([]Logger, 0), 92 | parent: l, 93 | }) 94 | l.children = append(l.children, lg) 95 | return lg 96 | } 97 | 98 | func (l *logger) GetLogger(name string) Logger { 99 | bits := strings.Split(name, ".") 100 | 101 | if l.name == bits[0] { 102 | if len(bits) == 1 { 103 | return l 104 | } 105 | 106 | child := bits[1] 107 | n := strings.Join(bits[1:], ".") 108 | for _, c := range l.children { 109 | if c.Name() == child { 110 | return c.GetLogger(n) 111 | } 112 | } 113 | 114 | lg := l.New(child) 115 | return lg.GetLogger(n) 116 | } 117 | lg := l.New(bits[0]) 118 | return lg.GetLogger(name) 119 | } 120 | 121 | type stringer interface { 122 | String() string 123 | } 124 | 125 | func (l *logger) write(level levels.LogLevel, params ...interface{}) { 126 | a := l.Appender() 127 | if a != nil { 128 | if s, ok := params[0].(string); ok { 129 | a.Write(level, s, params[1:]...) 130 | } else if s, ok := params[0].(stringer); ok { 131 | a.Write(level, s.String(), params[1:]...) 132 | } else { 133 | a.Write(level, fmt.Sprintf("%s", params[0]), params[1:]...) 134 | } 135 | } 136 | } 137 | 138 | func (l *logger) Appender() Appender { 139 | if a := l.appender; a != nil { 140 | return a 141 | } 142 | if l.parent != nil { 143 | if a := l.parent.Appender(); a != nil { 144 | return a 145 | } 146 | } 147 | return nil 148 | } 149 | 150 | func (l *logger) Log(level levels.LogLevel, params ...interface{}) { 151 | if !l.Enabled()[level] { 152 | return 153 | } 154 | l.write(level, unwrap(params...)...) 155 | 156 | if l.ExitOnFatal && level == levels.FATAL { 157 | os.Exit(1) 158 | } 159 | } 160 | 161 | func (l *logger) Level() levels.LogLevel { 162 | if l.level == levels.INHERIT { 163 | return l.parent.Level() 164 | } 165 | return l.level 166 | } 167 | 168 | func (l *logger) Enabled() map[levels.LogLevel]bool { 169 | if l.level == levels.INHERIT { 170 | return l.parent.Enabled() 171 | } 172 | return l.enabled 173 | } 174 | 175 | func (l *logger) Name() string { 176 | return l.name 177 | } 178 | 179 | func (l *logger) FullName() string { 180 | n := l.name 181 | if l.parent != nil { 182 | p := l.parent.FullName() 183 | if len(p) > 0 { 184 | n = l.parent.FullName() + "." + n 185 | } 186 | } 187 | return n 188 | } 189 | 190 | func (l *logger) Children() []Logger { 191 | return l.children 192 | } 193 | 194 | func (l *logger) Parent() Logger { 195 | return l.parent 196 | } 197 | 198 | func (l *logger) SetLevel(level levels.LogLevel) { 199 | l.level = level 200 | for k := range levels.LogLevelsToString { 201 | if k <= level { 202 | l.enabled[k] = true 203 | } else { 204 | l.enabled[k] = false 205 | } 206 | } 207 | } 208 | 209 | func (l *logger) SetAppender(appender Appender) { 210 | l.appender = appender 211 | } 212 | 213 | func (l *logger) Debug(params ...interface{}) { l.Log(levels.DEBUG, params...) } 214 | func (l *logger) Info(params ...interface{}) { l.Log(levels.INFO, params...) } 215 | func (l *logger) Warn(params ...interface{}) { l.Log(levels.WARN, params...) } 216 | func (l *logger) Error(params ...interface{}) { l.Log(levels.ERROR, params...) } 217 | func (l *logger) Trace(params ...interface{}) { l.Log(levels.TRACE, params...) } 218 | func (l *logger) Printf(params ...interface{}) { l.Log(levels.INFO, params...) } 219 | func (l *logger) Println(params ...interface{}) { l.Log(levels.INFO, params...) } 220 | func (l *logger) Fatal(params ...interface{}) { l.Log(levels.FATAL, params...) } 221 | func (l *logger) Fatalf(params ...interface{}) { l.Log(levels.FATAL, params...) } 222 | -------------------------------------------------------------------------------- /logger/logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/ian-kent/go-log/levels" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestNew(t *testing.T) { 10 | logger := New("") 11 | assert.NotNil(t, logger) 12 | assert.Equal(t, logger.Level(), levels.DEBUG) 13 | assert.NotNil(t, logger.Name()) 14 | assert.Equal(t, logger.Name(), "") 15 | 16 | logger = New("foo") 17 | assert.NotNil(t, logger) 18 | assert.Equal(t, logger.Level(), levels.DEBUG) 19 | assert.NotNil(t, logger.Name()) 20 | assert.Equal(t, logger.Name(), "foo") 21 | 22 | logger = New("foo.bar") 23 | assert.NotNil(t, logger) 24 | assert.Equal(t, logger.Level(), levels.DEBUG) 25 | assert.NotNil(t, logger.Name()) 26 | assert.Equal(t, logger.Name(), "foo.bar") 27 | } 28 | 29 | func TestLevels(t *testing.T) { 30 | logger := New("") 31 | assert.NotNil(t, logger) 32 | assert.Equal(t, logger.Level(), levels.DEBUG) 33 | assert.Equal(t, logger.Enabled()[levels.TRACE], false) 34 | assert.Equal(t, logger.Enabled()[levels.DEBUG], true) 35 | assert.Equal(t, logger.Enabled()[levels.WARN], true) 36 | assert.Equal(t, logger.Enabled()[levels.ERROR], true) 37 | assert.Equal(t, logger.Enabled()[levels.INFO], true) 38 | assert.Equal(t, logger.Enabled()[levels.FATAL], true) 39 | 40 | logger.SetLevel(levels.TRACE) 41 | assert.Equal(t, logger.Level(), levels.TRACE) 42 | assert.Equal(t, logger.Enabled()[levels.TRACE], true) 43 | assert.Equal(t, logger.Enabled()[levels.DEBUG], true) 44 | assert.Equal(t, logger.Enabled()[levels.WARN], true) 45 | assert.Equal(t, logger.Enabled()[levels.ERROR], true) 46 | assert.Equal(t, logger.Enabled()[levels.INFO], true) 47 | assert.Equal(t, logger.Enabled()[levels.FATAL], true) 48 | 49 | logger.SetLevel(levels.FATAL) 50 | assert.Equal(t, logger.Level(), levels.FATAL) 51 | assert.Equal(t, logger.Enabled()[levels.TRACE], false) 52 | assert.Equal(t, logger.Enabled()[levels.DEBUG], false) 53 | assert.Equal(t, logger.Enabled()[levels.WARN], false) 54 | assert.Equal(t, logger.Enabled()[levels.ERROR], false) 55 | assert.Equal(t, logger.Enabled()[levels.INFO], false) 56 | assert.Equal(t, logger.Enabled()[levels.FATAL], true) 57 | 58 | logger.SetLevel(levels.INFO) 59 | assert.Equal(t, logger.Level(), levels.INFO) 60 | assert.Equal(t, logger.Enabled()[levels.TRACE], false) 61 | assert.Equal(t, logger.Enabled()[levels.DEBUG], false) 62 | assert.Equal(t, logger.Enabled()[levels.WARN], false) 63 | assert.Equal(t, logger.Enabled()[levels.ERROR], true) 64 | assert.Equal(t, logger.Enabled()[levels.INFO], true) 65 | assert.Equal(t, logger.Enabled()[levels.FATAL], true) 66 | } 67 | 68 | func TestUnwrap(t *testing.T) { 69 | args := unwrap(func(args ...interface{}) []interface{} { 70 | return []interface{}{ 71 | "example log message", 72 | "example args", 73 | } 74 | }) 75 | assert.NotNil(t, args) 76 | assert.Equal(t, len(args), 2) 77 | assert.Equal(t, args[0], "example log message") 78 | assert.Equal(t, args[1], "example args") 79 | 80 | var passedArgs []interface{} 81 | args = unwrap(func(args ...interface{}) []interface{} { 82 | passedArgs = args 83 | return []interface{}{ 84 | "example log message", 85 | "example args", 86 | "more example args", 87 | } 88 | }, "passed args", "more passed args") 89 | assert.NotNil(t, args) 90 | assert.Equal(t, len(args), 3) 91 | assert.Equal(t, args[0], "example log message") 92 | assert.Equal(t, args[1], "example args") 93 | assert.Equal(t, args[2], "more example args") 94 | assert.Equal(t, len(passedArgs), 2) 95 | assert.Equal(t, passedArgs[0], "passed args") 96 | assert.Equal(t, passedArgs[1], "more passed args") 97 | 98 | args = unwrap("example log message", "example args", "more args") 99 | assert.NotNil(t, args) 100 | assert.Equal(t, len(args), 3) 101 | assert.Equal(t, args[0], "example log message") 102 | assert.Equal(t, args[1], "example args") 103 | assert.Equal(t, args[2], "more args") 104 | 105 | args = unwrap(func() []interface{} { 106 | return []interface{}{ 107 | "example log message", 108 | "example args", 109 | "more example args", 110 | } 111 | }) 112 | assert.NotNil(t, args) 113 | assert.Equal(t, len(args), 3) 114 | assert.Equal(t, args[0], "example log message") 115 | assert.Equal(t, args[1], "example args") 116 | assert.Equal(t, args[2], "more example args") 117 | 118 | args = unwrap(func() (string, []interface{}) { 119 | return "example log message", []interface{}{ 120 | "example args", 121 | "more example args", 122 | } 123 | }) 124 | assert.NotNil(t, args) 125 | assert.Equal(t, len(args), 3) 126 | assert.Equal(t, args[0], "example log message") 127 | assert.Equal(t, args[1], "example args") 128 | assert.Equal(t, args[2], "more example args") 129 | } 130 | 131 | func TestWrite(t *testing.T) { 132 | 133 | } 134 | 135 | func TestLog(t *testing.T) { 136 | 137 | } 138 | 139 | func TestGlobalFuncs(t *testing.T) { 140 | 141 | } 142 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | const ( 9 | AppName = "go-log" 10 | AppVersionMajor = 0 11 | AppVersionMinor = 1 12 | ) 13 | 14 | // revision part of the program version. 15 | // This will be set automatically at build time like so: 16 | // 17 | // go build -ldflags "-X main.AppVersionRev `date -u +%s`" 18 | var AppVersionRev string 19 | 20 | func Version() string { 21 | if len(AppVersionRev) == 0 { 22 | AppVersionRev = "0" 23 | } 24 | 25 | return fmt.Sprintf("%s %d.%d.%s (Go runtime %s).\nCopyright (c) 2014 - 2016, Ian Kent.", 26 | AppName, AppVersionMajor, AppVersionMinor, AppVersionRev, runtime.Version()) 27 | } 28 | --------------------------------------------------------------------------------