├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── context.go ├── context_test.go ├── custom.go ├── example └── example.go ├── logging.go ├── logging_example_test.go ├── logging_unix.go ├── sink.go ├── sink_test.go └── syslog.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3 4 | - 1.4.2 5 | - tip 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Koding, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logging 2 | ======= 3 | 4 | Simple logging package in Go. 5 | 6 | [![GoDoc](https://godoc.org/github.com/koding/logging?status.svg)](https://godoc.org/github.com/koding/logging) 7 | [![Build Status](https://travis-ci.org/koding/logging.svg)](https://travis-ci.org/koding/logging) 8 | 9 | 10 | Install 11 | ------- 12 | 13 | ```sh 14 | $ go get github.com/koding/logging 15 | ``` 16 | 17 | 18 | Features 19 | -------- 20 | 21 | * Log levels (DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL) 22 | * Different colored output for different levels (can be disabled) 23 | * No global state in package 24 | * Customizable logging handlers 25 | * Customizable formatters 26 | * Log to multiple backends concurrently 27 | * Context based (inherited) loggers 28 | 29 | 30 | Example Usage 31 | ------------- 32 | 33 | See [https://github.com/koding/logging/blob/master/example/example.go](https://github.com/koding/logging/blob/master/example/example.go) 34 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "fmt" 4 | 5 | type context struct { 6 | prefix string 7 | logger 8 | } 9 | 10 | // Fatal is equivalent to Critical() followed by a call to os.Exit(1). 11 | func (c *context) Fatal(format string, args ...interface{}) { 12 | c.logger.Fatal(c.prefixFormat()+format, args...) 13 | } 14 | 15 | // Panic is equivalent to Critical() followed by a call to panic(). 16 | func (c *context) Panic(format string, args ...interface{}) { 17 | c.logger.Panic(c.prefixFormat()+format, args...) 18 | } 19 | 20 | // Critical sends a critical level log message to the handler. Arguments are 21 | // handled in the manner of fmt.Printf. 22 | func (c *context) Critical(format string, args ...interface{}) { 23 | c.logger.Critical(c.prefixFormat()+format, args...) 24 | } 25 | 26 | // Error sends a error level log message to the handler. Arguments are handled 27 | // in the manner of fmt.Printf. 28 | func (c *context) Error(format string, args ...interface{}) { 29 | c.logger.Error(c.prefixFormat()+format, args...) 30 | } 31 | 32 | // Warning sends a warning level log message to the handler. Arguments are 33 | // handled in the manner of fmt.Printf. 34 | func (c *context) Warning(format string, args ...interface{}) { 35 | c.logger.Warning(c.prefixFormat()+format, args...) 36 | } 37 | 38 | // Notice sends a notice level log message to the handler. Arguments are 39 | // handled in the manner of fmt.Printf. 40 | func (c *context) Notice(format string, args ...interface{}) { 41 | c.logger.Notice(c.prefixFormat()+format, args...) 42 | } 43 | 44 | // Info sends a info level log message to the handler. Arguments are handled in 45 | // the manner of fmt.Printf. 46 | func (c *context) Info(format string, args ...interface{}) { 47 | c.logger.Info(c.prefixFormat()+format, args...) 48 | } 49 | 50 | // Debug sends a debug level log message to the handler. Arguments are handled 51 | // in the manner of fmt.Printf. 52 | func (c *context) Debug(format string, args ...interface{}) { 53 | c.logger.Debug(c.prefixFormat()+format, args...) 54 | } 55 | 56 | // New creates a new Logger from current context 57 | func (c *context) New(prefixes ...interface{}) Logger { 58 | return newContext(c.logger, c.prefix, prefixes...) 59 | } 60 | 61 | func (c *context) prefixFormat() string { 62 | return c.prefix + " " 63 | } 64 | 65 | func newContext(logger logger, initial string, prefixes ...interface{}) *context { 66 | resultPrefix := "" // resultPrefix holds prefix after initialization 67 | connector := "" // connector holds the connector string 68 | 69 | for _, prefix := range prefixes { 70 | resultPrefix += fmt.Sprintf("%s%+v", connector, prefix) 71 | switch connector { 72 | case "=": // if previous is `=` replace with ][ 73 | connector = "][" 74 | case "][": // if previous is `][` replace with = 75 | connector = "=" 76 | default: 77 | connector = "=" // if its first iteration, assing = 78 | } 79 | } 80 | 81 | return &context{ 82 | prefix: initial + "[" + resultPrefix + "]", 83 | logger: logger, 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "testing" 4 | 5 | func TestContext(t *testing.T) { 6 | var tds = []struct { 7 | expected string 8 | context []interface{} 9 | }{ 10 | { 11 | expected: "[prefix]", 12 | context: []interface{}{"prefix"}, 13 | }, 14 | { 15 | expected: "[isEnabled=true][count=1233123123123][name=koding][standalone]", 16 | context: []interface{}{ 17 | "isEnabled", true, 18 | "count", 1233123123123, 19 | "name", "koding", 20 | "standalone", 21 | }, 22 | }, 23 | { 24 | expected: "[isEnabled=true][count=1233123123123][name=koding][ratio=3.42323]", 25 | context: []interface{}{ 26 | "isEnabled", true, 27 | "count", 1233123123123, 28 | "name", "koding", 29 | "ratio", 3.42323, 30 | }, 31 | }, 32 | } 33 | 34 | l := NewLogger("test") 35 | 36 | for _, td := range tds { 37 | contextedLogger := l.New(td.context...) 38 | if contextedLogger.(*context).prefix != td.expected { 39 | t.Fatalf("Prefix expected as %s, got: %s", td.expected, contextedLogger.(*context).prefix) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /custom.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type CustomFormatter struct{} 10 | 11 | func (f *CustomFormatter) Format(rec *Record) string { 12 | paths := strings.Split(rec.Filename, string(os.PathSeparator)) 13 | // does even anyone uses root folder as their gopath? 14 | filePath := strings.Join(paths[len(paths)-2:], string(os.PathSeparator)) 15 | 16 | return fmt.Sprintf("%-24s %-8s [%-15s][PID:%d][%s:%d] %s", 17 | rec.Time.UTC().Format("2006-01-02T15:04:05.999Z"), 18 | LevelNames[rec.Level], 19 | rec.LoggerName, 20 | rec.ProcessID, 21 | filePath, 22 | rec.Line, 23 | fmt.Sprintf(rec.Format, rec.Args...), 24 | ) 25 | } 26 | 27 | func NewCustom(name string, debug bool) Logger { 28 | log := NewLogger(name) 29 | logHandler := NewWriterHandler(os.Stderr) 30 | logHandler.Formatter = &CustomFormatter{} 31 | logHandler.Colorize = true 32 | log.SetHandler(logHandler) 33 | 34 | if debug { 35 | log.SetLevel(DEBUG) 36 | logHandler.SetLevel(DEBUG) 37 | } 38 | 39 | return log 40 | } 41 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/koding/logging" 7 | ) 8 | 9 | func main() { 10 | 11 | // Default logger 12 | logging.Debug("Debug") 13 | logging.Info("Info") 14 | logging.Notice("Notice") 15 | logging.Warning("Warning") 16 | logging.Error("Error") 17 | logging.Critical("Critical") 18 | 19 | // Custom logger with default handler 20 | l := logging.NewLogger("test") 21 | 22 | l.Debug("Debug") 23 | l.Info("Info") 24 | l.Notice("Notice") 25 | l.Warning("Warning") 26 | l.Error("Error") 27 | l.Critical("Critical") 28 | 29 | // Custom logger with custom handler 30 | l2 := logging.NewLogger("test2") 31 | l2.SetHandler(&MyHandler{}) 32 | 33 | l2.Debug("Debug") 34 | l2.Info("Info") 35 | l2.Notice("Notice") 36 | l2.Warning("Warning") 37 | l2.Error("Error") 38 | l2.Critical("Critical") 39 | } 40 | 41 | type MyHandler struct { 42 | logging.BaseHandler 43 | } 44 | 45 | func (h *MyHandler) Handle(rec *logging.Record) { 46 | fmt.Printf(rec.Format, rec.Args...) 47 | } 48 | 49 | func (h *MyHandler) Close() { 50 | } 51 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | // Package logging is an alternative to log package in standard library. 2 | package logging 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type ( 16 | // Color represents log level colors 17 | Color int 18 | 19 | // Level represent severity of logs 20 | Level int 21 | ) 22 | 23 | // Colors for different log levels. 24 | const ( 25 | BLACK Color = (iota + 30) 26 | RED 27 | GREEN 28 | YELLOW 29 | BLUE 30 | MAGENTA 31 | CYAN 32 | WHITE 33 | ) 34 | 35 | // Logging levels. 36 | const ( 37 | CRITICAL Level = iota 38 | ERROR 39 | WARNING 40 | NOTICE 41 | INFO 42 | DEBUG 43 | ) 44 | 45 | // LevelNames provides mapping for log levels 46 | var LevelNames = map[Level]string{ 47 | CRITICAL: "CRITICAL", 48 | ERROR: "ERROR", 49 | WARNING: "WARNING", 50 | NOTICE: "NOTICE", 51 | INFO: "INFO", 52 | DEBUG: "DEBUG", 53 | } 54 | 55 | // LevelColors provides mapping for log colors 56 | var LevelColors = map[Level]Color{ 57 | CRITICAL: MAGENTA, 58 | ERROR: RED, 59 | WARNING: YELLOW, 60 | NOTICE: GREEN, 61 | INFO: WHITE, 62 | DEBUG: CYAN, 63 | } 64 | 65 | var ( 66 | // DefaultLogger holds default logger 67 | DefaultLogger Logger = NewLogger(procName()) 68 | 69 | // DefaultLevel holds default value for loggers 70 | DefaultLevel Level = INFO 71 | 72 | // DefaultHandler holds default handler for loggers 73 | DefaultHandler Handler = StderrHandler 74 | 75 | // DefaultFormatter holds default formatter for loggers 76 | DefaultFormatter Formatter = &defaultFormatter{} 77 | 78 | // StdoutHandler holds a handler with outputting to stdout 79 | StdoutHandler = NewWriterHandler(os.Stdout) 80 | 81 | // StderrHandler holds a handler with outputting to stderr 82 | StderrHandler = NewWriterHandler(os.Stderr) 83 | ) 84 | 85 | // Logger is the interface for outputing log messages in different levels. 86 | // A new Logger can be created with NewLogger() function. 87 | // You can changed the output handler with SetHandler() function. 88 | type Logger interface { 89 | // SetLevel changes the level of the logger. Default is logging.Info. 90 | SetLevel(Level) 91 | 92 | // SetHandler replaces the current handler for output. Default is logging.StderrHandler. 93 | SetHandler(Handler) 94 | 95 | // SetCallDepth sets the parameter passed to runtime.Caller(). 96 | // It is used to get the file name from call stack. 97 | // For example you need to set it to 1 if you are using a wrapper around 98 | // the Logger. Default value is zero. 99 | SetCallDepth(int) 100 | 101 | // New creates a new inerhited context logger with given prefixes. 102 | New(prefixes ...interface{}) Logger 103 | 104 | // Fatal is equivalent to l.Critical followed by a call to os.Exit(1). 105 | Fatal(format string, args ...interface{}) 106 | 107 | // Panic is equivalent to l.Critical followed by a call to panic(). 108 | Panic(format string, args ...interface{}) 109 | 110 | // Critical logs a message using CRITICAL as log level. 111 | Critical(format string, args ...interface{}) 112 | 113 | // Error logs a message using ERROR as log level. 114 | Error(format string, args ...interface{}) 115 | 116 | // Warning logs a message using WARNING as log level. 117 | Warning(format string, args ...interface{}) 118 | 119 | // Notice logs a message using NOTICE as log level. 120 | Notice(format string, args ...interface{}) 121 | 122 | // Info logs a message using INFO as log level. 123 | Info(format string, args ...interface{}) 124 | 125 | // Debug logs a message using DEBUG as log level. 126 | Debug(format string, args ...interface{}) 127 | } 128 | 129 | // Handler handles the output. 130 | type Handler interface { 131 | SetFormatter(Formatter) 132 | SetLevel(Level) 133 | 134 | // Handle single log record. 135 | Handle(*Record) 136 | 137 | // Close the handler. 138 | Close() 139 | } 140 | 141 | // Record contains all of the information about a single log message. 142 | type Record struct { 143 | Format string // Format string 144 | Args []interface{} // Arguments to format string 145 | LoggerName string // Name of the logger module 146 | Level Level // Level of the record 147 | Time time.Time // Time of the record (local time) 148 | Filename string // File name of the log call (absolute path) 149 | Line int // Line number in file 150 | ProcessID int // PID 151 | ProcessName string // Name of the process 152 | } 153 | 154 | // Formatter formats a record. 155 | type Formatter interface { 156 | // Format the record and return a message. 157 | Format(*Record) (message string) 158 | } 159 | 160 | /////////////////////// 161 | // // 162 | // Default Formatter // 163 | // // 164 | /////////////////////// 165 | 166 | type defaultFormatter struct{} 167 | 168 | // Format outputs a message like "2014-02-28 18:15:57 [example] INFO something happened" 169 | func (f *defaultFormatter) Format(rec *Record) string { 170 | return fmt.Sprintf("%s [%s] %-8s %s", fmt.Sprint(rec.Time)[:19], rec.LoggerName, LevelNames[rec.Level], fmt.Sprintf(rec.Format, rec.Args...)) 171 | } 172 | 173 | /////////////////////////// 174 | // // 175 | // Logger implementation // 176 | // // 177 | /////////////////////////// 178 | 179 | // logger is the default Logger implementation. 180 | type logger struct { 181 | Name string 182 | Level Level 183 | Handler Handler 184 | calldepth int 185 | } 186 | 187 | // NewLogger returns a new Logger implementation. Do not forget to close it at exit. 188 | func NewLogger(name string) Logger { 189 | return &logger{ 190 | Name: name, 191 | Level: DefaultLevel, 192 | Handler: DefaultHandler, 193 | } 194 | } 195 | 196 | // New creates a new inerhited logger with the given prefixes 197 | func (l *logger) New(prefixes ...interface{}) Logger { 198 | return newContext(*l, "", prefixes...) 199 | } 200 | 201 | func (l *logger) SetLevel(level Level) { 202 | l.Level = level 203 | } 204 | 205 | func (l *logger) SetHandler(b Handler) { 206 | l.Handler = b 207 | } 208 | 209 | func (l *logger) SetCallDepth(n int) { 210 | l.calldepth = n 211 | } 212 | 213 | // Fatal is equivalent to Critical() followed by a call to os.Exit(1). 214 | func (l *logger) Fatal(format string, args ...interface{}) { 215 | l.Critical(format, args...) 216 | l.Handler.Close() 217 | os.Exit(1) 218 | } 219 | 220 | // Panic is equivalent to Critical() followed by a call to panic(). 221 | func (l *logger) Panic(format string, args ...interface{}) { 222 | l.Critical(format, args...) 223 | panic(fmt.Sprintf(format, args...)) 224 | } 225 | 226 | // Critical sends a critical level log message to the handler. Arguments are handled in the manner of fmt.Printf. 227 | func (l *logger) Critical(format string, args ...interface{}) { 228 | if l.Level >= CRITICAL { 229 | l.log(CRITICAL, format, args...) 230 | } 231 | } 232 | 233 | // Error sends a error level log message to the handler. Arguments are handled in the manner of fmt.Printf. 234 | func (l *logger) Error(format string, args ...interface{}) { 235 | if l.Level >= ERROR { 236 | l.log(ERROR, format, args...) 237 | } 238 | } 239 | 240 | // Warning sends a warning level log message to the handler. Arguments are handled in the manner of fmt.Printf. 241 | func (l *logger) Warning(format string, args ...interface{}) { 242 | if l.Level >= WARNING { 243 | l.log(WARNING, format, args...) 244 | } 245 | } 246 | 247 | // Notice sends a notice level log message to the handler. Arguments are handled in the manner of fmt.Printf. 248 | func (l *logger) Notice(format string, args ...interface{}) { 249 | if l.Level >= NOTICE { 250 | l.log(NOTICE, format, args...) 251 | } 252 | } 253 | 254 | // Info sends a info level log message to the handler. Arguments are handled in the manner of fmt.Printf. 255 | func (l *logger) Info(format string, args ...interface{}) { 256 | if l.Level >= INFO { 257 | l.log(INFO, format, args...) 258 | } 259 | } 260 | 261 | // Debug sends a debug level log message to the handler. Arguments are handled in the manner of fmt.Printf. 262 | func (l *logger) Debug(format string, args ...interface{}) { 263 | if l.Level >= DEBUG { 264 | l.log(DEBUG, format, args...) 265 | } 266 | } 267 | 268 | func (l *logger) log(level Level, format string, args ...interface{}) { 269 | // Add missing newline at the end. 270 | if !strings.HasSuffix(format, "\n") { 271 | format += "\n" 272 | } 273 | 274 | _, file, line, ok := runtime.Caller(l.calldepth + 2) 275 | if !ok { 276 | file = "???" 277 | line = 0 278 | } 279 | 280 | rec := &Record{ 281 | Format: format, 282 | Args: args, 283 | LoggerName: l.Name, 284 | Level: level, 285 | Time: time.Now(), 286 | Filename: file, 287 | Line: line, 288 | ProcessName: procName(), 289 | ProcessID: os.Getpid(), 290 | } 291 | 292 | l.Handler.Handle(rec) 293 | } 294 | 295 | // procName returns the name of the current process. 296 | func procName() string { return filepath.Base(os.Args[0]) } 297 | 298 | /////////////////// 299 | // // 300 | // DefaultLogger // 301 | // // 302 | /////////////////// 303 | 304 | // Fatal is equivalent to Critical() followed by a call to os.Exit(1). 305 | func Fatal(format string, args ...interface{}) { 306 | DefaultLogger.Fatal(format, args...) 307 | } 308 | 309 | // Panic is equivalent to Critical() followed by a call to panic(). 310 | func Panic(format string, args ...interface{}) { 311 | DefaultLogger.Panic(format, args...) 312 | } 313 | 314 | // Critical prints a critical level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 315 | func Critical(format string, args ...interface{}) { 316 | DefaultLogger.Critical(format, args...) 317 | } 318 | 319 | // Error prints a error level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 320 | func Error(format string, args ...interface{}) { 321 | DefaultLogger.Error(format, args...) 322 | } 323 | 324 | // Warning prints a warning level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 325 | func Warning(format string, args ...interface{}) { 326 | DefaultLogger.Warning(format, args...) 327 | } 328 | 329 | // Notice prints a notice level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 330 | func Notice(format string, args ...interface{}) { 331 | DefaultLogger.Notice(format, args...) 332 | } 333 | 334 | // Info prints a info level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 335 | func Info(format string, args ...interface{}) { 336 | DefaultLogger.Info(format, args...) 337 | } 338 | 339 | // Debug prints a debug level log message to the stderr. Arguments are handled in the manner of fmt.Printf. 340 | func Debug(format string, args ...interface{}) { 341 | DefaultLogger.Debug(format, args...) 342 | } 343 | 344 | ///////////////// 345 | // // 346 | // BaseHandler // 347 | // // 348 | ///////////////// 349 | 350 | // BaseHandler provides basic functionality for handler 351 | type BaseHandler struct { 352 | Level Level 353 | Formatter Formatter 354 | } 355 | 356 | // NewBaseHandler creates a newBaseHandler with default values 357 | func NewBaseHandler() *BaseHandler { 358 | return &BaseHandler{ 359 | Level: DefaultLevel, 360 | Formatter: DefaultFormatter, 361 | } 362 | } 363 | 364 | // SetLevel sets logging level for handler 365 | func (h *BaseHandler) SetLevel(l Level) { 366 | h.Level = l 367 | } 368 | 369 | // SetFormatter sets logging formatter for handler 370 | func (h *BaseHandler) SetFormatter(f Formatter) { 371 | h.Formatter = f 372 | } 373 | 374 | // FilterAndFormat filters any record according to loggging level 375 | func (h *BaseHandler) FilterAndFormat(rec *Record) string { 376 | if h.Level >= rec.Level { 377 | return h.Formatter.Format(rec) 378 | } 379 | return "" 380 | } 381 | 382 | /////////////////// 383 | // // 384 | // WriterHandler // 385 | // // 386 | /////////////////// 387 | 388 | // WriterHandler is a handler implementation that writes the logging output to a io.Writer. 389 | type WriterHandler struct { 390 | *BaseHandler 391 | w io.Writer 392 | Colorize bool 393 | } 394 | 395 | // NewWriterHandler creates a new writer handler with given io.Writer 396 | func NewWriterHandler(w io.Writer) *WriterHandler { 397 | return &WriterHandler{ 398 | BaseHandler: NewBaseHandler(), 399 | w: w, 400 | } 401 | } 402 | 403 | // Handle writes any given Record to the Writer. 404 | func (b *WriterHandler) Handle(rec *Record) { 405 | message := b.BaseHandler.FilterAndFormat(rec) 406 | if message == "" { 407 | return 408 | } 409 | if b.Colorize { 410 | b.w.Write([]byte(fmt.Sprintf("\033[%dm", LevelColors[rec.Level]))) 411 | } 412 | fmt.Fprint(b.w, message) 413 | if b.Colorize { 414 | b.w.Write([]byte("\033[0m")) // reset color 415 | } 416 | } 417 | 418 | // Close closes WriterHandler 419 | func (b *WriterHandler) Close() {} 420 | 421 | ////////////////// 422 | // // 423 | // MultiHandler // 424 | // // 425 | ////////////////// 426 | 427 | // MultiHandler sends the log output to multiple handlers concurrently. 428 | type MultiHandler struct { 429 | handlers []Handler 430 | } 431 | 432 | // NewMultiHandler creates a new handler with given handlers 433 | func NewMultiHandler(handlers ...Handler) *MultiHandler { 434 | return &MultiHandler{handlers: handlers} 435 | } 436 | 437 | // SetFormatter sets formatter for all handlers 438 | func (b *MultiHandler) SetFormatter(f Formatter) { 439 | for _, h := range b.handlers { 440 | h.SetFormatter(f) 441 | } 442 | } 443 | 444 | // SetLevel sets level for all handlers 445 | func (b *MultiHandler) SetLevel(l Level) { 446 | for _, h := range b.handlers { 447 | h.SetLevel(l) 448 | } 449 | } 450 | 451 | // Handle handles given record with all handlers concurrently 452 | func (b *MultiHandler) Handle(rec *Record) { 453 | wg := sync.WaitGroup{} 454 | wg.Add(len(b.handlers)) 455 | for _, handler := range b.handlers { 456 | go func(handler Handler) { 457 | handler.Handle(rec) 458 | wg.Done() 459 | }(handler) 460 | } 461 | wg.Wait() 462 | } 463 | 464 | // Close closes all handlers concurrently 465 | func (b *MultiHandler) Close() { 466 | wg := sync.WaitGroup{} 467 | wg.Add(len(b.handlers)) 468 | for _, handler := range b.handlers { 469 | go func(handler Handler) { 470 | handler.Close() 471 | wg.Done() 472 | }(handler) 473 | } 474 | wg.Wait() 475 | } 476 | -------------------------------------------------------------------------------- /logging_example_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type exampleFormatter struct{} 9 | 10 | func (f *exampleFormatter) Format(rec *Record) string { 11 | return fmt.Sprintf("%-8s [%-4s] %s", 12 | LevelNames[rec.Level], 13 | rec.LoggerName, 14 | fmt.Sprintf(rec.Format, rec.Args...), 15 | ) 16 | } 17 | 18 | func ExampleContexted() { 19 | // Custom logger with new std out handler 20 | l := NewLogger("test") 21 | l.SetLevel(DEBUG) 22 | 23 | // create handler 24 | logHandler := NewWriterHandler(os.Stdout) 25 | logHandler.SetLevel(DEBUG) 26 | logHandler.Formatter = &exampleFormatter{} 27 | // set handler 28 | l.SetHandler(logHandler) 29 | 30 | // Custom logger with inherited handler 31 | ctx1 := l.New("example") 32 | ctx1.Debug("Debug") 33 | 34 | // derive new one from previous contexted 35 | ctx2 := ctx1.New("debug", true, "level", 2, "example") 36 | ctx2.Debug("Debug") 37 | 38 | // create new context inline 39 | ctx1.New("standalone").Debug("Debug") 40 | 41 | // Output: 42 | //DEBUG [test] [example] Debug 43 | //DEBUG [test] [example][debug=true][level=2][example] Debug 44 | //DEBUG [test] [example][standalone] Debug 45 | } 46 | -------------------------------------------------------------------------------- /logging_unix.go: -------------------------------------------------------------------------------- 1 | // +build darwin freebsd linux netbsd openbsd 2 | 3 | package logging 4 | 5 | func init() { 6 | StdoutHandler.Colorize = true 7 | StderrHandler.Colorize = true 8 | } 9 | -------------------------------------------------------------------------------- /sink.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | ) 8 | 9 | ///////////////// 10 | // // 11 | // SinkHandler // 12 | // // 13 | ///////////////// 14 | 15 | // SinkHandler sends log records to buffered channel, the logs are written in a dedicated routine consuming the channel. 16 | type SinkHandler struct { 17 | inner Handler 18 | sinkCh chan *Record 19 | bufSize int 20 | wg sync.WaitGroup 21 | } 22 | 23 | // NewSinkHandler creates SinkHandler with sink channel buffer size bufSize that wraps inner handler for writing logs. 24 | // When SinkHandler is created a go routine is started. When not used always call Close to terminate go routine. 25 | func NewSinkHandler(inner Handler, bufSize int) *SinkHandler { 26 | b := &SinkHandler{ 27 | inner: inner, 28 | sinkCh: make(chan *Record, bufSize), 29 | bufSize: bufSize, 30 | } 31 | 32 | b.wg.Add(1) 33 | go b.process() 34 | 35 | return b 36 | } 37 | 38 | // process reads log records from sinkCh and calls inner log handler to write it. 39 | func (b *SinkHandler) process() { 40 | for { 41 | rec, ok := <-b.sinkCh 42 | if !ok { 43 | b.inner.Close() 44 | break 45 | } 46 | 47 | b.inner.Handle(rec) 48 | } 49 | b.wg.Done() 50 | } 51 | 52 | // Status reports sink capacity and length. 53 | func (b *SinkHandler) Status() (int, int) { 54 | return b.bufSize, len(b.sinkCh) 55 | } 56 | 57 | // SetLevel sets logging level for handler 58 | func (b *SinkHandler) SetLevel(l Level) { 59 | b.inner.SetLevel(l) 60 | } 61 | 62 | // SetFormatter sets logging formatter for handler 63 | func (b *SinkHandler) SetFormatter(f Formatter) { 64 | b.inner.SetFormatter(f) 65 | } 66 | 67 | // Handle puts rec to the sink. 68 | func (b *SinkHandler) Handle(rec *Record) { 69 | select { 70 | case b.sinkCh <- rec: 71 | default: 72 | fmt.Fprintf(os.Stderr, "SinkHandler buffer too small dropping record\n") 73 | } 74 | } 75 | 76 | // Close closes the sink channel, inner handler will be closed when all pending logs are processed. 77 | // Close blocks until all the logs are processed. 78 | func (b *SinkHandler) Close() { 79 | close(b.sinkCh) 80 | b.wg.Wait() 81 | } 82 | -------------------------------------------------------------------------------- /sink_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | "strings" 10 | "sync" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | type LogRecorder struct { 16 | Level Level 17 | Formatter Formatter 18 | Records map[string][]*Record 19 | Closed bool 20 | } 21 | 22 | func NewLogRecorder() *LogRecorder { 23 | return &LogRecorder{ 24 | Records: make(map[string][]*Record), 25 | } 26 | } 27 | 28 | func (b *LogRecorder) SetLevel(l Level) { 29 | b.Level = l 30 | } 31 | 32 | func (b *LogRecorder) SetFormatter(f Formatter) { 33 | b.Formatter = f 34 | } 35 | 36 | func (b *LogRecorder) Handle(rec *Record) { 37 | v, ok := b.Records[rec.LoggerName] 38 | if !ok { 39 | v = []*Record{} 40 | } 41 | b.Records[rec.LoggerName] = append(v, rec) 42 | } 43 | 44 | func (b *LogRecorder) Close() { 45 | b.Closed = true 46 | } 47 | 48 | func TestSinkHandler_Handle(t *testing.T) { 49 | loggers := 4 * runtime.NumCPU() 50 | logEntries := 100 51 | 52 | r := NewLogRecorder() 53 | b := NewSinkHandler(r, loggers) 54 | 55 | wg := sync.WaitGroup{} 56 | for i := 0; i < loggers; i++ { 57 | l := NewLogger(fmt.Sprint("logger ", i)) 58 | l.SetHandler(b) 59 | wg.Add(1) 60 | go doLog(l, logEntries, &wg) 61 | } 62 | wg.Wait() 63 | 64 | b.Close() 65 | if !r.Closed { 66 | t.Errorf("Not closed") 67 | } 68 | 69 | for i := 0; i < loggers; i++ { 70 | if v, ok := r.Records[fmt.Sprint("logger ", i)]; !ok || len(v) != logEntries { 71 | t.Errorf("Missing log records expected %d got %d", logEntries, r.Records[fmt.Sprint("logger ", i)]) 72 | } 73 | } 74 | } 75 | 76 | func doLog(l Logger, n int, wg *sync.WaitGroup) { 77 | for i := 0; i < n; i++ { 78 | l.Info("test %d", i) 79 | time.Sleep(time.Millisecond) 80 | } 81 | wg.Done() 82 | } 83 | 84 | func TestSinkHandler_HandleFatal(t *testing.T) { 85 | if os.Getenv("CRASH_TEST") == "1" { 86 | b := NewSinkHandler(StdoutHandler, 1) 87 | l := NewLogger("test") 88 | l.SetHandler(b) 89 | l.Fatal("Goodbye!") 90 | } 91 | 92 | stdout := bytes.NewBuffer(make([]byte, 32)) 93 | 94 | cmd := exec.Command(os.Args[0], "-test.run=TestSinkHandler_HandleFatal") 95 | cmd.Env = append(os.Environ(), "CRASH_TEST=1") 96 | cmd.Stdout = stdout 97 | cmd.Run() 98 | 99 | if !strings.Contains(stdout.String(), "Goodbye!") { 100 | t.Errorf("Message not logged") 101 | } 102 | } 103 | 104 | func TestBaseHandler_SetLevel(t *testing.T) { 105 | r := NewLogRecorder() 106 | b := NewSinkHandler(r, 1) 107 | 108 | b.SetLevel(DefaultLevel) 109 | if r.Level != DefaultLevel { 110 | t.Errorf("Level not set") 111 | } 112 | } 113 | 114 | func TestSinkHandler_SetFormatter(t *testing.T) { 115 | r := NewLogRecorder() 116 | b := NewSinkHandler(r, 1) 117 | 118 | b.SetFormatter(DefaultFormatter) 119 | if r.Formatter != DefaultFormatter { 120 | t.Errorf("Formatter not set") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /syslog.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!plan9 2 | 3 | package logging 4 | 5 | import ( 6 | "log/syslog" 7 | ) 8 | 9 | /////////////////// 10 | // // 11 | // SyslogHandler // 12 | // // 13 | /////////////////// 14 | 15 | // SyslogHandler sends the logging output to syslog. 16 | type SyslogHandler struct { 17 | *BaseHandler 18 | w *syslog.Writer 19 | } 20 | 21 | func NewSyslogHandler(tag string) (*SyslogHandler, error) { 22 | // Priority in New constructor is not important here because we 23 | // do not use w.Write() directly. 24 | w, err := syslog.New(syslog.LOG_INFO|syslog.LOG_USER, tag) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &SyslogHandler{ 29 | BaseHandler: NewBaseHandler(), 30 | w: w, 31 | }, nil 32 | } 33 | 34 | func (b *SyslogHandler) Handle(rec *Record) { 35 | message := b.BaseHandler.FilterAndFormat(rec) 36 | if message == "" { 37 | return 38 | } 39 | 40 | var fn func(string) error 41 | switch rec.Level { 42 | case CRITICAL: 43 | fn = b.w.Crit 44 | case ERROR: 45 | fn = b.w.Err 46 | case WARNING: 47 | fn = b.w.Warning 48 | case NOTICE: 49 | fn = b.w.Notice 50 | case INFO: 51 | fn = b.w.Info 52 | case DEBUG: 53 | fn = b.w.Debug 54 | } 55 | fn(message) 56 | } 57 | 58 | func (b *SyslogHandler) Close() { 59 | b.w.Close() 60 | } 61 | --------------------------------------------------------------------------------