├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── backend.go ├── example_test.go ├── examples ├── example.go └── example.png ├── format.go ├── format_test.go ├── level.go ├── level_test.go ├── log_nix.go ├── log_test.go ├── log_windows.go ├── logger.go ├── logger_test.go ├── memory.go ├── memory_test.go ├── multi.go ├── multi_test.go ├── syslog.go └── syslog_fallback.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - tip 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0-rc1 (2016-02-11) 4 | 5 | Time flies and it has been three years since this package was first released. 6 | There have been a couple of API changes I have wanted to do for some time but 7 | I've tried to maintain backwards compatibility. Some inconsistencies in the 8 | API have started to show, proper vendor support in Go out of the box and 9 | the fact that `go vet` will give warnings -- I have decided to bump the major 10 | version. 11 | 12 | * Make eg. `Info` and `Infof` do different things. You want to change all calls 13 | to `Info` with a string format go to `Infof` etc. In many cases, `go vet` will 14 | guide you. 15 | * `Id` in `Record` is now called `ID` 16 | 17 | ## 1.0.0 (2013-02-21) 18 | 19 | Initial release 20 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alec Thomas 2 | Guilhem Lettron 3 | Ivan Daniluk 4 | Nimi Wariboko Jr 5 | Róbert Selvek 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Örjan Persson. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Golang logging library 2 | 3 | [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/op/go-logging) [![build](https://img.shields.io/travis/op/go-logging.svg?style=flat)](https://travis-ci.org/op/go-logging) 4 | 5 | Package logging implements a logging infrastructure for Go. Its output format 6 | is customizable and supports different logging backends like syslog, file and 7 | memory. Multiple backends can be utilized with different log levels per backend 8 | and logger. 9 | 10 | **_NOTE:_** backwards compatibility promise have been dropped for master. Please 11 | vendor this package or use `gopkg.in/op/go-logging.v1` for previous version. See 12 | [changelog](CHANGELOG.md) for details. 13 | 14 | ## Example 15 | 16 | Let's have a look at an [example](examples/example.go) which demonstrates most 17 | of the features found in this library. 18 | 19 | [![Example Output](examples/example.png)](examples/example.go) 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "os" 26 | 27 | "github.com/op/go-logging" 28 | ) 29 | 30 | var log = logging.MustGetLogger("example") 31 | 32 | // Example format string. Everything except the message has a custom color 33 | // which is dependent on the log level. Many fields have a custom output 34 | // formatting too, eg. the time returns the hour down to the milli second. 35 | var format = logging.MustStringFormatter( 36 | `%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 37 | ) 38 | 39 | // Password is just an example type implementing the Redactor interface. Any 40 | // time this is logged, the Redacted() function will be called. 41 | type Password string 42 | 43 | func (p Password) Redacted() interface{} { 44 | return logging.Redact(string(p)) 45 | } 46 | 47 | func main() { 48 | // For demo purposes, create two backend for os.Stderr. 49 | backend1 := logging.NewLogBackend(os.Stderr, "", 0) 50 | backend2 := logging.NewLogBackend(os.Stderr, "", 0) 51 | 52 | // For messages written to backend2 we want to add some additional 53 | // information to the output, including the used log level and the name of 54 | // the function. 55 | backend2Formatter := logging.NewBackendFormatter(backend2, format) 56 | 57 | // Only errors and more severe messages should be sent to backend1 58 | backend1Leveled := logging.AddModuleLevel(backend1) 59 | backend1Leveled.SetLevel(logging.ERROR, "") 60 | 61 | // Set the backends to be used. 62 | logging.SetBackend(backend1Leveled, backend2Formatter) 63 | 64 | log.Debugf("debug %s", Password("secret")) 65 | log.Info("info") 66 | log.Notice("notice") 67 | log.Warning("warning") 68 | log.Error("err") 69 | log.Critical("crit") 70 | } 71 | ``` 72 | 73 | ## Installing 74 | 75 | ### Using *go get* 76 | 77 | $ go get github.com/op/go-logging 78 | 79 | After this command *go-logging* is ready to use. Its source will be in: 80 | 81 | $GOPATH/src/pkg/github.com/op/go-logging 82 | 83 | You can use `go get -u` to update the package. 84 | 85 | ## Documentation 86 | 87 | For docs, see http://godoc.org/github.com/op/go-logging or run: 88 | 89 | $ godoc github.com/op/go-logging 90 | 91 | ## Additional resources 92 | 93 | * [wslog](https://godoc.org/github.com/cryptix/exp/wslog) -- exposes log messages through a WebSocket. 94 | -------------------------------------------------------------------------------- /backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | // defaultBackend is the backend used for all logging calls. 8 | var defaultBackend LeveledBackend 9 | 10 | // Backend is the interface which a log backend need to implement to be able to 11 | // be used as a logging backend. 12 | type Backend interface { 13 | Log(Level, int, *Record) error 14 | } 15 | 16 | // SetBackend replaces the backend currently set with the given new logging 17 | // backend. 18 | func SetBackend(backends ...Backend) LeveledBackend { 19 | var backend Backend 20 | if len(backends) == 1 { 21 | backend = backends[0] 22 | } else { 23 | backend = MultiLogger(backends...) 24 | } 25 | 26 | defaultBackend = AddModuleLevel(backend) 27 | return defaultBackend 28 | } 29 | 30 | // SetLevel sets the logging level for the specified module. The module 31 | // corresponds to the string specified in GetLogger. 32 | func SetLevel(level Level, module string) { 33 | defaultBackend.SetLevel(level, module) 34 | } 35 | 36 | // GetLevel returns the logging level for the specified module. 37 | func GetLevel(module string) Level { 38 | return defaultBackend.GetLevel(module) 39 | } 40 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "os" 4 | 5 | func Example() { 6 | // This call is for testing purposes and will set the time to unix epoch. 7 | InitForTesting(DEBUG) 8 | 9 | var log = MustGetLogger("example") 10 | 11 | // For demo purposes, create two backend for os.Stdout. 12 | // 13 | // os.Stderr should most likely be used in the real world but then the 14 | // "Output:" check in this example would not work. 15 | backend1 := NewLogBackend(os.Stdout, "", 0) 16 | backend2 := NewLogBackend(os.Stdout, "", 0) 17 | 18 | // For messages written to backend2 we want to add some additional 19 | // information to the output, including the used log level and the name of 20 | // the function. 21 | var format = MustStringFormatter( 22 | `%{time:15:04:05.000} %{shortfunc} %{level:.1s} %{message}`, 23 | ) 24 | backend2Formatter := NewBackendFormatter(backend2, format) 25 | 26 | // Only errors and more severe messages should be sent to backend2 27 | backend2Leveled := AddModuleLevel(backend2Formatter) 28 | backend2Leveled.SetLevel(ERROR, "") 29 | 30 | // Set the backends to be used and the default level. 31 | SetBackend(backend1, backend2Leveled) 32 | 33 | log.Debugf("debug %s", "arg") 34 | log.Error("error") 35 | 36 | // Output: 37 | // debug arg 38 | // error 39 | // 00:00:00.000 Example E error 40 | } 41 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/op/go-logging" 7 | ) 8 | 9 | var log = logging.MustGetLogger("example") 10 | 11 | // Example format string. Everything except the message has a custom color 12 | // which is dependent on the log level. Many fields have a custom output 13 | // formatting too, eg. the time returns the hour down to the milli second. 14 | var format = logging.MustStringFormatter( 15 | `%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 16 | ) 17 | 18 | // Password is just an example type implementing the Redactor interface. Any 19 | // time this is logged, the Redacted() function will be called. 20 | type Password string 21 | 22 | func (p Password) Redacted() interface{} { 23 | return logging.Redact(string(p)) 24 | } 25 | 26 | func main() { 27 | // For demo purposes, create two backend for os.Stderr. 28 | backend1 := logging.NewLogBackend(os.Stderr, "", 0) 29 | backend2 := logging.NewLogBackend(os.Stderr, "", 0) 30 | 31 | // For messages written to backend2 we want to add some additional 32 | // information to the output, including the used log level and the name of 33 | // the function. 34 | backend2Formatter := logging.NewBackendFormatter(backend2, format) 35 | 36 | // Only errors and more severe messages should be sent to backend1 37 | backend1Leveled := logging.AddModuleLevel(backend1) 38 | backend1Leveled.SetLevel(logging.ERROR, "") 39 | 40 | // Set the backends to be used. 41 | logging.SetBackend(backend1Leveled, backend2Formatter) 42 | 43 | log.Debugf("debug %s", Password("secret")) 44 | log.Info("info") 45 | log.Notice("notice") 46 | log.Warning("warning") 47 | log.Error("err") 48 | log.Critical("crit") 49 | } 50 | -------------------------------------------------------------------------------- /examples/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/op/go-logging/970db520ece77730c7e4724c61121037378659d9/examples/example.png -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "regexp" 16 | "runtime" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | // TODO see Formatter interface in fmt/print.go 24 | // TODO try text/template, maybe it have enough performance 25 | // TODO other template systems? 26 | // TODO make it possible to specify formats per backend? 27 | type fmtVerb int 28 | 29 | const ( 30 | fmtVerbTime fmtVerb = iota 31 | fmtVerbLevel 32 | fmtVerbID 33 | fmtVerbPid 34 | fmtVerbProgram 35 | fmtVerbModule 36 | fmtVerbMessage 37 | fmtVerbLongfile 38 | fmtVerbShortfile 39 | fmtVerbLongpkg 40 | fmtVerbShortpkg 41 | fmtVerbLongfunc 42 | fmtVerbShortfunc 43 | fmtVerbCallpath 44 | fmtVerbLevelColor 45 | 46 | // Keep last, there are no match for these below. 47 | fmtVerbUnknown 48 | fmtVerbStatic 49 | ) 50 | 51 | var fmtVerbs = []string{ 52 | "time", 53 | "level", 54 | "id", 55 | "pid", 56 | "program", 57 | "module", 58 | "message", 59 | "longfile", 60 | "shortfile", 61 | "longpkg", 62 | "shortpkg", 63 | "longfunc", 64 | "shortfunc", 65 | "callpath", 66 | "color", 67 | } 68 | 69 | const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00" 70 | 71 | var defaultVerbsLayout = []string{ 72 | rfc3339Milli, 73 | "s", 74 | "d", 75 | "d", 76 | "s", 77 | "s", 78 | "s", 79 | "s", 80 | "s", 81 | "s", 82 | "s", 83 | "s", 84 | "s", 85 | "0", 86 | "", 87 | } 88 | 89 | var ( 90 | pid = os.Getpid() 91 | program = filepath.Base(os.Args[0]) 92 | ) 93 | 94 | func getFmtVerbByName(name string) fmtVerb { 95 | for i, verb := range fmtVerbs { 96 | if name == verb { 97 | return fmtVerb(i) 98 | } 99 | } 100 | return fmtVerbUnknown 101 | } 102 | 103 | // Formatter is the required interface for a custom log record formatter. 104 | type Formatter interface { 105 | Format(calldepth int, r *Record, w io.Writer) error 106 | } 107 | 108 | // formatter is used by all backends unless otherwise overriden. 109 | var formatter struct { 110 | sync.RWMutex 111 | def Formatter 112 | } 113 | 114 | func getFormatter() Formatter { 115 | formatter.RLock() 116 | defer formatter.RUnlock() 117 | return formatter.def 118 | } 119 | 120 | var ( 121 | // DefaultFormatter is the default formatter used and is only the message. 122 | DefaultFormatter = MustStringFormatter("%{message}") 123 | 124 | // GlogFormatter mimics the glog format 125 | GlogFormatter = MustStringFormatter("%{level:.1s}%{time:0102 15:04:05.999999} %{pid} %{shortfile}] %{message}") 126 | ) 127 | 128 | // SetFormatter sets the default formatter for all new backends. A backend will 129 | // fetch this value once it is needed to format a record. Note that backends 130 | // will cache the formatter after the first point. For now, make sure to set 131 | // the formatter before logging. 132 | func SetFormatter(f Formatter) { 133 | formatter.Lock() 134 | defer formatter.Unlock() 135 | formatter.def = f 136 | } 137 | 138 | var formatRe = regexp.MustCompile(`%{([a-z]+)(?::(.*?[^\\]))?}`) 139 | 140 | type part struct { 141 | verb fmtVerb 142 | layout string 143 | } 144 | 145 | // stringFormatter contains a list of parts which explains how to build the 146 | // formatted string passed on to the logging backend. 147 | type stringFormatter struct { 148 | parts []part 149 | } 150 | 151 | // NewStringFormatter returns a new Formatter which outputs the log record as a 152 | // string based on the 'verbs' specified in the format string. 153 | // 154 | // The verbs: 155 | // 156 | // General: 157 | // %{id} Sequence number for log message (uint64). 158 | // %{pid} Process id (int) 159 | // %{time} Time when log occurred (time.Time) 160 | // %{level} Log level (Level) 161 | // %{module} Module (string) 162 | // %{program} Basename of os.Args[0] (string) 163 | // %{message} Message (string) 164 | // %{longfile} Full file name and line number: /a/b/c/d.go:23 165 | // %{shortfile} Final file name element and line number: d.go:23 166 | // %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call ~. meaning truncated path 167 | // %{color} ANSI color based on log level 168 | // 169 | // For normal types, the output can be customized by using the 'verbs' defined 170 | // in the fmt package, eg. '%{id:04d}' to make the id output be '%04d' as the 171 | // format string. 172 | // 173 | // For time.Time, use the same layout as time.Format to change the time format 174 | // when output, eg "2006-01-02T15:04:05.999Z-07:00". 175 | // 176 | // For the 'color' verb, the output can be adjusted to either use bold colors, 177 | // i.e., '%{color:bold}' or to reset the ANSI attributes, i.e., 178 | // '%{color:reset}' Note that if you use the color verb explicitly, be sure to 179 | // reset it or else the color state will persist past your log message. e.g., 180 | // "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will 181 | // just colorize the time and level, leaving the message uncolored. 182 | // 183 | // For the 'callpath' verb, the output can be adjusted to limit the printing 184 | // the stack depth. i.e. '%{callpath:3}' will print '~.a.b.c' 185 | // 186 | // Colors on Windows is unfortunately not supported right now and is currently 187 | // a no-op. 188 | // 189 | // There's also a couple of experimental 'verbs'. These are exposed to get 190 | // feedback and needs a bit of tinkering. Hence, they might change in the 191 | // future. 192 | // 193 | // Experimental: 194 | // %{longpkg} Full package path, eg. github.com/go-logging 195 | // %{shortpkg} Base package path, eg. go-logging 196 | // %{longfunc} Full function name, eg. littleEndian.PutUint32 197 | // %{shortfunc} Base function name, eg. PutUint32 198 | // %{callpath} Call function path, eg. main.a.b.c 199 | func NewStringFormatter(format string) (Formatter, error) { 200 | var fmter = &stringFormatter{} 201 | 202 | // Find the boundaries of all %{vars} 203 | matches := formatRe.FindAllStringSubmatchIndex(format, -1) 204 | if matches == nil { 205 | return nil, errors.New("logger: invalid log format: " + format) 206 | } 207 | 208 | // Collect all variables and static text for the format 209 | prev := 0 210 | for _, m := range matches { 211 | start, end := m[0], m[1] 212 | if start > prev { 213 | fmter.add(fmtVerbStatic, format[prev:start]) 214 | } 215 | 216 | name := format[m[2]:m[3]] 217 | verb := getFmtVerbByName(name) 218 | if verb == fmtVerbUnknown { 219 | return nil, errors.New("logger: unknown variable: " + name) 220 | } 221 | 222 | // Handle layout customizations or use the default. If this is not for the 223 | // time, color formatting or callpath, we need to prefix with %. 224 | layout := defaultVerbsLayout[verb] 225 | if m[4] != -1 { 226 | layout = format[m[4]:m[5]] 227 | } 228 | if verb != fmtVerbTime && verb != fmtVerbLevelColor && verb != fmtVerbCallpath { 229 | layout = "%" + layout 230 | } 231 | 232 | fmter.add(verb, layout) 233 | prev = end 234 | } 235 | end := format[prev:] 236 | if end != "" { 237 | fmter.add(fmtVerbStatic, end) 238 | } 239 | 240 | // Make a test run to make sure we can format it correctly. 241 | t, err := time.Parse(time.RFC3339, "2010-02-04T21:00:57-08:00") 242 | if err != nil { 243 | panic(err) 244 | } 245 | testFmt := "hello %s" 246 | r := &Record{ 247 | ID: 12345, 248 | Time: t, 249 | Module: "logger", 250 | Args: []interface{}{"go"}, 251 | fmt: &testFmt, 252 | } 253 | if err := fmter.Format(0, r, &bytes.Buffer{}); err != nil { 254 | return nil, err 255 | } 256 | 257 | return fmter, nil 258 | } 259 | 260 | // MustStringFormatter is equivalent to NewStringFormatter with a call to panic 261 | // on error. 262 | func MustStringFormatter(format string) Formatter { 263 | f, err := NewStringFormatter(format) 264 | if err != nil { 265 | panic("Failed to initialized string formatter: " + err.Error()) 266 | } 267 | return f 268 | } 269 | 270 | func (f *stringFormatter) add(verb fmtVerb, layout string) { 271 | f.parts = append(f.parts, part{verb, layout}) 272 | } 273 | 274 | func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) error { 275 | for _, part := range f.parts { 276 | if part.verb == fmtVerbStatic { 277 | output.Write([]byte(part.layout)) 278 | } else if part.verb == fmtVerbTime { 279 | output.Write([]byte(r.Time.Format(part.layout))) 280 | } else if part.verb == fmtVerbLevelColor { 281 | doFmtVerbLevelColor(part.layout, r.Level, output) 282 | } else if part.verb == fmtVerbCallpath { 283 | depth, err := strconv.Atoi(part.layout) 284 | if err != nil { 285 | depth = 0 286 | } 287 | output.Write([]byte(formatCallpath(calldepth+1, depth))) 288 | } else { 289 | var v interface{} 290 | switch part.verb { 291 | case fmtVerbLevel: 292 | v = r.Level 293 | break 294 | case fmtVerbID: 295 | v = r.ID 296 | break 297 | case fmtVerbPid: 298 | v = pid 299 | break 300 | case fmtVerbProgram: 301 | v = program 302 | break 303 | case fmtVerbModule: 304 | v = r.Module 305 | break 306 | case fmtVerbMessage: 307 | v = r.Message() 308 | break 309 | case fmtVerbLongfile, fmtVerbShortfile: 310 | _, file, line, ok := runtime.Caller(calldepth + 1) 311 | if !ok { 312 | file = "???" 313 | line = 0 314 | } else if part.verb == fmtVerbShortfile { 315 | file = filepath.Base(file) 316 | } 317 | v = fmt.Sprintf("%s:%d", file, line) 318 | case fmtVerbLongfunc, fmtVerbShortfunc, 319 | fmtVerbLongpkg, fmtVerbShortpkg: 320 | // TODO cache pc 321 | v = "???" 322 | if pc, _, _, ok := runtime.Caller(calldepth + 1); ok { 323 | if f := runtime.FuncForPC(pc); f != nil { 324 | v = formatFuncName(part.verb, f.Name()) 325 | } 326 | } 327 | default: 328 | panic("unhandled format part") 329 | } 330 | fmt.Fprintf(output, part.layout, v) 331 | } 332 | } 333 | return nil 334 | } 335 | 336 | // formatFuncName tries to extract certain part of the runtime formatted 337 | // function name to some pre-defined variation. 338 | // 339 | // This function is known to not work properly if the package path or name 340 | // contains a dot. 341 | func formatFuncName(v fmtVerb, f string) string { 342 | i := strings.LastIndex(f, "/") 343 | j := strings.Index(f[i+1:], ".") 344 | if j < 1 { 345 | return "???" 346 | } 347 | pkg, fun := f[:i+j+1], f[i+j+2:] 348 | switch v { 349 | case fmtVerbLongpkg: 350 | return pkg 351 | case fmtVerbShortpkg: 352 | return path.Base(pkg) 353 | case fmtVerbLongfunc: 354 | return fun 355 | case fmtVerbShortfunc: 356 | i = strings.LastIndex(fun, ".") 357 | return fun[i+1:] 358 | } 359 | panic("unexpected func formatter") 360 | } 361 | 362 | func formatCallpath(calldepth int, depth int) string { 363 | v := "" 364 | callers := make([]uintptr, 64) 365 | n := runtime.Callers(calldepth+2, callers) 366 | oldPc := callers[n-1] 367 | 368 | start := n - 3 369 | if depth > 0 && start >= depth { 370 | start = depth - 1 371 | v += "~." 372 | } 373 | recursiveCall := false 374 | for i := start; i >= 0; i-- { 375 | pc := callers[i] 376 | if oldPc == pc { 377 | recursiveCall = true 378 | continue 379 | } 380 | oldPc = pc 381 | if recursiveCall { 382 | recursiveCall = false 383 | v += ".." 384 | } 385 | if i < start { 386 | v += "." 387 | } 388 | if f := runtime.FuncForPC(pc); f != nil { 389 | v += formatFuncName(fmtVerbShortfunc, f.Name()) 390 | } 391 | } 392 | return v 393 | } 394 | 395 | // backendFormatter combines a backend with a specific formatter making it 396 | // possible to have different log formats for different backends. 397 | type backendFormatter struct { 398 | b Backend 399 | f Formatter 400 | } 401 | 402 | // NewBackendFormatter creates a new backend which makes all records that 403 | // passes through it beeing formatted by the specific formatter. 404 | func NewBackendFormatter(b Backend, f Formatter) Backend { 405 | return &backendFormatter{b, f} 406 | } 407 | 408 | // Log implements the Log function required by the Backend interface. 409 | func (bf *backendFormatter) Log(level Level, calldepth int, r *Record) error { 410 | // Make a shallow copy of the record and replace any formatter 411 | r2 := *r 412 | r2.formatter = bf.f 413 | return bf.b.Log(level, calldepth+1, &r2) 414 | } 415 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestFormat(t *testing.T) { 13 | backend := InitForTesting(DEBUG) 14 | 15 | f, err := NewStringFormatter("%{shortfile} %{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}") 16 | if err != nil { 17 | t.Fatalf("failed to set format: %s", err) 18 | } 19 | SetFormatter(f) 20 | 21 | log := MustGetLogger("module") 22 | log.Debug("hello") 23 | 24 | line := MemoryRecordN(backend, 0).Formatted(0) 25 | if "format_test.go:24 1970-01-01T00:00:00 D 0001 module hello" != line { 26 | t.Errorf("Unexpected format: %s", line) 27 | } 28 | } 29 | 30 | func logAndGetLine(backend *MemoryBackend) string { 31 | MustGetLogger("foo").Debug("hello") 32 | return MemoryRecordN(backend, 0).Formatted(1) 33 | } 34 | 35 | func getLastLine(backend *MemoryBackend) string { 36 | return MemoryRecordN(backend, 0).Formatted(1) 37 | } 38 | 39 | func realFunc(backend *MemoryBackend) string { 40 | return logAndGetLine(backend) 41 | } 42 | 43 | type structFunc struct{} 44 | 45 | func (structFunc) Log(backend *MemoryBackend) string { 46 | return logAndGetLine(backend) 47 | } 48 | 49 | func TestRealFuncFormat(t *testing.T) { 50 | backend := InitForTesting(DEBUG) 51 | SetFormatter(MustStringFormatter("%{shortfunc}")) 52 | 53 | line := realFunc(backend) 54 | if "realFunc" != line { 55 | t.Errorf("Unexpected format: %s", line) 56 | } 57 | } 58 | 59 | func TestStructFuncFormat(t *testing.T) { 60 | backend := InitForTesting(DEBUG) 61 | SetFormatter(MustStringFormatter("%{longfunc}")) 62 | 63 | var x structFunc 64 | line := x.Log(backend) 65 | if "structFunc.Log" != line { 66 | t.Errorf("Unexpected format: %s", line) 67 | } 68 | } 69 | 70 | func TestVarFuncFormat(t *testing.T) { 71 | backend := InitForTesting(DEBUG) 72 | SetFormatter(MustStringFormatter("%{shortfunc}")) 73 | 74 | var varFunc = func() string { 75 | return logAndGetLine(backend) 76 | } 77 | 78 | line := varFunc() 79 | if "???" == line || "TestVarFuncFormat" == line || "varFunc" == line { 80 | t.Errorf("Unexpected format: %s", line) 81 | } 82 | } 83 | 84 | func TestFormatFuncName(t *testing.T) { 85 | var tests = []struct { 86 | filename string 87 | longpkg string 88 | shortpkg string 89 | longfunc string 90 | shortfunc string 91 | }{ 92 | {"", 93 | "???", 94 | "???", 95 | "???", 96 | "???"}, 97 | {"main", 98 | "???", 99 | "???", 100 | "???", 101 | "???"}, 102 | {"main.", 103 | "main", 104 | "main", 105 | "", 106 | ""}, 107 | {"main.main", 108 | "main", 109 | "main", 110 | "main", 111 | "main"}, 112 | {"github.com/op/go-logging.func·001", 113 | "github.com/op/go-logging", 114 | "go-logging", 115 | "func·001", 116 | "func·001"}, 117 | {"github.com/op/go-logging.stringFormatter.Format", 118 | "github.com/op/go-logging", 119 | "go-logging", 120 | "stringFormatter.Format", 121 | "Format"}, 122 | } 123 | 124 | var v string 125 | for _, test := range tests { 126 | v = formatFuncName(fmtVerbLongpkg, test.filename) 127 | if test.longpkg != v { 128 | t.Errorf("%s != %s", test.longpkg, v) 129 | } 130 | v = formatFuncName(fmtVerbShortpkg, test.filename) 131 | if test.shortpkg != v { 132 | t.Errorf("%s != %s", test.shortpkg, v) 133 | } 134 | v = formatFuncName(fmtVerbLongfunc, test.filename) 135 | if test.longfunc != v { 136 | t.Errorf("%s != %s", test.longfunc, v) 137 | } 138 | v = formatFuncName(fmtVerbShortfunc, test.filename) 139 | if test.shortfunc != v { 140 | t.Errorf("%s != %s", test.shortfunc, v) 141 | } 142 | } 143 | } 144 | 145 | func TestBackendFormatter(t *testing.T) { 146 | InitForTesting(DEBUG) 147 | 148 | // Create two backends and wrap one of the with a backend formatter 149 | b1 := NewMemoryBackend(1) 150 | b2 := NewMemoryBackend(1) 151 | 152 | f := MustStringFormatter("%{level} %{message}") 153 | bf := NewBackendFormatter(b2, f) 154 | 155 | SetBackend(b1, bf) 156 | 157 | log := MustGetLogger("module") 158 | log.Info("foo") 159 | if "foo" != getLastLine(b1) { 160 | t.Errorf("Unexpected line: %s", getLastLine(b1)) 161 | } 162 | if "INFO foo" != getLastLine(b2) { 163 | t.Errorf("Unexpected line: %s", getLastLine(b2)) 164 | } 165 | } 166 | 167 | func BenchmarkStringFormatter(b *testing.B) { 168 | fmt := "%{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}" 169 | f := MustStringFormatter(fmt) 170 | 171 | backend := InitForTesting(DEBUG) 172 | buf := &bytes.Buffer{} 173 | log := MustGetLogger("module") 174 | log.Debug("") 175 | record := MemoryRecordN(backend, 0) 176 | 177 | b.ResetTimer() 178 | for i := 0; i < b.N; i++ { 179 | if err := f.Format(1, record, buf); err != nil { 180 | b.Fatal(err) 181 | buf.Truncate(0) 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /level.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "errors" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | // ErrInvalidLogLevel is used when an invalid log level has been used. 14 | var ErrInvalidLogLevel = errors.New("logger: invalid log level") 15 | 16 | // Level defines all available log levels for log messages. 17 | type Level int 18 | 19 | // Log levels. 20 | const ( 21 | CRITICAL Level = iota 22 | ERROR 23 | WARNING 24 | NOTICE 25 | INFO 26 | DEBUG 27 | ) 28 | 29 | var levelNames = []string{ 30 | "CRITICAL", 31 | "ERROR", 32 | "WARNING", 33 | "NOTICE", 34 | "INFO", 35 | "DEBUG", 36 | } 37 | 38 | // String returns the string representation of a logging level. 39 | func (p Level) String() string { 40 | return levelNames[p] 41 | } 42 | 43 | // LogLevel returns the log level from a string representation. 44 | func LogLevel(level string) (Level, error) { 45 | for i, name := range levelNames { 46 | if strings.EqualFold(name, level) { 47 | return Level(i), nil 48 | } 49 | } 50 | return ERROR, ErrInvalidLogLevel 51 | } 52 | 53 | // Leveled interface is the interface required to be able to add leveled 54 | // logging. 55 | type Leveled interface { 56 | GetLevel(string) Level 57 | SetLevel(Level, string) 58 | IsEnabledFor(Level, string) bool 59 | } 60 | 61 | // LeveledBackend is a log backend with additional knobs for setting levels on 62 | // individual modules to different levels. 63 | type LeveledBackend interface { 64 | Backend 65 | Leveled 66 | } 67 | 68 | type moduleLeveled struct { 69 | levels map[string]Level 70 | backend Backend 71 | formatter Formatter 72 | once sync.Once 73 | } 74 | 75 | // AddModuleLevel wraps a log backend with knobs to have different log levels 76 | // for different modules. 77 | func AddModuleLevel(backend Backend) LeveledBackend { 78 | var leveled LeveledBackend 79 | var ok bool 80 | if leveled, ok = backend.(LeveledBackend); !ok { 81 | leveled = &moduleLeveled{ 82 | levels: make(map[string]Level), 83 | backend: backend, 84 | } 85 | } 86 | return leveled 87 | } 88 | 89 | // GetLevel returns the log level for the given module. 90 | func (l *moduleLeveled) GetLevel(module string) Level { 91 | level, exists := l.levels[module] 92 | if exists == false { 93 | level, exists = l.levels[""] 94 | // no configuration exists, default to debug 95 | if exists == false { 96 | level = DEBUG 97 | } 98 | } 99 | return level 100 | } 101 | 102 | // SetLevel sets the log level for the given module. 103 | func (l *moduleLeveled) SetLevel(level Level, module string) { 104 | l.levels[module] = level 105 | } 106 | 107 | // IsEnabledFor will return true if logging is enabled for the given module. 108 | func (l *moduleLeveled) IsEnabledFor(level Level, module string) bool { 109 | return level <= l.GetLevel(module) 110 | } 111 | 112 | func (l *moduleLeveled) Log(level Level, calldepth int, rec *Record) (err error) { 113 | if l.IsEnabledFor(level, rec.Module) { 114 | // TODO get rid of traces of formatter here. BackendFormatter should be used. 115 | rec.formatter = l.getFormatterAndCacheCurrent() 116 | err = l.backend.Log(level, calldepth+1, rec) 117 | } 118 | return 119 | } 120 | 121 | func (l *moduleLeveled) getFormatterAndCacheCurrent() Formatter { 122 | l.once.Do(func() { 123 | if l.formatter == nil { 124 | l.formatter = getFormatter() 125 | } 126 | }) 127 | return l.formatter 128 | } 129 | -------------------------------------------------------------------------------- /level_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | func TestLevelString(t *testing.T) { 10 | // Make sure all levels can be converted from string -> constant -> string 11 | for _, name := range levelNames { 12 | level, err := LogLevel(name) 13 | if err != nil { 14 | t.Errorf("failed to get level: %v", err) 15 | continue 16 | } 17 | 18 | if level.String() != name { 19 | t.Errorf("invalid level conversion: %v != %v", level, name) 20 | } 21 | } 22 | } 23 | 24 | func TestLevelLogLevel(t *testing.T) { 25 | tests := []struct { 26 | expected Level 27 | level string 28 | }{ 29 | {-1, "bla"}, 30 | {INFO, "iNfO"}, 31 | {ERROR, "error"}, 32 | {WARNING, "warninG"}, 33 | } 34 | 35 | for _, test := range tests { 36 | level, err := LogLevel(test.level) 37 | if err != nil { 38 | if test.expected == -1 { 39 | continue 40 | } else { 41 | t.Errorf("failed to convert %s: %s", test.level, err) 42 | } 43 | } 44 | if test.expected != level { 45 | t.Errorf("failed to convert %s to level: %s != %s", test.level, test.expected, level) 46 | } 47 | } 48 | } 49 | 50 | func TestLevelModuleLevel(t *testing.T) { 51 | backend := NewMemoryBackend(128) 52 | 53 | leveled := AddModuleLevel(backend) 54 | leveled.SetLevel(NOTICE, "") 55 | leveled.SetLevel(ERROR, "foo") 56 | leveled.SetLevel(INFO, "foo.bar") 57 | leveled.SetLevel(WARNING, "bar") 58 | 59 | expected := []struct { 60 | level Level 61 | module string 62 | }{ 63 | {NOTICE, ""}, 64 | {NOTICE, "something"}, 65 | {ERROR, "foo"}, 66 | {INFO, "foo.bar"}, 67 | {WARNING, "bar"}, 68 | } 69 | 70 | for _, e := range expected { 71 | actual := leveled.GetLevel(e.module) 72 | if e.level != actual { 73 | t.Errorf("unexpected level in %s: %s != %s", e.module, e.level, actual) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /log_nix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | // Copyright 2013, Örjan Persson. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | 7 | package logging 8 | 9 | import ( 10 | "bytes" 11 | "fmt" 12 | "io" 13 | "log" 14 | ) 15 | 16 | type color int 17 | 18 | const ( 19 | ColorBlack = iota + 30 20 | ColorRed 21 | ColorGreen 22 | ColorYellow 23 | ColorBlue 24 | ColorMagenta 25 | ColorCyan 26 | ColorWhite 27 | ) 28 | 29 | var ( 30 | colors = []string{ 31 | CRITICAL: ColorSeq(ColorMagenta), 32 | ERROR: ColorSeq(ColorRed), 33 | WARNING: ColorSeq(ColorYellow), 34 | NOTICE: ColorSeq(ColorGreen), 35 | DEBUG: ColorSeq(ColorCyan), 36 | } 37 | boldcolors = []string{ 38 | CRITICAL: ColorSeqBold(ColorMagenta), 39 | ERROR: ColorSeqBold(ColorRed), 40 | WARNING: ColorSeqBold(ColorYellow), 41 | NOTICE: ColorSeqBold(ColorGreen), 42 | DEBUG: ColorSeqBold(ColorCyan), 43 | } 44 | ) 45 | 46 | // LogBackend utilizes the standard log module. 47 | type LogBackend struct { 48 | Logger *log.Logger 49 | Color bool 50 | ColorConfig []string 51 | } 52 | 53 | // NewLogBackend creates a new LogBackend. 54 | func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend { 55 | return &LogBackend{Logger: log.New(out, prefix, flag)} 56 | } 57 | 58 | // Log implements the Backend interface. 59 | func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error { 60 | if b.Color { 61 | col := colors[level] 62 | if len(b.ColorConfig) > int(level) && b.ColorConfig[level] != "" { 63 | col = b.ColorConfig[level] 64 | } 65 | 66 | buf := &bytes.Buffer{} 67 | buf.Write([]byte(col)) 68 | buf.Write([]byte(rec.Formatted(calldepth + 1))) 69 | buf.Write([]byte("\033[0m")) 70 | // For some reason, the Go logger arbitrarily decided "2" was the correct 71 | // call depth... 72 | return b.Logger.Output(calldepth+2, buf.String()) 73 | } 74 | 75 | return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1)) 76 | } 77 | 78 | // ConvertColors takes a list of ints representing colors for log levels and 79 | // converts them into strings for ANSI color formatting 80 | func ConvertColors(colors []int, bold bool) []string { 81 | converted := []string{} 82 | for _, i := range colors { 83 | if bold { 84 | converted = append(converted, ColorSeqBold(color(i))) 85 | } else { 86 | converted = append(converted, ColorSeq(color(i))) 87 | } 88 | } 89 | 90 | return converted 91 | } 92 | 93 | func ColorSeq(color color) string { 94 | return fmt.Sprintf("\033[%dm", int(color)) 95 | } 96 | 97 | func ColorSeqBold(color color) string { 98 | return fmt.Sprintf("\033[%d;1m", int(color)) 99 | } 100 | 101 | func doFmtVerbLevelColor(layout string, level Level, output io.Writer) { 102 | if layout == "bold" { 103 | output.Write([]byte(boldcolors[level])) 104 | } else if layout == "reset" { 105 | output.Write([]byte("\033[0m")) 106 | } else { 107 | output.Write([]byte(colors[level])) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "log" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func TestLogCalldepth(t *testing.T) { 16 | buf := &bytes.Buffer{} 17 | SetBackend(NewLogBackend(buf, "", log.Lshortfile)) 18 | SetFormatter(MustStringFormatter("%{shortfile} %{level} %{message}")) 19 | 20 | log := MustGetLogger("test") 21 | log.Info("test filename") 22 | 23 | parts := strings.SplitN(buf.String(), " ", 2) 24 | 25 | // Verify that the correct filename is registered by the stdlib logger 26 | if !strings.HasPrefix(parts[0], "log_test.go:") { 27 | t.Errorf("incorrect filename: %s", parts[0]) 28 | } 29 | // Verify that the correct filename is registered by go-logging 30 | if !strings.HasPrefix(parts[1], "log_test.go:") { 31 | t.Errorf("incorrect filename: %s", parts[1]) 32 | } 33 | } 34 | 35 | func c(log *Logger) { log.Info("test callpath") } 36 | func b(log *Logger) { c(log) } 37 | func a(log *Logger) { b(log) } 38 | 39 | func rec(log *Logger, r int) { 40 | if r == 0 { 41 | a(log) 42 | return 43 | } 44 | rec(log, r-1) 45 | } 46 | 47 | func testCallpath(t *testing.T, format string, expect string) { 48 | buf := &bytes.Buffer{} 49 | SetBackend(NewLogBackend(buf, "", log.Lshortfile)) 50 | SetFormatter(MustStringFormatter(format)) 51 | 52 | logger := MustGetLogger("test") 53 | rec(logger, 6) 54 | 55 | parts := strings.SplitN(buf.String(), " ", 3) 56 | 57 | // Verify that the correct filename is registered by the stdlib logger 58 | if !strings.HasPrefix(parts[0], "log_test.go:") { 59 | t.Errorf("incorrect filename: %s", parts[0]) 60 | } 61 | // Verify that the correct callpath is registered by go-logging 62 | if !strings.HasPrefix(parts[1], expect) { 63 | t.Errorf("incorrect callpath: %s", parts[1]) 64 | } 65 | // Verify that the correct message is registered by go-logging 66 | if !strings.HasPrefix(parts[2], "test callpath") { 67 | t.Errorf("incorrect message: %s", parts[2]) 68 | } 69 | } 70 | 71 | func TestLogCallpath(t *testing.T) { 72 | testCallpath(t, "%{callpath} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 73 | testCallpath(t, "%{callpath:-1} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 74 | testCallpath(t, "%{callpath:0} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 75 | testCallpath(t, "%{callpath:1} %{message}", "~.c") 76 | testCallpath(t, "%{callpath:2} %{message}", "~.b.c") 77 | testCallpath(t, "%{callpath:3} %{message}", "~.a.b.c") 78 | } 79 | 80 | func BenchmarkLogMemoryBackendIgnored(b *testing.B) { 81 | backend := SetBackend(NewMemoryBackend(1024)) 82 | backend.SetLevel(INFO, "") 83 | RunLogBenchmark(b) 84 | } 85 | 86 | func BenchmarkLogMemoryBackend(b *testing.B) { 87 | backend := SetBackend(NewMemoryBackend(1024)) 88 | backend.SetLevel(DEBUG, "") 89 | RunLogBenchmark(b) 90 | } 91 | 92 | func BenchmarkLogChannelMemoryBackend(b *testing.B) { 93 | channelBackend := NewChannelMemoryBackend(1024) 94 | backend := SetBackend(channelBackend) 95 | backend.SetLevel(DEBUG, "") 96 | RunLogBenchmark(b) 97 | channelBackend.Flush() 98 | } 99 | 100 | func BenchmarkLogLeveled(b *testing.B) { 101 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 102 | backend.SetLevel(INFO, "") 103 | 104 | RunLogBenchmark(b) 105 | } 106 | 107 | func BenchmarkLogLogBackend(b *testing.B) { 108 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 109 | backend.SetLevel(DEBUG, "") 110 | RunLogBenchmark(b) 111 | } 112 | 113 | func BenchmarkLogLogBackendColor(b *testing.B) { 114 | colorizer := NewLogBackend(ioutil.Discard, "", 0) 115 | colorizer.Color = true 116 | backend := SetBackend(colorizer) 117 | backend.SetLevel(DEBUG, "") 118 | RunLogBenchmark(b) 119 | } 120 | 121 | func BenchmarkLogLogBackendStdFlags(b *testing.B) { 122 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.LstdFlags)) 123 | backend.SetLevel(DEBUG, "") 124 | RunLogBenchmark(b) 125 | } 126 | 127 | func BenchmarkLogLogBackendLongFileFlag(b *testing.B) { 128 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.Llongfile)) 129 | backend.SetLevel(DEBUG, "") 130 | RunLogBenchmark(b) 131 | } 132 | 133 | func RunLogBenchmark(b *testing.B) { 134 | password := Password("foo") 135 | log := MustGetLogger("test") 136 | 137 | b.ResetTimer() 138 | for i := 0; i < b.N; i++ { 139 | log.Debug("log line for %d and this is rectified: %s", i, password) 140 | } 141 | } 142 | 143 | func BenchmarkLogFixed(b *testing.B) { 144 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 145 | backend.SetLevel(DEBUG, "") 146 | 147 | RunLogBenchmarkFixedString(b) 148 | } 149 | 150 | func BenchmarkLogFixedIgnored(b *testing.B) { 151 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 152 | backend.SetLevel(INFO, "") 153 | RunLogBenchmarkFixedString(b) 154 | } 155 | 156 | func RunLogBenchmarkFixedString(b *testing.B) { 157 | log := MustGetLogger("test") 158 | 159 | b.ResetTimer() 160 | for i := 0; i < b.N; i++ { 161 | log.Debug("some random fixed text") 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /log_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | // Copyright 2013, Örjan Persson. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package logging 7 | 8 | import ( 9 | "bytes" 10 | "io" 11 | "log" 12 | "syscall" 13 | ) 14 | 15 | var ( 16 | kernel32DLL = syscall.NewLazyDLL("kernel32.dll") 17 | setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") 18 | ) 19 | 20 | // Character attributes 21 | // Note: 22 | // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan). 23 | // Clearing all foreground or background colors results in black; setting all creates white. 24 | // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes. 25 | const ( 26 | fgBlack = 0x0000 27 | fgBlue = 0x0001 28 | fgGreen = 0x0002 29 | fgCyan = 0x0003 30 | fgRed = 0x0004 31 | fgMagenta = 0x0005 32 | fgYellow = 0x0006 33 | fgWhite = 0x0007 34 | fgIntensity = 0x0008 35 | fgMask = 0x000F 36 | ) 37 | 38 | var ( 39 | colors = []uint16{ 40 | INFO: fgWhite, 41 | CRITICAL: fgMagenta, 42 | ERROR: fgRed, 43 | WARNING: fgYellow, 44 | NOTICE: fgGreen, 45 | DEBUG: fgCyan, 46 | } 47 | boldcolors = []uint16{ 48 | INFO: fgWhite | fgIntensity, 49 | CRITICAL: fgMagenta | fgIntensity, 50 | ERROR: fgRed | fgIntensity, 51 | WARNING: fgYellow | fgIntensity, 52 | NOTICE: fgGreen | fgIntensity, 53 | DEBUG: fgCyan | fgIntensity, 54 | } 55 | ) 56 | 57 | type file interface { 58 | Fd() uintptr 59 | } 60 | 61 | // LogBackend utilizes the standard log module. 62 | type LogBackend struct { 63 | Logger *log.Logger 64 | Color bool 65 | 66 | // f is set to a non-nil value if the underlying writer which logs writes to 67 | // implements the file interface. This makes us able to colorise the output. 68 | f file 69 | } 70 | 71 | // NewLogBackend creates a new LogBackend. 72 | func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend { 73 | b := &LogBackend{Logger: log.New(out, prefix, flag)} 74 | 75 | // Unfortunately, the API used only takes an io.Writer where the Windows API 76 | // need the actual fd to change colors. 77 | if f, ok := out.(file); ok { 78 | b.f = f 79 | } 80 | 81 | return b 82 | } 83 | 84 | func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error { 85 | if b.Color && b.f != nil { 86 | buf := &bytes.Buffer{} 87 | setConsoleTextAttribute(b.f, colors[level]) 88 | buf.Write([]byte(rec.Formatted(calldepth + 1))) 89 | err := b.Logger.Output(calldepth+2, buf.String()) 90 | setConsoleTextAttribute(b.f, fgWhite) 91 | return err 92 | } 93 | return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1)) 94 | } 95 | 96 | // setConsoleTextAttribute sets the attributes of characters written to the 97 | // console screen buffer by the WriteFile or WriteConsole function. 98 | // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx. 99 | func setConsoleTextAttribute(f file, attribute uint16) bool { 100 | ok, _, _ := setConsoleTextAttributeProc.Call(f.Fd(), uintptr(attribute), 0) 101 | return ok != 0 102 | } 103 | 104 | func doFmtVerbLevelColor(layout string, level Level, output io.Writer) { 105 | // TODO not supported on Windows since the io.Writer here is actually a 106 | // bytes.Buffer. 107 | } 108 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package logging implements a logging infrastructure for Go. It supports 6 | // different logging backends like syslog, file and memory. Multiple backends 7 | // can be utilized with different log levels per backend and logger. 8 | package logging 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "log" 14 | "os" 15 | "strings" 16 | "sync/atomic" 17 | "time" 18 | ) 19 | 20 | // Redactor is an interface for types that may contain sensitive information 21 | // (like passwords), which shouldn't be printed to the log. The idea was found 22 | // in relog as part of the vitness project. 23 | type Redactor interface { 24 | Redacted() interface{} 25 | } 26 | 27 | // Redact returns a string of * having the same length as s. 28 | func Redact(s string) string { 29 | return strings.Repeat("*", len(s)) 30 | } 31 | 32 | var ( 33 | // Sequence number is incremented and utilized for all log records created. 34 | sequenceNo uint64 35 | 36 | // timeNow is a customizable for testing purposes. 37 | timeNow = time.Now 38 | ) 39 | 40 | // Record represents a log record and contains the timestamp when the record 41 | // was created, an increasing id, filename and line and finally the actual 42 | // formatted log line. 43 | type Record struct { 44 | ID uint64 45 | Time time.Time 46 | Module string 47 | Level Level 48 | Args []interface{} 49 | 50 | // message is kept as a pointer to have shallow copies update this once 51 | // needed. 52 | message *string 53 | fmt *string 54 | formatter Formatter 55 | formatted string 56 | } 57 | 58 | // Formatted returns the formatted log record string. 59 | func (r *Record) Formatted(calldepth int) string { 60 | if r.formatted == "" { 61 | var buf bytes.Buffer 62 | r.formatter.Format(calldepth+1, r, &buf) 63 | r.formatted = buf.String() 64 | } 65 | return r.formatted 66 | } 67 | 68 | // Message returns the log record message. 69 | func (r *Record) Message() string { 70 | if r.message == nil { 71 | // Redact the arguments that implements the Redactor interface 72 | for i, arg := range r.Args { 73 | if redactor, ok := arg.(Redactor); ok == true { 74 | r.Args[i] = redactor.Redacted() 75 | } 76 | } 77 | var buf bytes.Buffer 78 | if r.fmt != nil { 79 | fmt.Fprintf(&buf, *r.fmt, r.Args...) 80 | } else { 81 | // use Fprintln to make sure we always get space between arguments 82 | fmt.Fprintln(&buf, r.Args...) 83 | buf.Truncate(buf.Len() - 1) // strip newline 84 | } 85 | msg := buf.String() 86 | r.message = &msg 87 | } 88 | return *r.message 89 | } 90 | 91 | // Logger is the actual logger which creates log records based on the functions 92 | // called and passes them to the underlying logging backend. 93 | type Logger struct { 94 | Module string 95 | backend LeveledBackend 96 | haveBackend bool 97 | 98 | // ExtraCallDepth can be used to add additional call depth when getting the 99 | // calling function. This is normally used when wrapping a logger. 100 | ExtraCalldepth int 101 | } 102 | 103 | // SetBackend overrides any previously defined backend for this logger. 104 | func (l *Logger) SetBackend(backend LeveledBackend) { 105 | l.backend = backend 106 | l.haveBackend = true 107 | } 108 | 109 | // TODO call NewLogger and remove MustGetLogger? 110 | 111 | // GetLogger creates and returns a Logger object based on the module name. 112 | func GetLogger(module string) (*Logger, error) { 113 | return &Logger{Module: module}, nil 114 | } 115 | 116 | // MustGetLogger is like GetLogger but panics if the logger can't be created. 117 | // It simplifies safe initialization of a global logger for eg. a package. 118 | func MustGetLogger(module string) *Logger { 119 | logger, err := GetLogger(module) 120 | if err != nil { 121 | panic("logger: " + module + ": " + err.Error()) 122 | } 123 | return logger 124 | } 125 | 126 | // Reset restores the internal state of the logging library. 127 | func Reset() { 128 | // TODO make a global Init() method to be less magic? or make it such that 129 | // if there's no backends at all configured, we could use some tricks to 130 | // automatically setup backends based if we have a TTY or not. 131 | sequenceNo = 0 132 | b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags)) 133 | b.SetLevel(DEBUG, "") 134 | SetFormatter(DefaultFormatter) 135 | timeNow = time.Now 136 | } 137 | 138 | // IsEnabledFor returns true if the logger is enabled for the given level. 139 | func (l *Logger) IsEnabledFor(level Level) bool { 140 | return defaultBackend.IsEnabledFor(level, l.Module) 141 | } 142 | 143 | func (l *Logger) log(lvl Level, format *string, args ...interface{}) { 144 | if !l.IsEnabledFor(lvl) { 145 | return 146 | } 147 | 148 | // Create the logging record and pass it in to the backend 149 | record := &Record{ 150 | ID: atomic.AddUint64(&sequenceNo, 1), 151 | Time: timeNow(), 152 | Module: l.Module, 153 | Level: lvl, 154 | fmt: format, 155 | Args: args, 156 | } 157 | 158 | // TODO use channels to fan out the records to all backends? 159 | // TODO in case of errors, do something (tricky) 160 | 161 | // calldepth=2 brings the stack up to the caller of the level 162 | // methods, Info(), Fatal(), etc. 163 | // ExtraCallDepth allows this to be extended further up the stack in case we 164 | // are wrapping these methods, eg. to expose them package level 165 | if l.haveBackend { 166 | l.backend.Log(lvl, 2+l.ExtraCalldepth, record) 167 | return 168 | } 169 | 170 | defaultBackend.Log(lvl, 2+l.ExtraCalldepth, record) 171 | } 172 | 173 | // Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1). 174 | func (l *Logger) Fatal(args ...interface{}) { 175 | l.log(CRITICAL, nil, args...) 176 | os.Exit(1) 177 | } 178 | 179 | // Fatalf is equivalent to l.Critical followed by a call to os.Exit(1). 180 | func (l *Logger) Fatalf(format string, args ...interface{}) { 181 | l.log(CRITICAL, &format, args...) 182 | os.Exit(1) 183 | } 184 | 185 | // Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic(). 186 | func (l *Logger) Panic(args ...interface{}) { 187 | l.log(CRITICAL, nil, args...) 188 | panic(fmt.Sprint(args...)) 189 | } 190 | 191 | // Panicf is equivalent to l.Critical followed by a call to panic(). 192 | func (l *Logger) Panicf(format string, args ...interface{}) { 193 | l.log(CRITICAL, &format, args...) 194 | panic(fmt.Sprintf(format, args...)) 195 | } 196 | 197 | // Critical logs a message using CRITICAL as log level. 198 | func (l *Logger) Critical(args ...interface{}) { 199 | l.log(CRITICAL, nil, args...) 200 | } 201 | 202 | // Criticalf logs a message using CRITICAL as log level. 203 | func (l *Logger) Criticalf(format string, args ...interface{}) { 204 | l.log(CRITICAL, &format, args...) 205 | } 206 | 207 | // Error logs a message using ERROR as log level. 208 | func (l *Logger) Error(args ...interface{}) { 209 | l.log(ERROR, nil, args...) 210 | } 211 | 212 | // Errorf logs a message using ERROR as log level. 213 | func (l *Logger) Errorf(format string, args ...interface{}) { 214 | l.log(ERROR, &format, args...) 215 | } 216 | 217 | // Warning logs a message using WARNING as log level. 218 | func (l *Logger) Warning(args ...interface{}) { 219 | l.log(WARNING, nil, args...) 220 | } 221 | 222 | // Warningf logs a message using WARNING as log level. 223 | func (l *Logger) Warningf(format string, args ...interface{}) { 224 | l.log(WARNING, &format, args...) 225 | } 226 | 227 | // Notice logs a message using NOTICE as log level. 228 | func (l *Logger) Notice(args ...interface{}) { 229 | l.log(NOTICE, nil, args...) 230 | } 231 | 232 | // Noticef logs a message using NOTICE as log level. 233 | func (l *Logger) Noticef(format string, args ...interface{}) { 234 | l.log(NOTICE, &format, args...) 235 | } 236 | 237 | // Info logs a message using INFO as log level. 238 | func (l *Logger) Info(args ...interface{}) { 239 | l.log(INFO, nil, args...) 240 | } 241 | 242 | // Infof logs a message using INFO as log level. 243 | func (l *Logger) Infof(format string, args ...interface{}) { 244 | l.log(INFO, &format, args...) 245 | } 246 | 247 | // Debug logs a message using DEBUG as log level. 248 | func (l *Logger) Debug(args ...interface{}) { 249 | l.log(DEBUG, nil, args...) 250 | } 251 | 252 | // Debugf logs a message using DEBUG as log level. 253 | func (l *Logger) Debugf(format string, args ...interface{}) { 254 | l.log(DEBUG, &format, args...) 255 | } 256 | 257 | func init() { 258 | Reset() 259 | } 260 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | type Password string 10 | 11 | func (p Password) Redacted() interface{} { 12 | return Redact(string(p)) 13 | } 14 | 15 | func TestSequenceNoOverflow(t *testing.T) { 16 | // Forcefully set the next sequence number to the maximum 17 | backend := InitForTesting(DEBUG) 18 | sequenceNo = ^uint64(0) 19 | 20 | log := MustGetLogger("test") 21 | log.Debug("test") 22 | 23 | if MemoryRecordN(backend, 0).ID != 0 { 24 | t.Errorf("Unexpected sequence no: %v", MemoryRecordN(backend, 0).ID) 25 | } 26 | } 27 | 28 | func TestRedact(t *testing.T) { 29 | backend := InitForTesting(DEBUG) 30 | password := Password("123456") 31 | log := MustGetLogger("test") 32 | log.Debug("foo", password) 33 | if "foo ******" != MemoryRecordN(backend, 0).Formatted(0) { 34 | t.Errorf("redacted line: %v", MemoryRecordN(backend, 0)) 35 | } 36 | } 37 | 38 | func TestRedactf(t *testing.T) { 39 | backend := InitForTesting(DEBUG) 40 | password := Password("123456") 41 | log := MustGetLogger("test") 42 | log.Debugf("foo %s", password) 43 | if "foo ******" != MemoryRecordN(backend, 0).Formatted(0) { 44 | t.Errorf("redacted line: %v", MemoryRecordN(backend, 0).Formatted(0)) 45 | } 46 | } 47 | 48 | func TestPrivateBackend(t *testing.T) { 49 | stdBackend := InitForTesting(DEBUG) 50 | log := MustGetLogger("test") 51 | privateBackend := NewMemoryBackend(10240) 52 | lvlBackend := AddModuleLevel(privateBackend) 53 | lvlBackend.SetLevel(DEBUG, "") 54 | log.SetBackend(lvlBackend) 55 | log.Debug("to private backend") 56 | if stdBackend.size > 0 { 57 | t.Errorf("something in stdBackend, size of backend: %d", stdBackend.size) 58 | } 59 | if "to private baсkend" == MemoryRecordN(privateBackend, 0).Formatted(0) { 60 | t.Error("logged to defaultBackend:", MemoryRecordN(privateBackend, 0)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package logging 8 | 9 | import ( 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | "unsafe" 14 | ) 15 | 16 | // TODO pick one of the memory backends and stick with it or share interface. 17 | 18 | // InitForTesting is a convenient method when using logging in a test. Once 19 | // called, the time will be frozen to January 1, 1970 UTC. 20 | func InitForTesting(level Level) *MemoryBackend { 21 | Reset() 22 | 23 | memoryBackend := NewMemoryBackend(10240) 24 | 25 | leveledBackend := AddModuleLevel(memoryBackend) 26 | leveledBackend.SetLevel(level, "") 27 | SetBackend(leveledBackend) 28 | 29 | timeNow = func() time.Time { 30 | return time.Unix(0, 0).UTC() 31 | } 32 | return memoryBackend 33 | } 34 | 35 | // Node is a record node pointing to an optional next node. 36 | type node struct { 37 | next *node 38 | Record *Record 39 | } 40 | 41 | // Next returns the next record node. If there's no node available, it will 42 | // return nil. 43 | func (n *node) Next() *node { 44 | return n.next 45 | } 46 | 47 | // MemoryBackend is a simple memory based logging backend that will not produce 48 | // any output but merly keep records, up to the given size, in memory. 49 | type MemoryBackend struct { 50 | size int32 51 | maxSize int32 52 | head, tail unsafe.Pointer 53 | } 54 | 55 | // NewMemoryBackend creates a simple in-memory logging backend. 56 | func NewMemoryBackend(size int) *MemoryBackend { 57 | return &MemoryBackend{maxSize: int32(size)} 58 | } 59 | 60 | // Log implements the Log method required by Backend. 61 | func (b *MemoryBackend) Log(level Level, calldepth int, rec *Record) error { 62 | var size int32 63 | 64 | n := &node{Record: rec} 65 | np := unsafe.Pointer(n) 66 | 67 | // Add the record to the tail. If there's no records available, tail and 68 | // head will both be nil. When we successfully set the tail and the previous 69 | // value was nil, it's safe to set the head to the current value too. 70 | for { 71 | tailp := b.tail 72 | swapped := atomic.CompareAndSwapPointer( 73 | &b.tail, 74 | tailp, 75 | np, 76 | ) 77 | if swapped == true { 78 | if tailp == nil { 79 | b.head = np 80 | } else { 81 | (*node)(tailp).next = n 82 | } 83 | size = atomic.AddInt32(&b.size, 1) 84 | break 85 | } 86 | } 87 | 88 | // Since one record was added, we might have overflowed the list. Remove 89 | // a record if that is the case. The size will fluctate a bit, but 90 | // eventual consistent. 91 | if b.maxSize > 0 && size > b.maxSize { 92 | for { 93 | headp := b.head 94 | head := (*node)(b.head) 95 | if head.next == nil { 96 | break 97 | } 98 | swapped := atomic.CompareAndSwapPointer( 99 | &b.head, 100 | headp, 101 | unsafe.Pointer(head.next), 102 | ) 103 | if swapped == true { 104 | atomic.AddInt32(&b.size, -1) 105 | break 106 | } 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | // Head returns the oldest record node kept in memory. It can be used to 113 | // iterate over records, one by one, up to the last record. 114 | // 115 | // Note: new records can get added while iterating. Hence the number of records 116 | // iterated over might be larger than the maximum size. 117 | func (b *MemoryBackend) Head() *node { 118 | return (*node)(b.head) 119 | } 120 | 121 | type event int 122 | 123 | const ( 124 | eventFlush event = iota 125 | eventStop 126 | ) 127 | 128 | // ChannelMemoryBackend is very similar to the MemoryBackend, except that it 129 | // internally utilizes a channel. 130 | type ChannelMemoryBackend struct { 131 | maxSize int 132 | size int 133 | incoming chan *Record 134 | events chan event 135 | mu sync.Mutex 136 | running bool 137 | flushWg sync.WaitGroup 138 | stopWg sync.WaitGroup 139 | head, tail *node 140 | } 141 | 142 | // NewChannelMemoryBackend creates a simple in-memory logging backend which 143 | // utilizes a go channel for communication. 144 | // 145 | // Start will automatically be called by this function. 146 | func NewChannelMemoryBackend(size int) *ChannelMemoryBackend { 147 | backend := &ChannelMemoryBackend{ 148 | maxSize: size, 149 | incoming: make(chan *Record, 1024), 150 | events: make(chan event), 151 | } 152 | backend.Start() 153 | return backend 154 | } 155 | 156 | // Start launches the internal goroutine which starts processing data from the 157 | // input channel. 158 | func (b *ChannelMemoryBackend) Start() { 159 | b.mu.Lock() 160 | defer b.mu.Unlock() 161 | 162 | // Launch the goroutine unless it's already running. 163 | if b.running != true { 164 | b.running = true 165 | b.stopWg.Add(1) 166 | go b.process() 167 | } 168 | } 169 | 170 | func (b *ChannelMemoryBackend) process() { 171 | defer b.stopWg.Done() 172 | for { 173 | select { 174 | case rec := <-b.incoming: 175 | b.insertRecord(rec) 176 | case e := <-b.events: 177 | switch e { 178 | case eventStop: 179 | return 180 | case eventFlush: 181 | for len(b.incoming) > 0 { 182 | b.insertRecord(<-b.incoming) 183 | } 184 | b.flushWg.Done() 185 | } 186 | } 187 | } 188 | } 189 | 190 | func (b *ChannelMemoryBackend) insertRecord(rec *Record) { 191 | prev := b.tail 192 | b.tail = &node{Record: rec} 193 | if prev == nil { 194 | b.head = b.tail 195 | } else { 196 | prev.next = b.tail 197 | } 198 | 199 | if b.maxSize > 0 && b.size >= b.maxSize { 200 | b.head = b.head.next 201 | } else { 202 | b.size++ 203 | } 204 | } 205 | 206 | // Flush waits until all records in the buffered channel have been processed. 207 | func (b *ChannelMemoryBackend) Flush() { 208 | b.flushWg.Add(1) 209 | b.events <- eventFlush 210 | b.flushWg.Wait() 211 | } 212 | 213 | // Stop signals the internal goroutine to exit and waits until it have. 214 | func (b *ChannelMemoryBackend) Stop() { 215 | b.mu.Lock() 216 | if b.running == true { 217 | b.running = false 218 | b.events <- eventStop 219 | } 220 | b.mu.Unlock() 221 | b.stopWg.Wait() 222 | } 223 | 224 | // Log implements the Log method required by Backend. 225 | func (b *ChannelMemoryBackend) Log(level Level, calldepth int, rec *Record) error { 226 | b.incoming <- rec 227 | return nil 228 | } 229 | 230 | // Head returns the oldest record node kept in memory. It can be used to 231 | // iterate over records, one by one, up to the last record. 232 | // 233 | // Note: new records can get added while iterating. Hence the number of records 234 | // iterated over might be larger than the maximum size. 235 | func (b *ChannelMemoryBackend) Head() *node { 236 | return b.head 237 | } 238 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "strconv" 9 | "testing" 10 | ) 11 | 12 | // TODO share more code between these tests 13 | func MemoryRecordN(b *MemoryBackend, n int) *Record { 14 | node := b.Head() 15 | for i := 0; i < n; i++ { 16 | if node == nil { 17 | break 18 | } 19 | node = node.Next() 20 | } 21 | if node == nil { 22 | return nil 23 | } 24 | return node.Record 25 | } 26 | 27 | func ChannelMemoryRecordN(b *ChannelMemoryBackend, n int) *Record { 28 | b.Flush() 29 | node := b.Head() 30 | for i := 0; i < n; i++ { 31 | if node == nil { 32 | break 33 | } 34 | node = node.Next() 35 | } 36 | if node == nil { 37 | return nil 38 | } 39 | return node.Record 40 | } 41 | 42 | func TestMemoryBackend(t *testing.T) { 43 | backend := NewMemoryBackend(8) 44 | SetBackend(backend) 45 | 46 | log := MustGetLogger("test") 47 | 48 | if nil != MemoryRecordN(backend, 0) || 0 != backend.size { 49 | t.Errorf("memory level: %d", backend.size) 50 | } 51 | 52 | // Run 13 times, the resulting vector should be [5..12] 53 | for i := 0; i < 13; i++ { 54 | log.Infof("%d", i) 55 | } 56 | 57 | if 8 != backend.size { 58 | t.Errorf("record length: %d", backend.size) 59 | } 60 | record := MemoryRecordN(backend, 0) 61 | if "5" != record.Formatted(0) { 62 | t.Errorf("unexpected start: %s", record.Formatted(0)) 63 | } 64 | for i := 0; i < 8; i++ { 65 | record = MemoryRecordN(backend, i) 66 | if strconv.Itoa(i+5) != record.Formatted(0) { 67 | t.Errorf("unexpected record: %v", record.Formatted(0)) 68 | } 69 | } 70 | record = MemoryRecordN(backend, 7) 71 | if "12" != record.Formatted(0) { 72 | t.Errorf("unexpected end: %s", record.Formatted(0)) 73 | } 74 | record = MemoryRecordN(backend, 8) 75 | if nil != record { 76 | t.Errorf("unexpected eof: %s", record.Formatted(0)) 77 | } 78 | } 79 | 80 | func TestChannelMemoryBackend(t *testing.T) { 81 | backend := NewChannelMemoryBackend(8) 82 | SetBackend(backend) 83 | 84 | log := MustGetLogger("test") 85 | 86 | if nil != ChannelMemoryRecordN(backend, 0) || 0 != backend.size { 87 | t.Errorf("memory level: %d", backend.size) 88 | } 89 | 90 | // Run 13 times, the resulting vector should be [5..12] 91 | for i := 0; i < 13; i++ { 92 | log.Infof("%d", i) 93 | } 94 | backend.Flush() 95 | 96 | if 8 != backend.size { 97 | t.Errorf("record length: %d", backend.size) 98 | } 99 | record := ChannelMemoryRecordN(backend, 0) 100 | if "5" != record.Formatted(0) { 101 | t.Errorf("unexpected start: %s", record.Formatted(0)) 102 | } 103 | for i := 0; i < 8; i++ { 104 | record = ChannelMemoryRecordN(backend, i) 105 | if strconv.Itoa(i+5) != record.Formatted(0) { 106 | t.Errorf("unexpected record: %v", record.Formatted(0)) 107 | } 108 | } 109 | record = ChannelMemoryRecordN(backend, 7) 110 | if "12" != record.Formatted(0) { 111 | t.Errorf("unexpected end: %s", record.Formatted(0)) 112 | } 113 | record = ChannelMemoryRecordN(backend, 8) 114 | if nil != record { 115 | t.Errorf("unexpected eof: %s", record.Formatted(0)) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /multi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | // TODO remove Level stuff from the multi logger. Do one thing. 8 | 9 | // multiLogger is a log multiplexer which can be used to utilize multiple log 10 | // backends at once. 11 | type multiLogger struct { 12 | backends []LeveledBackend 13 | } 14 | 15 | // MultiLogger creates a logger which contain multiple loggers. 16 | func MultiLogger(backends ...Backend) LeveledBackend { 17 | var leveledBackends []LeveledBackend 18 | for _, backend := range backends { 19 | leveledBackends = append(leveledBackends, AddModuleLevel(backend)) 20 | } 21 | return &multiLogger{leveledBackends} 22 | } 23 | 24 | // Log passes the log record to all backends. 25 | func (b *multiLogger) Log(level Level, calldepth int, rec *Record) (err error) { 26 | for _, backend := range b.backends { 27 | if backend.IsEnabledFor(level, rec.Module) { 28 | // Shallow copy of the record for the formatted cache on Record and get the 29 | // record formatter from the backend. 30 | r2 := *rec 31 | if e := backend.Log(level, calldepth+1, &r2); e != nil { 32 | err = e 33 | } 34 | } 35 | } 36 | return 37 | } 38 | 39 | // GetLevel returns the highest level enabled by all backends. 40 | func (b *multiLogger) GetLevel(module string) Level { 41 | var level Level 42 | for _, backend := range b.backends { 43 | if backendLevel := backend.GetLevel(module); backendLevel > level { 44 | level = backendLevel 45 | } 46 | } 47 | return level 48 | } 49 | 50 | // SetLevel propagates the same level to all backends. 51 | func (b *multiLogger) SetLevel(level Level, module string) { 52 | for _, backend := range b.backends { 53 | backend.SetLevel(level, module) 54 | } 55 | } 56 | 57 | // IsEnabledFor returns true if any of the backends are enabled for it. 58 | func (b *multiLogger) IsEnabledFor(level Level, module string) bool { 59 | for _, backend := range b.backends { 60 | if backend.IsEnabledFor(level, module) { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | -------------------------------------------------------------------------------- /multi_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | func TestMultiLogger(t *testing.T) { 10 | log1 := NewMemoryBackend(8) 11 | log2 := NewMemoryBackend(8) 12 | SetBackend(MultiLogger(log1, log2)) 13 | 14 | log := MustGetLogger("test") 15 | log.Debug("log") 16 | 17 | if "log" != MemoryRecordN(log1, 0).Formatted(0) { 18 | t.Errorf("log1: %v", MemoryRecordN(log1, 0).Formatted(0)) 19 | } 20 | if "log" != MemoryRecordN(log2, 0).Formatted(0) { 21 | t.Errorf("log2: %v", MemoryRecordN(log2, 0).Formatted(0)) 22 | } 23 | } 24 | 25 | func TestMultiLoggerLevel(t *testing.T) { 26 | log1 := NewMemoryBackend(8) 27 | log2 := NewMemoryBackend(8) 28 | 29 | leveled1 := AddModuleLevel(log1) 30 | leveled2 := AddModuleLevel(log2) 31 | 32 | multi := MultiLogger(leveled1, leveled2) 33 | multi.SetLevel(ERROR, "test") 34 | SetBackend(multi) 35 | 36 | log := MustGetLogger("test") 37 | log.Notice("log") 38 | 39 | if nil != MemoryRecordN(log1, 0) || nil != MemoryRecordN(log2, 0) { 40 | t.Errorf("unexpected log record") 41 | } 42 | 43 | leveled1.SetLevel(DEBUG, "test") 44 | log.Notice("log") 45 | if "log" != MemoryRecordN(log1, 0).Formatted(0) { 46 | t.Errorf("log1 not received") 47 | } 48 | if nil != MemoryRecordN(log2, 0) { 49 | t.Errorf("log2 received") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /syslog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build !windows,!plan9 6 | 7 | package logging 8 | 9 | import "log/syslog" 10 | 11 | // SyslogBackend is a simple logger to syslog backend. It automatically maps 12 | // the internal log levels to appropriate syslog log levels. 13 | type SyslogBackend struct { 14 | Writer *syslog.Writer 15 | } 16 | 17 | // NewSyslogBackend connects to the syslog daemon using UNIX sockets with the 18 | // given prefix. If prefix is not given, the prefix will be derived from the 19 | // launched command. 20 | func NewSyslogBackend(prefix string) (b *SyslogBackend, err error) { 21 | var w *syslog.Writer 22 | w, err = syslog.New(syslog.LOG_CRIT, prefix) 23 | return &SyslogBackend{w}, err 24 | } 25 | 26 | // NewSyslogBackendPriority is the same as NewSyslogBackend, but with custom 27 | // syslog priority, like syslog.LOG_LOCAL3|syslog.LOG_DEBUG etc. 28 | func NewSyslogBackendPriority(prefix string, priority syslog.Priority) (b *SyslogBackend, err error) { 29 | var w *syslog.Writer 30 | w, err = syslog.New(priority, prefix) 31 | return &SyslogBackend{w}, err 32 | } 33 | 34 | // Log implements the Backend interface. 35 | func (b *SyslogBackend) Log(level Level, calldepth int, rec *Record) error { 36 | line := rec.Formatted(calldepth + 1) 37 | switch level { 38 | case CRITICAL: 39 | return b.Writer.Crit(line) 40 | case ERROR: 41 | return b.Writer.Err(line) 42 | case WARNING: 43 | return b.Writer.Warning(line) 44 | case NOTICE: 45 | return b.Writer.Notice(line) 46 | case INFO: 47 | return b.Writer.Info(line) 48 | case DEBUG: 49 | return b.Writer.Debug(line) 50 | default: 51 | } 52 | panic("unhandled log level") 53 | } 54 | -------------------------------------------------------------------------------- /syslog_fallback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //+build windows plan9 6 | 7 | package logging 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | type Priority int 14 | 15 | type SyslogBackend struct { 16 | } 17 | 18 | func NewSyslogBackend(prefix string) (b *SyslogBackend, err error) { 19 | return nil, fmt.Errorf("Platform does not support syslog") 20 | } 21 | 22 | func NewSyslogBackendPriority(prefix string, priority Priority) (b *SyslogBackend, err error) { 23 | return nil, fmt.Errorf("Platform does not support syslog") 24 | } 25 | 26 | func (b *SyslogBackend) Log(level Level, calldepth int, rec *Record) error { 27 | return fmt.Errorf("Platform does not support syslog") 28 | } 29 | --------------------------------------------------------------------------------