├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── distillog.go ├── doc.go ├── std.go ├── std_test.go ├── stream.go ├── stream_test.go ├── syslog.go └── util.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | go: 5 | - 1.4 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Akshay Moghe 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/amoghe/distillog.svg)](https://travis-ci.org/amoghe/distillog) 2 | [![Documentation](https://godoc.org/github.com/amoghe/distillog?status.svg)](http://godoc.org/github.com/amoghe/distillog) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/amoghe/distillog)](https://goreportcard.com/report/github.com/amoghe/distillog) 4 | 5 | # What is `distillog`? 6 | 7 | `distillog` aims to offer a minimalistic logging interface that also supports 8 | log levels. It takes the `stdlib` API and only slightly enhances it. Hence, you 9 | could think of it as levelled logging, _distilled_. 10 | 11 | # Yet _another_ logging library for go(lang)? 12 | 13 | > Logging libraries are like opinions, everyone seems to have one -- Anon(?) 14 | 15 | Most other logging libraries do either __too little__ ([stdlib][0]) 16 | or __too much__ ([glog][1]). 17 | 18 | As with most other libraries, this one is opinionated. In terms of functionality 19 | it exposes, it attempts to sit somewhere between the stdlib and the majority of 20 | other logging libraries available (but leans mostly towards the spartan side 21 | of stdlib). 22 | 23 | ## The stdlib does _too little_, you say? 24 | 25 | Just a smidge. 26 | 27 | Presenting varying levels of verbosity (or severity) are an important part of 28 | what makes a program more usable or debuggable. For example, `debug` or `info` 29 | level messages may be useful to the developers during the development cycle. 30 | These messages may be dropped or suppressed in production since they are not 31 | useful to everyone. Similarly `warning` messages may be emitted when a error has 32 | been gracefully handled but the program would like to notify its human overlords 33 | of some impending doom. 34 | 35 | In most cases, some downstream entity "knows" how to filter the messages and 36 | keep those that are relevant to the environment. As evidence of this, most 37 | other languages have log libraries that support levels. Similarly some programs 38 | offer varying verbosity levels (e.g. `-v`, `-vv` etc). The golang stdlib takes 39 | a much more spartan approach (exposing only `Println` and friends) so using it 40 | in programs to emit messages of varying interest/levels can get tedious (manual 41 | prefixes, anyone?). This is where `distillog` steps in. It aims to slightly 42 | improve on this minimalstic logging interface. __Slightly__. 43 | 44 | ## Other libraries do _too much_, you say? 45 | 46 | Ever used `log.Panicf` or `log.Fatalf`? Exiting your program is *not* something 47 | your log library should be doing! Similarly, other libraries offer options for 48 | maintaining old log files and rotating them. Your logging library shouldn't need 49 | to care about this. Whatever facility (other libraries call this a "backend") 50 | messages are sent to should determine how old messages are handled. `distillog` 51 | prefers that you use `lumberjack` (or an equivalent WriteCloser) depending on 52 | where you choose to persist the messages. 53 | 54 | > But log file rotation is absolutely necessary! 55 | 56 | Agreed, and someone's gotta do it, but it need not be your logging library! 57 | 58 | You can use `distillog` along with a [lumberjack][2] "backend". It provides an 59 | `io.WriteCloser` which performs all the magic you need. Initialize a logger 60 | using `distillog.NewStream`, pass it an instance of the `io.WriteCloser` 61 | that lumberjack returns, _et voila_, you have a logger that does what you need. 62 | 63 | ## And how is `distillog` different? 64 | 65 | `distillog` aims to offer a only slightly richer interface than the stdlib. 66 | 67 | To this end, it restricts itself to: 68 | - presenting a minimal interface so that you can emit levelled log messages 69 | - providing logger implementations for logging to the most common backends 70 | - streams - e.g. stderr/stdout 71 | - files - anything via `io.WriteCloser` (via `lumberjack`) 72 | - syslog 73 | - avoid taking on any non-essential responsibilities (colors, _ahem_) 74 | - expose a logger interface, instead of an implementation 75 | 76 | ## Expose an interface? Why? 77 | 78 | By exposing an interface you can write programs that use levelled log messages, 79 | but switch between logging to various facilities by simply instantiating the 80 | appropriate logger as determined by the caller (Your program can offer a 81 | command-line switch like so - `--log-to=[syslog,stderr,]` and the simply 82 | instantiate the appropriate logger). 83 | 84 | # Usage/examples: 85 | 86 | As seen in the [godoc](https://godoc.org/github.com/amoghe/distillog#Logger), 87 | the interface is limited to: 88 | 89 | ```golang 90 | type Logger interface { 91 | Debugf(format string, v ...interface{}) 92 | Debugln(v ...interface{}) 93 | 94 | Infof(format string, v ...interface{}) 95 | Infoln(v ...interface{}) 96 | 97 | Warningf(format string, v ...interface{}) 98 | Warningln(v ...interface{}) 99 | 100 | Errorf(format string, v ...interface{}) 101 | Errorln(v ...interface{}) 102 | 103 | Close() error 104 | } 105 | ``` 106 | 107 | Log to stdout, or stderr using a logger instantiated like so: 108 | 109 | ```golang 110 | outLogger := distillog.NewStdoutLogger("test") 111 | 112 | errLogger := distillog.NewStderrLogger("test") 113 | 114 | sysLogger := distillog.NewSyslogLogger("test") 115 | ``` 116 | 117 | Alternatively, you can use the package for your logging needs: 118 | 119 | ```golang 120 | import log "github.com/amoghe/distillog" 121 | 122 | // ... later ... 123 | 124 | log.Infoln("Starting program") 125 | log.Debugln("initializing the frobnicator") 126 | log.Warningln("frobnicator failure detected, proceeding anyways...") 127 | log.Infoln("Exiting") 128 | ``` 129 | 130 | If you have a file you wish to log to, you should open the file and instantiate 131 | a logger using the file handle, like so: 132 | 133 | ```golang 134 | if fileHandle, err := ioutil.Tempfile("/tmp", "distillog-test"); err == nil { 135 | fileLogger := distillog.NewStreamLogger("test", fileHandle) 136 | } 137 | ``` 138 | 139 | If you need a logger that manages the rotation of its files, use `lumberjack`, 140 | like so: 141 | 142 | ```golang 143 | lumberjackHandle := &lumberjack.Logger{ 144 | Filename: "/var/log/myapp/foo.log", 145 | MaxSize: 500, // megabytes 146 | MaxBackups: 3, 147 | MaxAge: 28, // days 148 | } 149 | 150 | logger := distillog.NewStreamLogger("tag", lumberjackHandle) 151 | 152 | // Alternatively, configure the pkg level logger to emit here 153 | 154 | distillog.SetOutput(lumberjackHandle) 155 | ``` 156 | 157 | Once instantiated, you can log messages, like so: 158 | 159 | ```golang 160 | var := "World!" 161 | myLogger.Infof("Hello, %s", var) 162 | myLogger.Warningln("Goodbye, cruel world!") 163 | 164 | ``` 165 | 166 | # Contributing 167 | 168 | 1. Create an issue, describe the bugfix/feature you wish to implement. 169 | 2. Fork the repository 170 | 3. Create your feature branch (`git checkout -b my-new-feature`) 171 | 4. Commit your changes (`git commit -am 'Add some feature'`) 172 | 5. Push to the branch (`git push origin my-new-feature`) 173 | 6. Create a new Pull Request 174 | 175 | # License 176 | 177 | See [LICENSE.txt](LICENSE.txt) 178 | 179 | [0]: https://golang.org/pkg/log/ 180 | [1]: https://github.com/golang/glog 181 | [2]: https://github.com/natefinch/lumberjack 182 | -------------------------------------------------------------------------------- /distillog.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | const ( 4 | timeFormatStr = "Mon Jan 2 15:04:05" 5 | ) 6 | 7 | // Logger defines a distilled interface for logging messages from your program. 8 | // Note: All functions append a trailing newline if one doesn't exist. 9 | type Logger interface { 10 | Debugf(format string, v ...interface{}) 11 | Debugln(v ...interface{}) 12 | 13 | Infof(format string, v ...interface{}) 14 | Infoln(v ...interface{}) 15 | 16 | Warningf(format string, v ...interface{}) 17 | Warningln(v ...interface{}) 18 | 19 | Errorf(format string, v ...interface{}) 20 | Errorln(v ...interface{}) 21 | 22 | Close() error 23 | } 24 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package distillog provides a minimalistic logging interface (inspired by the 3 | stdlib) that also supports levelled logging. 4 | 5 | You can instantiate a logger (that adheres to the Logger interface) or use the 6 | pkg level log functions (like the stdlib). 7 | 8 | You can set where the log messages are sent to by either either instantiating 9 | your own logger using the appropriate constructor function, or by using 10 | SetOutput to configure the pkg level logger. 11 | 12 | You may also use `lumberjack` or a similar library in conjunction with this 13 | one to manage (rotate) your log files. 14 | 15 | Visit the README at https://github.com/amoghe/distillog 16 | */ 17 | package distillog 18 | -------------------------------------------------------------------------------- /std.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // 8 | // This file exposes package level logging functions so that this package 9 | // can be used directly for logging (to stderr) without needing the caller 10 | // to instantiate a logger (a la stdlib) 11 | // 12 | 13 | var ( 14 | std = NewStderrLogger("") 15 | ) 16 | 17 | // Debugf logs a message to stderr at 'debug' level 18 | func Debugf(format string, v ...interface{}) { 19 | std.Debugf(format, v...) 20 | } 21 | 22 | // Debugln logs a message to stderr at 'debug' level 23 | func Debugln(v ...interface{}) { 24 | std.Debugln(v...) 25 | } 26 | 27 | // Infof logs a message to stderr at 'info' level 28 | func Infof(format string, v ...interface{}) { 29 | std.Infof(format, v...) 30 | } 31 | 32 | // Infoln logs a message to stderr at 'info' level 33 | func Infoln(v ...interface{}) { 34 | std.Infoln(v...) 35 | } 36 | 37 | // Warningf logs a message to stderr at 'warn' level 38 | func Warningf(format string, v ...interface{}) { 39 | std.Warningf(format, v...) 40 | } 41 | 42 | // Warningln logs a message to stderr at 'warn' level 43 | func Warningln(v ...interface{}) { 44 | std.Warningln(v...) 45 | } 46 | 47 | // Errorf logs a message to stderr at 'error' level 48 | func Errorf(format string, v ...interface{}) { 49 | std.Errorf(format, v...) 50 | } 51 | 52 | // Errorln logs a message to stderr at 'error' level 53 | func Errorln(v ...interface{}) { 54 | std.Errorln(v...) 55 | } 56 | 57 | // Close closes the stream to which the default logger logs to 58 | func Close() { 59 | std.Close() 60 | } 61 | 62 | // SetOutput allows you to configure the package level logger to emit to the 63 | // specified output stream. 64 | // NOTE: this is not safe when called concurrently from multiple goroutines. 65 | // This should typically be called once during program initialization before 66 | // spawning goroutines that may use the log. 67 | func SetOutput(out io.WriteCloser) { 68 | std = NewStreamLogger("", out) 69 | } 70 | -------------------------------------------------------------------------------- /std_test.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestSetStream(t *testing.T) { 9 | strm := &dummyStream{} 10 | 11 | SetOutput(strm) 12 | Infoln("random message") 13 | 14 | if !strings.Contains(strm.String(), "random message") { 15 | t.Error("expected string not found in stream buffer") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | // streamLogger implements the Logger interface and writes to the specified io.Writer ("stream"). 13 | // It mimics the stdlib logger including memory optimizations such as minimizing calls to fmt.Sprintf 14 | // and using a shared buffer to format the message before writing it out. 15 | type streamLogger struct { 16 | stream io.WriteCloser 17 | tag string 18 | linebuf []byte 19 | lock sync.Mutex 20 | } 21 | 22 | // NewStderrLogger returns a Logger that outputs messages to Stderr. 23 | func NewStderrLogger(tag string) Logger { 24 | return &streamLogger{ 25 | tag: tag, 26 | linebuf: []byte{}, 27 | stream: noopCloser{os.Stderr}, 28 | } 29 | } 30 | 31 | // NewStdoutLogger returns a Logger that outputs messages to Stdout. 32 | func NewStdoutLogger(tag string) Logger { 33 | return &streamLogger{ 34 | tag: tag, 35 | linebuf: []byte{}, 36 | stream: noopCloser{os.Stdout}, 37 | } 38 | } 39 | 40 | // NewNullLogger returns a logger that drops messages (outputs to /dev/null). 41 | func NewNullLogger(tag string) Logger { 42 | return &streamLogger{ 43 | tag: tag, 44 | linebuf: []byte{}, 45 | stream: noopCloser{ioutil.Discard}, 46 | } 47 | } 48 | 49 | // NewStreamLogger returns a Logger that outputs messages to the specified stream. 50 | func NewStreamLogger(tag string, stream io.WriteCloser) Logger { 51 | return &streamLogger{ 52 | tag: tag, 53 | linebuf: []byte{}, 54 | stream: stream, 55 | } 56 | } 57 | 58 | // writes a formatted message (w/ timestamp, level) to the output stream. 59 | func (w *streamLogger) output(timeStr, level, msg string) { 60 | // We need to serialize access to the linebuffer that is used to assemble the message \ 61 | // as well as the output stream we will print to. 62 | w.lock.Lock() 63 | 64 | // save memory, (re)use a buffer instead of relying on fmt.Sprintf to format the output string 65 | w.linebuf = w.linebuf[:0] 66 | 67 | w.linebuf = append(w.linebuf, timeStr...) 68 | w.linebuf = append(w.linebuf, ' ') 69 | w.linebuf = append(w.linebuf, w.tag...) 70 | w.linebuf = append(w.linebuf, ' ') 71 | 72 | w.linebuf = append(w.linebuf, '[') 73 | w.linebuf = fixedWidthStr(5, level, w.linebuf) 74 | w.linebuf = append(w.linebuf, ']') 75 | 76 | w.linebuf = append(w.linebuf, ' ') 77 | w.linebuf = append(w.linebuf, msg...) 78 | 79 | if len(msg) == 0 || msg[len(msg)-1] != '\n' { 80 | w.linebuf = append(w.linebuf, '\n') 81 | } 82 | 83 | w.stream.Write(w.linebuf) 84 | w.lock.Unlock() 85 | } 86 | 87 | func (w *streamLogger) Debugf(f string, v ...interface{}) { 88 | msg := fmt.Sprintf(f, v...) 89 | now := time.Now().Format(timeFormatStr) 90 | w.output(now, "DEBUG", msg) 91 | } 92 | 93 | func (w *streamLogger) Debugln(v ...interface{}) { 94 | msg := fmt.Sprintln(v...) 95 | now := time.Now().Format(timeFormatStr) 96 | w.output(now, "DEBUG", msg) 97 | } 98 | 99 | func (w *streamLogger) Infof(f string, v ...interface{}) { 100 | msg := fmt.Sprintf(f, v...) 101 | now := time.Now().Format(timeFormatStr) 102 | w.output(now, "INFO", msg) 103 | } 104 | 105 | func (w *streamLogger) Infoln(v ...interface{}) { 106 | msg := fmt.Sprintln(v...) 107 | now := time.Now().Format(timeFormatStr) 108 | w.output(now, "INFO", msg) 109 | } 110 | 111 | func (w *streamLogger) Warningf(f string, v ...interface{}) { 112 | msg := fmt.Sprintf(f, v...) 113 | now := time.Now().Format(timeFormatStr) 114 | w.output(now, "WARN", msg) 115 | } 116 | 117 | func (w *streamLogger) Warningln(v ...interface{}) { 118 | msg := fmt.Sprintln(v...) 119 | now := time.Now().Format(timeFormatStr) 120 | w.output(now, "WARN", msg) 121 | } 122 | 123 | func (w *streamLogger) Errorf(f string, v ...interface{}) { 124 | msg := fmt.Sprintf(f, v...) 125 | now := time.Now().Format(timeFormatStr) 126 | w.output(now, "ERROR", msg) 127 | } 128 | 129 | func (w *streamLogger) Errorln(v ...interface{}) { 130 | msg := fmt.Sprintln(v...) 131 | now := time.Now().Format(timeFormatStr) 132 | w.output(now, "ERROR", msg) 133 | } 134 | 135 | func (w *streamLogger) Close() error { 136 | w.lock.Lock() 137 | defer w.lock.Unlock() 138 | 139 | return w.stream.Close() 140 | } 141 | -------------------------------------------------------------------------------- /stream_test.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | type dummyStream struct { 11 | bytes.Buffer 12 | } 13 | 14 | func (d *dummyStream) Close() error { return nil } 15 | 16 | func TestOutput(t *testing.T) { 17 | 18 | strm := dummyStream{} 19 | tlog := &streamLogger{ 20 | tag: "TAG", 21 | linebuf: []byte{}, 22 | stream: &strm, 23 | } 24 | tlog.output("12345", "LEVEL", "message") 25 | 26 | got := strm.String() 27 | exp := "12345 TAG [LEVEL] message\n" 28 | if got != exp { 29 | t.Error("unexpected output", got, "vs", exp) 30 | } 31 | } 32 | 33 | func TestLevelDebugf(t *testing.T) { 34 | strm := dummyStream{} 35 | tlog := &streamLogger{ 36 | tag: "TAG", 37 | linebuf: []byte{}, 38 | stream: &strm, 39 | } 40 | 41 | msg := "informational message" 42 | tlog.Debugf("%s", msg) 43 | 44 | got := strm.String() 45 | exp := fmt.Sprintf("[DEBUG] %s", msg) 46 | 47 | if !strings.Contains(got, exp) { 48 | t.Error("output does not contain message", got) 49 | } 50 | } 51 | 52 | func TestLevelDebugln(t *testing.T) { 53 | strm := dummyStream{} 54 | tlog := &streamLogger{ 55 | tag: "TAG", 56 | linebuf: []byte{}, 57 | stream: &strm, 58 | } 59 | 60 | msg := "informational message" 61 | tlog.Debugln(msg) 62 | 63 | got := strm.String() 64 | exp := fmt.Sprintf("[DEBUG] %s", msg) 65 | 66 | if !strings.Contains(got, exp) { 67 | t.Error("output does not contain message", got) 68 | } 69 | } 70 | 71 | func TestLevelInfoln(t *testing.T) { 72 | strm := dummyStream{} 73 | tlog := &streamLogger{ 74 | tag: "TAG", 75 | linebuf: []byte{}, 76 | stream: &strm, 77 | } 78 | 79 | msg := "informational message" 80 | tlog.Infoln(msg) 81 | 82 | got := strm.String() 83 | exp := fmt.Sprintf("[INFO ] %s", msg) 84 | 85 | if !strings.Contains(got, exp) { 86 | t.Error("output does not contain message", got) 87 | } 88 | } 89 | 90 | func TestLevelInfof(t *testing.T) { 91 | strm := dummyStream{} 92 | tlog := &streamLogger{ 93 | tag: "TAG", 94 | linebuf: []byte{}, 95 | stream: &strm, 96 | } 97 | 98 | msg := "informational message" 99 | tlog.Infof("%s", msg) 100 | 101 | got := strm.String() 102 | exp := fmt.Sprintf("[INFO ] %s", msg) 103 | 104 | if !strings.Contains(got, exp) { 105 | t.Error("output does not contain message", got) 106 | } 107 | } 108 | 109 | func TestLevelWarningln(t *testing.T) { 110 | strm := dummyStream{} 111 | tlog := &streamLogger{ 112 | tag: "TAG", 113 | linebuf: []byte{}, 114 | stream: &strm, 115 | } 116 | 117 | msg := "informational message" 118 | tlog.Warningln(msg) 119 | 120 | got := strm.String() 121 | exp := fmt.Sprintf("[WARN ] %s", msg) 122 | 123 | if !strings.Contains(got, exp) { 124 | t.Error("output does not contain message", got) 125 | } 126 | } 127 | 128 | func TestLevelWarningf(t *testing.T) { 129 | strm := dummyStream{} 130 | tlog := &streamLogger{ 131 | tag: "TAG", 132 | linebuf: []byte{}, 133 | stream: &strm, 134 | } 135 | 136 | msg := "informational message" 137 | tlog.Warningf("%s", msg) 138 | 139 | got := strm.String() 140 | exp := fmt.Sprintf("[WARN ] %s", msg) 141 | 142 | if !strings.Contains(got, exp) { 143 | t.Error("output does not contain message", got) 144 | } 145 | } 146 | 147 | func TestLevelErrorln(t *testing.T) { 148 | strm := dummyStream{} 149 | tlog := &streamLogger{ 150 | tag: "TAG", 151 | linebuf: []byte{}, 152 | stream: &strm, 153 | } 154 | 155 | msg := "informational message" 156 | tlog.Errorln(msg) 157 | 158 | got := strm.String() 159 | exp := fmt.Sprintf("[ERROR] %s", msg) 160 | 161 | if !strings.Contains(got, exp) { 162 | t.Error("output does not contain message", got) 163 | } 164 | } 165 | 166 | func TestLevelErrorf(t *testing.T) { 167 | strm := dummyStream{} 168 | tlog := &streamLogger{ 169 | tag: "TAG", 170 | linebuf: []byte{}, 171 | stream: &strm, 172 | } 173 | 174 | msg := "informational message" 175 | tlog.Errorf("%s", msg) 176 | 177 | got := strm.String() 178 | exp := fmt.Sprintf("[ERROR] %s", msg) 179 | 180 | if !strings.Contains(got, exp) { 181 | t.Error("output does not contain message", got) 182 | } 183 | } 184 | 185 | func BenchmarkThroughput(b *testing.B) { 186 | tlog := NewNullLogger("") 187 | 188 | runParallelBody := func(pb *testing.PB) { 189 | for pb.Next() { 190 | tlog.Infoln("one", "iteration") 191 | } 192 | } 193 | 194 | b.RunParallel(runParallelBody) 195 | } 196 | 197 | // func BenchmarkStdlibThroughput(b *testing.B) { 198 | // log.SetOutput(ioutil.Discard) 199 | 200 | // runParallelBody := func(pb *testing.PB) { 201 | // for pb.Next() { 202 | // log.Println("one", "iteration") 203 | // } 204 | // } 205 | 206 | // b.RunParallel(runParallelBody) 207 | // } 208 | -------------------------------------------------------------------------------- /syslog.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin freebsd netbsd openbsd !windows 2 | 3 | package distillog 4 | 5 | import ( 6 | "fmt" 7 | "log/syslog" 8 | ) 9 | 10 | // wraps around a syslog.Writer to make it adhere to the `Logger` interface 11 | // exposed by this package 12 | type wrappedSyslogWriter struct { 13 | writer *syslog.Writer 14 | } 15 | 16 | // NewSyslogLogger returns a Logger that sends messages to the Syslog daemon. 17 | // This will panic if it is unable to connect to the local syslog daemon. 18 | func NewSyslogLogger(tag string) Logger { 19 | l, err := syslog.New(syslog.LOG_DAEMON, tag) 20 | if err != nil { 21 | panic(err) 22 | } 23 | return &wrappedSyslogWriter{l} 24 | } 25 | 26 | func (w *wrappedSyslogWriter) Debugf(f string, v ...interface{}) { 27 | w.writer.Debug(fmt.Sprintf(f, v...)) 28 | } 29 | func (w *wrappedSyslogWriter) Debugln(v ...interface{}) { 30 | w.writer.Debug(fmt.Sprintln(v...)) 31 | } 32 | func (w *wrappedSyslogWriter) Infof(f string, v ...interface{}) { 33 | w.writer.Info(fmt.Sprintf(f, v...)) 34 | } 35 | func (w *wrappedSyslogWriter) Infoln(v ...interface{}) { 36 | w.writer.Info(fmt.Sprintln(v...)) 37 | } 38 | func (w *wrappedSyslogWriter) Warningf(f string, v ...interface{}) { 39 | w.writer.Warning(fmt.Sprintf(f, v...)) 40 | } 41 | func (w *wrappedSyslogWriter) Warningln(v ...interface{}) { 42 | w.writer.Warning(fmt.Sprintln(v...)) 43 | } 44 | func (w *wrappedSyslogWriter) Errorf(f string, v ...interface{}) { 45 | w.writer.Err(fmt.Sprintf(f, v...)) 46 | } 47 | func (w *wrappedSyslogWriter) Errorln(v ...interface{}) { 48 | w.writer.Err(fmt.Sprintln(v...)) 49 | } 50 | func (w *wrappedSyslogWriter) Close() error { return nil } 51 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package distillog 2 | 3 | import ( 4 | "io" 5 | "runtime" 6 | ) 7 | 8 | // wrap around a io.Writer, and provide a dummy Close method. 9 | type noopCloser struct { 10 | io.Writer 11 | } 12 | 13 | func (n noopCloser) Close() error { return nil } 14 | 15 | // If we ever want to print callers file:line info in the message. 16 | func callerFileLine() (string, int) { 17 | if _, file, line, ok := runtime.Caller(3); ok { 18 | return file, line 19 | } 20 | return "???", 0 21 | 22 | } 23 | 24 | // appends a fixed width string 'str' into byte buffer 'b'. Appends spaces if 'str' is too short. 25 | func fixedWidthStr(width int, str string, b []byte) []byte { 26 | // Write as many bytes as 'width', writing spaces if we run out of chars 27 | for i := 0; i < width; i++ { 28 | if i < len(str) { 29 | b = append(b, str[i]) 30 | } else { 31 | b = append(b, ' ') 32 | } 33 | } 34 | return b 35 | } 36 | --------------------------------------------------------------------------------