├── .travis.yml ├── LICENSE ├── README.md ├── appender.go ├── appender_test.go ├── doc.go ├── formatter.go ├── formatter_test.go ├── golog_adapter.go ├── golog_adapter_test.go ├── logging.go ├── logging_test.go ├── loglevel.go ├── loglevel_test.go ├── rollingFileAppender.go ├── rollingFileAppender_test.go ├── syslog_appender.go ├── syslog_appender_win.go └── taglist.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | - 1.5 7 | - tip 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fog Creek Software, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logging [![Build Status](https://travis-ci.org/sasbury/logging.svg?branch=master)](https://travis-ci.org/sasbury/logging)[![GoDoc](https://godoc.org/github.com/sasbury/logging?status.svg)](https://godoc.org/github.com/sasbury/logging) 2 | ======= 3 | 4 | Logging is generally broken into two pieces: what to log and where to log it. 5 | 6 | These values are often specified by named loggers with formatters that specify the format of log messages, 7 | a level that specifies how much to log and some sort of target to specify where to log. The fog creek logging package for Go takes a slightly different approach based on our experience with real world logging. 8 | 9 | A default logger provides an easy path to logging. The logging package provides methods to directly log to the default logger without accessing it 10 | 11 | logging.Info("information message") 12 | 13 | is the same as 14 | 15 | logging.DefaultLogger().Info("information message") 16 | 17 | You can also use named loggers, to scope log levels to packages. 18 | 19 | logging.GetLogger("my logger").Infof("%v", aVar) 20 | 21 | Tags provide a way to set levels across concepts, perpendicular to logger names. 22 | 23 | tags := ["network", "server"] 24 | logging.DebugWithTags(tags, "tagged message") 25 | 26 | Levels are used to filter logging. There are five levels: 27 | 28 | * VERBOSE 29 | * DEBUG 30 | * INFO 31 | * WARN 32 | * ERROR 33 | 34 | By default INFO messages and above are allowed. Each logger can have a level. A method to set the default level is available, and actually sets the level on the default logger. 35 | 36 | logging.SetDefaultLogLevel(logging.INFO) 37 | 38 | is equivalent to 39 | 40 | logging.DefaultLogger().SetLogLevel(logging.INFO) 41 | 42 | Levels can also be set by tag, which overrides the loggers level. By default loggers use the default loggers level. 43 | 44 | Loggers have a four methods for each log level, one for formatted messages, and one for just a simple array of values, plus a version of each of these with tags. 45 | 46 | ErrorWithTagsf(tags []string, fmt string, args ...interface{}) 47 | ErrorWithTags(tags []string, args ...interface{}) 48 | Errorf(fmt string, args ...interface{}) 49 | Error(args ...interface{}) 50 | 51 | WarnWithTagsf(tags []string, fmt string, args ...interface{}) 52 | WarnWithTags(tags []string, args ...interface{}) 53 | Warnf(fmt string, args ...interface{}) 54 | Warn(args ...interface{}) 55 | 56 | InfoWithTagsf(tags []string, fmt string, args ...interface{}) 57 | InfoWithTags(tags []string, args ...interface{}) 58 | Infof(fmt string, args ...interface{}) 59 | Info(args ...interface{}) 60 | 61 | DebugWithTagsf(tags []string, fmt string, args ...interface{}) 62 | DebugWithTags(tags []string, args ...interface{}) 63 | Debugf(fmt string, args ...interface{}) 64 | Debug(args ...interface{}) 65 | 66 | VerboseWithTagsf(tags []string, fmt string, args ...interface{}) 67 | Verbosef(fmt string, args ...interface{}) 68 | 69 | Log messages are formatted by a format function. Which can be set per logger, by default loggers copy the default loggers format. 70 | 71 | Logging ultimately goes through one or more appenders to get the messages to the console, a file or wherever. 72 | All loggers share the same appenders - but appenders can be associated with a level which is unrelated to tags. 73 | 74 | Each logger has an optional buffer, that will be flushed whenever its level/tags change. 75 | This buffer contains un-passed messages. So that it is possible to configure the system to capture messages and replay them latter. 76 | 77 | Replayed messages are tagged and have a double time stamp. 78 | 79 | To use go vet with this package you can use the form: 80 | 81 | % go tool vet -printfuncs "ErrorWithTagsf,Errorf,WarnWithTagsf,Warnf,InfoWithTagsf,Infof,DebugWithTagsf,Debugf" 82 | 83 | To use logging with your project simply: 84 | 85 | % go get github.com/sasbury/logging 86 | 87 | -------------------------------------------------------------------------------- /appender.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | // LogAppender is used to push log records to a destination like a file. 12 | type LogAppender interface { 13 | // Log takes a record and should append it. 14 | Log(record *LogRecord) error 15 | // SetLevel should remember the level assigned to this appender and check 16 | // it to filter incoming records. 17 | SetLevel(l LogLevel) 18 | // SetFormatter should remember the formatting function that this 19 | // appender should use to generate strings from LogRecords. 20 | SetFormatter(formatter LogFormatter) 21 | } 22 | 23 | // ClosableAppender defines an optional single method for appenders that 24 | // need to be closed when they will not be used anymore. An example 25 | // is a file appender. 26 | type ClosableAppender interface { 27 | LogAppender 28 | io.Closer 29 | } 30 | 31 | // BaseLogAppender provides a simple struct for building log appenders. 32 | type BaseLogAppender struct { 33 | m sync.RWMutex 34 | level LogLevel 35 | formatter LogFormatter 36 | } 37 | 38 | // SetLevel stores the level in the BaseLogAppender struct. 39 | func (appender *BaseLogAppender) SetLevel(l LogLevel) { 40 | appender.m.Lock() 41 | appender.level = l 42 | appender.m.Unlock() 43 | } 44 | 45 | func (appender *BaseLogAppender) checkLevel(l LogLevel) bool { 46 | // caller is responsible for obtaining lock 47 | return appender.level <= l 48 | } 49 | 50 | // CheckLevel tests the level in the BaseLogAppender struct. 51 | func (appender *BaseLogAppender) CheckLevel(l LogLevel) bool { 52 | appender.m.RLock() 53 | defer appender.m.RUnlock() 54 | 55 | return appender.checkLevel(l) 56 | } 57 | 58 | // SetFormatter stores the formatting function in the BaseLogAppender struct. 59 | func (appender *BaseLogAppender) SetFormatter(formatter LogFormatter) { 60 | appender.m.Lock() 61 | appender.formatter = formatter 62 | appender.m.Unlock() 63 | } 64 | 65 | func (appender *BaseLogAppender) format(record *LogRecord) string { 66 | // caller is responsible for obtaining lock 67 | formatter := appender.formatter 68 | 69 | if formatter == nil { 70 | formatter = defaultFormatter 71 | } 72 | 73 | return formatter(record.Level, record.Tags, record.Message, record.Time, record.Original) 74 | } 75 | 76 | // NullAppender is a simple log appender that just counts the number of log messages. 77 | type NullAppender struct { 78 | BaseLogAppender 79 | count int64 80 | } 81 | 82 | // NewNullAppender creates a null appender. 83 | func NewNullAppender() *NullAppender { 84 | return &NullAppender{} 85 | } 86 | 87 | // Log adds 1 to the count. 88 | func (appender *NullAppender) Log(record *LogRecord) error { 89 | atomic.AddInt64(&(appender.count), 1) 90 | return nil 91 | } 92 | 93 | // Count returns the count of messages logged. 94 | func (appender *NullAppender) Count() int64 { 95 | return atomic.LoadInt64(&(appender.count)) 96 | } 97 | 98 | // ErrorAppender is provided for testing and will generate an error 99 | // when asked to log a message, it will also maintain a count. 100 | type ErrorAppender struct { 101 | NullAppender 102 | } 103 | 104 | // NewErrorAppender creates an ErrorAppender. 105 | func NewErrorAppender() *ErrorAppender { 106 | return &ErrorAppender{} 107 | } 108 | 109 | // Log adds to the count and returns an error. 110 | func (appender *ErrorAppender) Log(record *LogRecord) error { 111 | atomic.AddInt64(&(appender.count), 1) 112 | return fmt.Errorf("error: %s", record.Message) 113 | } 114 | 115 | // ConsoleAppender can be used to write log records to standard 116 | // err or standard out. 117 | type ConsoleAppender struct { 118 | useStdout bool 119 | BaseLogAppender 120 | } 121 | 122 | // NewStdErrAppender creates a console appender configured to write to 123 | // standard err. 124 | func NewStdErrAppender() *ConsoleAppender { 125 | return &ConsoleAppender{} 126 | } 127 | 128 | // NewStdOutAppender creates a console appender configured to write to 129 | // standard out. 130 | func NewStdOutAppender() *ConsoleAppender { 131 | return &ConsoleAppender{useStdout: true} 132 | } 133 | 134 | // Log writes the record, if its level passes the appenders level 135 | // to stderr or stdout. 136 | func (appender *ConsoleAppender) Log(record *LogRecord) error { 137 | appender.m.Lock() 138 | defer appender.m.Unlock() 139 | 140 | if !appender.checkLevel(record.Level) { 141 | return nil 142 | } 143 | 144 | if appender.useStdout { 145 | fmt.Fprintln(os.Stdout, appender.format(record)) 146 | } else { 147 | fmt.Fprintln(os.Stderr, appender.format(record)) 148 | } 149 | return nil 150 | } 151 | 152 | // MemoryAppender is useful for testing and keeps a list of logged messages. 153 | type MemoryAppender struct { 154 | BaseLogAppender 155 | // LoggedMesssages is the list of messages that have been logged to this appender 156 | LoggedMessages []string 157 | } 158 | 159 | // NewMemoryAppender creates a new empty memory appender. 160 | func NewMemoryAppender() *MemoryAppender { 161 | appender := new(MemoryAppender) 162 | appender.LoggedMessages = make([]string, 0, 100) 163 | return appender 164 | } 165 | 166 | // Log checks the log records level and if it passes appends the record to the list. 167 | func (appender *MemoryAppender) Log(record *LogRecord) error { 168 | appender.m.Lock() 169 | defer appender.m.Unlock() 170 | 171 | if !appender.checkLevel(record.Level) { 172 | return nil 173 | } 174 | 175 | appender.LoggedMessages = append(appender.LoggedMessages, appender.format(record)) 176 | return nil 177 | } 178 | 179 | // GetLoggedMessages returns the list of logged messages as strings. 180 | func (appender *MemoryAppender) GetLoggedMessages() []string { 181 | appender.m.RLock() 182 | defer appender.m.RUnlock() 183 | 184 | return appender.LoggedMessages 185 | } 186 | 187 | // WriterAppender is a simple appender that pushes messages as bytes to a writer. 188 | type WriterAppender struct { 189 | BaseLogAppender 190 | writer io.Writer 191 | } 192 | 193 | // NewWriterAppender creates an appender from the specified writer. 194 | func NewWriterAppender(writer io.Writer) *WriterAppender { 195 | return &WriterAppender{writer: writer} 196 | } 197 | 198 | // Log checks the log record's level and then writes the formatted record 199 | // to the writer, followed by the bytes for "\n". 200 | func (appender *WriterAppender) Log(record *LogRecord) error { 201 | appender.m.Lock() 202 | defer appender.m.Unlock() 203 | 204 | if !appender.checkLevel(record.Level) { 205 | return nil 206 | } 207 | 208 | if appender.writer != nil { 209 | _, err := appender.writer.Write([]byte(appender.format(record))) 210 | _, err = appender.writer.Write([]byte("\n")) 211 | return err 212 | } 213 | 214 | return nil 215 | } 216 | -------------------------------------------------------------------------------- /appender_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bytes" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestAppenderLevel(t *testing.T) { 10 | 11 | logger, memory := setup() 12 | logger.SetLogLevel(DEBUG) 13 | memory.SetLevel(WARN) 14 | 15 | secondAppender := NewMemoryAppender() 16 | secondAppender.SetLevel(DEBUG) 17 | AddAppender(secondAppender) 18 | 19 | logger.Error("error") 20 | logger.Info("info") 21 | 22 | WaitForIncoming() 23 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Appender should filter messages.") 24 | assert.Equal(t, len(secondAppender.GetLoggedMessages()), 2, "Appender should work separately.") 25 | } 26 | 27 | func TestNullAppender(t *testing.T) { 28 | ClearAppenders() 29 | 30 | app := NewNullAppender() 31 | AddAppender(app) 32 | 33 | SetDefaultLogLevel(INFO) 34 | Info("one") 35 | Debug("two") 36 | 37 | WaitForIncoming() 38 | assert.Equal(t, app.Count(), int64(1), "Null appender should check levels appropriately") 39 | } 40 | 41 | func TestAppenderCheckLevel(t *testing.T) { //not sure how to test std err without subproc so this is for coverage 42 | ClearAppenders() 43 | 44 | app := NewStdErrAppender() 45 | AddAppender(app) 46 | 47 | app.SetLevel(INFO) 48 | 49 | assert.True(t, app.CheckLevel(ERROR), "error is allowed") 50 | assert.True(t, app.CheckLevel(INFO), "info is allowed") 51 | assert.False(t, app.CheckLevel(DEBUG), "debug is not allowed") 52 | } 53 | 54 | func TestStdErrAppender(t *testing.T) { //not sure how to test std err without subproc so this is for coverage 55 | ClearAppenders() 56 | 57 | app := NewStdErrAppender() 58 | AddAppender(app) 59 | 60 | SetDefaultLogLevel(INFO) 61 | Info("one") 62 | Debug("two") 63 | WaitForIncoming() 64 | } 65 | 66 | func TestStdOutAppender(t *testing.T) { //not sure how to test std out without subproc so this is for coverage 67 | ClearAppenders() 68 | 69 | app := NewStdOutAppender() 70 | AddAppender(app) 71 | 72 | SetDefaultLogLevel(INFO) 73 | Info("one") 74 | Debug("two") 75 | WaitForIncoming() 76 | } 77 | 78 | func TestWriterAppender(t *testing.T) { 79 | ClearAppenders() 80 | 81 | SetDefaultLogLevel(DEBUG) 82 | 83 | var buf bytes.Buffer 84 | 85 | app := NewWriterAppender(&buf) 86 | app.SetFormatter(GetFormatter(MINIMAL)) 87 | AddAppender(app) 88 | 89 | app.SetLevel(INFO) 90 | 91 | Info("one") 92 | Debug("two") 93 | 94 | WaitForIncoming() 95 | PauseLogging() // data race if we don't pause 96 | 97 | s := string(buf.Bytes()) 98 | 99 | assert.Equal(t, s, "one\n", "File should contain a single entry for writer appender") 100 | RestartLogging() //don't leave logging off 101 | } 102 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package logging implements tag based logging. 3 | 4 | Logging is generally broken into two pieces: what to log and where to log it. 5 | These values are often specified by named loggers with formatters that specify the format of log messages, 6 | a level that specifies how much to log and some sort of target to specify where to log. The fog creek logging package for Go 7 | takes a slightly different approach based on our experience with real world logging. 8 | 9 | A default logger provides an easy path to logging. The logging package provides methods to directly log ot the default logger without accessing it 10 | 11 | logging.Info("information message") 12 | 13 | is the same as 14 | 15 | logging.DefaultLogger().Info("information message") 16 | 17 | You can also use named loggers, to scope log levels to packages. 18 | 19 | logging.GetLogger("my logger").Infof("%v", aVar) 20 | 21 | Tags provide a way to set levels across concepts, perpendicular to logger names. 22 | 23 | tags := ["network", "server"] 24 | logging.DebugWithTags(tags, "tagged message") 25 | 26 | Levels are used to filter logging. There are five levels: 27 | 28 | * VERBOSE 29 | * DEBUG 30 | * INFO 31 | * WARN 32 | * ERROR 33 | 34 | By default INFO messages and above are allowed. Each logger can have a level. A method to set the default level is available, and 35 | actually sets the level on the default logger. 36 | 37 | logging.SetDefaultLogLevel(logging.INFO) 38 | 39 | is equivalent to 40 | 41 | logging.DefaultLogger().SetLogLevel(logging.INFO) 42 | 43 | Levels can also be set by tag, which overrides the loggers level. By default loggers use the default loggers level. 44 | 45 | Loggers have a four methods for each log level, one for formatted messages, and one for just a simple array of values, plus a version of each of these with tags. 46 | 47 | ErrorWithTagsf(tags []string, fmt string, args ...interface{}) 48 | ErrorWithTags(tags []string, args ...interface{}) 49 | Errorf(fmt string, args ...interface{}) 50 | Error(args ...interface{}) 51 | 52 | WarnWithTagsf(tags []string, fmt string, args ...interface{}) 53 | WarnWithTags(tags []string, args ...interface{}) 54 | Warnf(fmt string, args ...interface{}) 55 | Warn(args ...interface{}) 56 | 57 | InfoWithTagsf(tags []string, fmt string, args ...interface{}) 58 | InfoWithTags(tags []string, args ...interface{}) 59 | Infof(fmt string, args ...interface{}) 60 | Info(args ...interface{}) 61 | 62 | DebugWithTagsf(tags []string, fmt string, args ...interface{}) 63 | DebugWithTags(tags []string, args ...interface{}) 64 | Debugf(fmt string, args ...interface{}) 65 | Debug(args ...interface{}) 66 | 67 | Verbose is special, since it rarely should/would be called without formatting. 68 | 69 | VerboseWithTagsf(tags []string, fmt string, args ...interface{}) 70 | Verbosef(fmt string, args ...interface{}) 71 | 72 | Log messages are formatted by a format function. Which can be set per logger, by default, loggers copy the default loggers format. 73 | 74 | Logging ultimately goes through one or more appenders to get the messages to the console, a file or wherever. 75 | All loggers share the same appenders - but appenders can be associated with a level which is unrelated to tags. 76 | 77 | Each logger has an optional buffer, that will be flushed whenever its level/tags change. 78 | This buffer contains un-passed messages. So that it is possible to configure the system to capture messages and replay them latter. 79 | Replayed messages are tagged and have a double time stamp. 80 | 81 | To use go vet with this package you can use the form: 82 | 83 | go tool vet -printfuncs "ErrorWithTagsf,Errorf,WarnWithTagsf,Warnf,InfoWithTagsf,Infof,DebugWithTagsf,Debugf" 84 | 85 | */ 86 | package logging 87 | -------------------------------------------------------------------------------- /formatter.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | // LogFormat is the name of a known formatting function. 10 | type LogFormat string 11 | 12 | // MINIMAL describes a formatter that just prints the message, replays are not indicated. 13 | const MINIMAL LogFormat = "minimal" 14 | 15 | // MINIMALTAGGED describes a formatter that just prints the level, tags and message, replays are not indicated. 16 | const MINIMALTAGGED LogFormat = "minimaltagged" 17 | 18 | // SIMPLE describes a formatter that just prints the date, level and message, replays are not indicated. 19 | const SIMPLE LogFormat = "simple" 20 | 21 | // FULL formats messages with the date to ms accuracy, the level, tags and message. Replayed messages have a special field added. 22 | const FULL LogFormat = "full" 23 | 24 | // FormatFromString converts a string name to a LogFormat. Valid 25 | // arguemnts include full, simple, minimaltagged and minimal. An 26 | // unknown string will be treated like simple. 27 | func FormatFromString(formatName string) LogFormat { 28 | formatName = strings.ToLower(formatName) 29 | switch formatName { 30 | case "full": 31 | return FULL 32 | case "simple": 33 | return SIMPLE 34 | case "minimaltagged": 35 | return MINIMALTAGGED 36 | case "minimal": 37 | return MINIMAL 38 | default: 39 | return SIMPLE 40 | } 41 | } 42 | 43 | // GetFormatter returns the function associated with a named format. 44 | func GetFormatter(formatName LogFormat) LogFormatter { 45 | switch formatName { 46 | case FULL: 47 | return fullFormat 48 | case SIMPLE: 49 | return simpleFormat 50 | case MINIMALTAGGED: 51 | return minimalWithTagsFormat 52 | case MINIMAL: 53 | return minimalFormat 54 | default: 55 | return simpleFormat 56 | } 57 | } 58 | 59 | // LogFormatter is a function type used to convert a log record into a string. 60 | // Original time is provided times when the formatter has to construct a replayed message from the buffer. 61 | type LogFormatter func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string 62 | 63 | var fullFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 64 | 65 | if original != t { 66 | message = fmt.Sprintf("[replayed from %v] %v", original.Format(time.StampMilli), message) 67 | } 68 | 69 | if tags != nil && len(tags) > 0 { 70 | return fmt.Sprintf("[%v] [%v] %v %v", t.Format(time.StampMilli), level, tags, message) 71 | } 72 | return fmt.Sprintf("[%v] [%v] %v", t.Format(time.StampMilli), level, message) 73 | } 74 | 75 | var simpleFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 76 | return fmt.Sprintf("[%v] [%v] %v", t.Format(time.Stamp), level, message) 77 | } 78 | 79 | var minimalFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 80 | return message 81 | } 82 | 83 | var minimalWithTagsFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 84 | if tags != nil && len(tags) > 0 { 85 | return fmt.Sprintf("[%v] %v %v", level, tags, message) 86 | } 87 | return fmt.Sprintf("[%v] %v", level, message) 88 | } 89 | -------------------------------------------------------------------------------- /formatter_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // return how many seconds time zone difference there is between the current location and 11 | // Pacific Time Zone 12 | func secondsFromPST(t *testing.T) int64 { 13 | pst, err := time.LoadLocation("America/Los_Angeles") 14 | assert.NoError(t, err) 15 | timeHere, err := time.ParseInLocation(time.ANSIC, "Mon Jan 2 15:04:05 2006", time.Now().Location()) 16 | assert.NoError(t, err) 17 | timePST, err := time.ParseInLocation(time.ANSIC, "Mon Jan 2 15:04:05 2006", pst) 18 | assert.NoError(t, err) 19 | return int64(timeHere.Sub(timePST).Seconds()) 20 | } 21 | 22 | func TestFormatFromString(t *testing.T) { 23 | assert.Equal(t, FormatFromString("FuLl"), FULL, "formats are case insensitive") 24 | assert.Equal(t, FormatFromString("SimplE"), SIMPLE, "formats are case insensitive") 25 | assert.Equal(t, FormatFromString("MinimalTagged"), MINIMALTAGGED, "formats are case insensitive") 26 | assert.Equal(t, FormatFromString("Minimal"), MINIMAL, "formats are case insensitive") 27 | assert.Equal(t, FormatFromString("foo"), SIMPLE, "default is simple") 28 | } 29 | 30 | func TestFormatGetFormatter(t *testing.T) { 31 | // Note: we can't do function equality, so we need to hack around it by temporarily 32 | // swapping out the functions 33 | fullFormatOriginal := fullFormat 34 | simpleFormatOriginal := simpleFormat 35 | minimalWithTagsFormatOriginal := minimalWithTagsFormat 36 | minimalFormatOriginal := minimalFormat 37 | defer func() { 38 | // put everything back 39 | fullFormat = fullFormatOriginal 40 | simpleFormat = simpleFormatOriginal 41 | minimalWithTagsFormat = minimalWithTagsFormatOriginal 42 | minimalFormat = minimalFormatOriginal 43 | }() 44 | 45 | fullFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 46 | return "FULL FORMAT" 47 | } 48 | simpleFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 49 | return "SIMPLE FORMAT" 50 | } 51 | minimalWithTagsFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 52 | return "MINIMAL WITH TAGS FORMAT" 53 | } 54 | minimalFormat = func(level LogLevel, tags []string, message string, t time.Time, original time.Time) string { 55 | return "MINIMAL FORMAT" 56 | } 57 | 58 | assert.Equal(t, "FULL FORMAT", GetFormatter(FULL)(WARN, nil, "", time.Now(), time.Now()), "should be full") 59 | assert.Equal(t, "SIMPLE FORMAT", GetFormatter(SIMPLE)(WARN, nil, "", time.Now(), time.Now()), "should be simple") 60 | assert.Equal(t, "MINIMAL WITH TAGS FORMAT", GetFormatter(MINIMALTAGGED)(WARN, nil, "", time.Now(), time.Now()), "should be minimal tagged") 61 | assert.Equal(t, "MINIMAL FORMAT", GetFormatter(MINIMAL)(WARN, nil, "", time.Now(), time.Now()), "should be minimal") 62 | assert.Equal(t, "SIMPLE FORMAT", GetFormatter(LogFormat("foo"))(WARN, nil, "", time.Now(), time.Now()), "should be simple") 63 | } 64 | 65 | func TestFormatFull(t *testing.T) { 66 | // the test was written for PST, so we need to apply the offset 67 | timeZoneOffset := secondsFromPST(t) 68 | 69 | at := time.Unix(1000+timeZoneOffset, 0) 70 | original := at.AddDate(0, 0, 1) 71 | 72 | expected := "[Dec 31 16:16:40.000] [INFO] [one two] [replayed from Jan 1 16:16:40.000] hello" 73 | assert.Equal(t, fullFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 74 | 75 | expected = "[Dec 31 16:16:40.000] [INFO] [replayed from Jan 1 16:16:40.000] hello" 76 | assert.Equal(t, fullFormat(INFO, nil, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 77 | 78 | expected = "[Dec 31 16:16:40.000] [INFO] [one two] hello" 79 | assert.Equal(t, fullFormat(INFO, []string{"one", "two"}, "hello", at, at), expected, fmt.Sprintf("should equal %s", expected)) 80 | 81 | expected = "[Dec 31 16:16:40.000] [INFO] [one two] [replayed from Jan 1 16:16:40.000] hello" 82 | assert.Equal(t, fullFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 83 | } 84 | 85 | func TestFormatSimple(t *testing.T) { 86 | // the test was written for PST, so we need to apply the offset 87 | timeZoneOffset := secondsFromPST(t) 88 | 89 | at := time.Unix(1000+timeZoneOffset, 0) 90 | original := at.AddDate(0, 0, 1) 91 | 92 | expected := "[Dec 31 16:16:40] [INFO] hello" 93 | assert.Equal(t, simpleFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 94 | assert.Equal(t, simpleFormat(INFO, nil, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 95 | assert.Equal(t, simpleFormat(INFO, []string{"one", "two"}, "hello", at, at), expected, fmt.Sprintf("should equal %s", expected)) 96 | assert.Equal(t, simpleFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 97 | } 98 | 99 | func TestFormatMinimal(t *testing.T) { 100 | 101 | at := time.Unix(1000, 0) 102 | original := at.AddDate(0, 0, 1) 103 | 104 | expected := "hello" 105 | assert.Equal(t, minimalFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 106 | assert.Equal(t, minimalFormat(INFO, nil, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 107 | assert.Equal(t, minimalFormat(INFO, []string{"one", "two"}, "hello", at, at), expected, fmt.Sprintf("should equal %s", expected)) 108 | assert.Equal(t, minimalFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 109 | } 110 | 111 | func TestFormatMinimalWithTags(t *testing.T) { 112 | 113 | at := time.Unix(1000, 0) 114 | original := at.AddDate(0, 0, 1) 115 | 116 | expected := "[INFO] [one two] hello" 117 | assert.Equal(t, minimalWithTagsFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 118 | 119 | expected = "[INFO] hello" 120 | assert.Equal(t, minimalWithTagsFormat(INFO, nil, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 121 | 122 | expected = "[INFO] [one two] hello" 123 | assert.Equal(t, minimalWithTagsFormat(INFO, []string{"one", "two"}, "hello", at, at), expected, fmt.Sprintf("should equal %s", expected)) 124 | 125 | expected = "[INFO] [one two] hello" 126 | assert.Equal(t, minimalWithTagsFormat(INFO, []string{"one", "two"}, "hello", at, original), expected, fmt.Sprintf("should equal %s", expected)) 127 | } 128 | -------------------------------------------------------------------------------- /golog_adapter.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type goLogAdapter struct { 8 | level LogLevel 9 | tags []string 10 | } 11 | 12 | func (adapter *goLogAdapter) Write(p []byte) (n int, err error) { 13 | s := string(p[:]) 14 | defaultLogger.log(adapter.level, adapter.tags, s) 15 | return len(p), nil 16 | } 17 | 18 | //AdaptStandardLogging points the standard logging to fog creek logging 19 | //using the provided level and tags. The default will be info with no tags. 20 | func AdaptStandardLogging(level LogLevel, tags []string) { 21 | adapter := goLogAdapter{ 22 | level: level, 23 | tags: tags, 24 | } 25 | 26 | log.SetFlags(0) 27 | log.SetOutput(&adapter) 28 | } 29 | -------------------------------------------------------------------------------- /golog_adapter_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestGoLogAdapter(t *testing.T) { 10 | 11 | memory := NewMemoryAppender() 12 | ClearAppenders() 13 | AddAppender(memory) 14 | 15 | AdaptStandardLogging(ERROR, []string{}) 16 | 17 | SetDefaultLogLevel(WARN) 18 | SetDefaultBufferLength(10) 19 | 20 | log.Print("error") 21 | log.Print("warn") 22 | log.Print("info") 23 | log.Print("debug") 24 | 25 | WaitForIncoming() 26 | assert.Equal(t, len(memory.GetLoggedMessages()), 4, "All messages at error should log with warn level.") 27 | } 28 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | // Package logging for Go takes a slightly different approach based on our experience with real world logging. 2 | // 3 | // A default logger provides an easy path to logging. 4 | // Named loggers provide a way to set levels by package. 5 | // Tags provide a way to set levels across concepts, perpendicular to logger names. 6 | // Default log levels and default tag levels are independent of the default logger. 7 | // All loggers share the same appenders - but appenders can be associated with a level which is unrelated to tags. 8 | // Each logger has an optional buffer, that will be flushd whenever its level/tags change. 9 | // This buffer contains un-passed messages. So that it is possible to configure the system to capture messages and replay them later. 10 | // Replayed messages are tagged and have a double time stamp. 11 | // A default appender is initialized to send log messages to stderr. 12 | package logging 13 | 14 | import ( 15 | "container/ring" 16 | "fmt" 17 | "runtime" 18 | "sync" 19 | "sync/atomic" 20 | "time" 21 | "strings" 22 | ) 23 | 24 | // Logger is the interface for the objects that are the target of logging messages. Logging methods 25 | // imply a level. For example, Info() implies a level of LogLevel.INFO. 26 | type Logger interface { 27 | PanicWithTagsf(tags []string, fmt string, args ...interface{}) 28 | PanicWithTags(tags []string, args ...interface{}) 29 | Panicf(fmt string, args ...interface{}) 30 | Panic(args ...interface{}) 31 | 32 | ErrorWithTagsf(tags []string, fmt string, args ...interface{}) 33 | ErrorWithTags(tags []string, args ...interface{}) 34 | Errorf(fmt string, args ...interface{}) 35 | Error(args ...interface{}) 36 | 37 | WarnWithTagsf(tags []string, fmt string, args ...interface{}) 38 | WarnWithTags(tags []string, args ...interface{}) 39 | Warnf(fmt string, args ...interface{}) 40 | Warn(args ...interface{}) 41 | 42 | InfoWithTagsf(tags []string, fmt string, args ...interface{}) 43 | InfoWithTags(tags []string, args ...interface{}) 44 | Infof(fmt string, args ...interface{}) 45 | Info(args ...interface{}) 46 | 47 | DebugWithTagsf(tags []string, fmt string, args ...interface{}) 48 | DebugWithTags(tags []string, args ...interface{}) 49 | Debugf(fmt string, args ...interface{}) 50 | Debug(args ...interface{}) 51 | 52 | VerboseWithTagsf(tags []string, fmt string, args ...interface{}) 53 | Verbosef(fmt string, args ...interface{}) 54 | 55 | SetLogLevel(l LogLevel) 56 | SetTagLevel(tag string, l LogLevel) 57 | CheckLevel(l LogLevel, tags []string) bool 58 | 59 | SetBufferLength(length int) 60 | } 61 | 62 | const ( 63 | stopped = iota 64 | paused 65 | running 66 | ) 67 | 68 | // logMutex is a global lock for protecting all global state in package. 69 | var logMutex = new(sync.RWMutex) 70 | 71 | // defaultLogger is provided for most logging situations. 72 | var defaultLogger *LoggerImpl 73 | 74 | // The default format is used to determine how appenders without a custom format log their messages. 75 | var defaultFormatter = GetFormatter(FULL) 76 | 77 | // Loggers share the appenders. 78 | var appenders = make([]LogAppender, 0) 79 | 80 | // The package maintains a map of named loggers. 81 | var loggers = make(map[string]*LoggerImpl) 82 | var incomingChannel = make(chan *LogRecord, 2048) 83 | var stateChannel = make(chan int, 0) 84 | var waiter = new(sync.WaitGroup) 85 | var logged uint64 86 | var processed uint64 87 | var logErrors chan<- error 88 | var enableVerbose int32 89 | 90 | func init() { 91 | defaultLogger = new(LoggerImpl) 92 | defaultLogger.name = "_default" 93 | defaultLogger.level = INFO 94 | defaultLogger.SetBufferLength(0) 95 | 96 | AddAppender(NewStdErrAppender()) 97 | AdaptStandardLogging(INFO, nil) 98 | 99 | go processIncoming() 100 | } 101 | 102 | // LogRecord is the type used in the logging buffer. 103 | type LogRecord struct { 104 | // Time is the time that the log record is being appended, can be 105 | // different from Original if the record was buffered. 106 | Time time.Time 107 | // Original is the original time for the log record. 108 | Original time.Time 109 | // Level is the level the record was logged at. 110 | Level LogLevel 111 | // Tags are the custom tags assigned to the record when it was logged. 112 | Tags []string 113 | // Message is the actual log message. 114 | Message string 115 | // Logger is the logger associated with this log record, if any. 116 | Logger *LoggerImpl 117 | } 118 | 119 | // LoggerImpl stores the data for a logger. 120 | // A Logger maintains its own level, tag levels and buffer. Each logger is named. 121 | type LoggerImpl struct { 122 | name string 123 | level LogLevel 124 | tagLevels tagList 125 | buffer *ring.Ring 126 | } 127 | 128 | // PauseLogging stops all logging from being processed. 129 | // Pause will not wait for all log messages to be processed. 130 | func PauseLogging() { 131 | stateChannel <- paused 132 | } 133 | 134 | // RestartLogging starts messages logging again. 135 | func RestartLogging() { 136 | stateChannel <- running 137 | } 138 | 139 | // StopLogging can only be called once, and completely stops the logging 140 | // process. 141 | func StopLogging() { 142 | stateChannel <- stopped 143 | waiter.Wait() 144 | } 145 | 146 | func processIncoming() { 147 | loop: 148 | for { 149 | select { 150 | case record := <-incomingChannel: 151 | processLogRecord(record) 152 | case newState := <-stateChannel: 153 | switch newState { 154 | case stopped: 155 | waiter.Done() 156 | break loop 157 | case paused: // run a sub-loop looking for a state change 158 | subloop: 159 | for { 160 | select { 161 | case state := <-stateChannel: 162 | switch state { 163 | case stopped: 164 | waiter.Done() 165 | break loop 166 | case running: 167 | break subloop 168 | default: 169 | continue 170 | } 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | 178 | // WaitForIncoming should be used in tests or system shutdowns to make sure 179 | // that all of the log messages pushed into the logging channel are processed 180 | // and appended appropriately. 181 | func WaitForIncoming() { 182 | runtime.Gosched() // start by giving the other go routines a chance to run 183 | for { 184 | if atomic.LoadUint64(&processed) != atomic.LoadUint64(&logged) { 185 | time.Sleep(2 * time.Millisecond) 186 | } else { 187 | return 188 | } 189 | } 190 | } 191 | 192 | // Waits for the specific log to be processed. 193 | func WaitForProcessed(logNum uint64) { 194 | runtime.Gosched() // start by giving the other go routines a chance to run 195 | for { 196 | if logNum != atomic.LoadUint64(&processed) { 197 | time.Sleep(1 * time.Millisecond) 198 | } else { 199 | return 200 | } 201 | } 202 | } 203 | 204 | // CaptureLoggingErrors allows the logging user to provide a channel 205 | // for capturing logging errors. Any error during the logging process, like an 206 | // appender failing will be sent to this channel. 207 | // By default there is no error channel. 208 | // Logging will not block when writting to the error channel so make sure the 209 | // channel is big enough to capture errors 210 | func CaptureLoggingErrors(errs chan<- error) { 211 | logMutex.Lock() 212 | logErrors = errs 213 | logMutex.Unlock() 214 | } 215 | 216 | // DefaultLogger returns a logger that can be used when a named logger isn't required. 217 | func DefaultLogger() Logger { 218 | return defaultLogger 219 | } 220 | 221 | // GetLogger returns a named logger, creating it if necessary. The logger will have the default settings. 222 | // By default the logger will use the DefaultLoggers level and tag levels. 223 | func GetLogger(name string) Logger { 224 | logMutex.RLock() 225 | logger := loggers[name] 226 | logMutex.RUnlock() 227 | 228 | if logger == nil { 229 | logger = new(LoggerImpl) 230 | logger.name = name 231 | logger.level = DEFAULT 232 | logger.SetBufferLength(defaultLogger.buffer.Len()) 233 | logMutex.Lock() 234 | loggers[name] = logger 235 | logMutex.Unlock() 236 | } 237 | 238 | return logger 239 | } 240 | 241 | // EnableVerboseLogging by default verbose logging is ignored, use this 242 | // method to allow verbose logging. 243 | func EnableVerboseLogging() { 244 | atomic.StoreInt32(&enableVerbose, 1) 245 | } 246 | 247 | // DisableVerboseLogging by default verbose logging is ignored, use this 248 | // method to turn off verbose logging if you have enabled it. 249 | func DisableVerboseLogging() { 250 | atomic.StoreInt32(&enableVerbose, 0) 251 | } 252 | 253 | // SetDefaultLogLevel sets the default loggers log level, flushes all buffers in case messages are cleared for logging. 254 | func SetDefaultLogLevel(l LogLevel) { 255 | defaultLogger.SetLogLevel(l) 256 | } 257 | 258 | // SetDefaultTagLogLevel sets the default loggers level for the specified tag, flushes all buffers in case messages are cleared for logging.. 259 | func SetDefaultTagLogLevel(tag string, l LogLevel) { 260 | defaultLogger.SetTagLevel(tag, l) 261 | } 262 | 263 | // SetDefaultFormatter sets the default formatter used by appenders that don't have their own. 264 | func SetDefaultFormatter(formatter LogFormatter) { 265 | logMutex.Lock() 266 | defaultFormatter = formatter 267 | logMutex.Unlock() 268 | } 269 | 270 | // SetDefaultBufferLength sets the buffer length for the default logger, new loggers will use this length. 271 | // Existing loggers with buffers are not affected, those with buffers are not effected. 272 | func SetDefaultBufferLength(length int) { 273 | 274 | logMutex.Lock() 275 | defaultLogger.setBufferLengthImpl(length) 276 | 277 | for _, val := range loggers { 278 | if val.buffer == nil { 279 | val.setBufferLengthImpl(length) 280 | } 281 | } 282 | 283 | logMutex.Unlock() 284 | } 285 | 286 | // AddAppender adds a new global appender for use by all loggers. Levels can be used to restrict logging to specific appenders. 287 | func AddAppender(appender LogAppender) { 288 | logMutex.Lock() 289 | appenders = append(appenders, appender) 290 | logMutex.Unlock() 291 | } 292 | 293 | // ClearAppenders removes all of the global appenders, mainly used during configuration. 294 | // Will pause and restart logging. 295 | func ClearAppenders() { 296 | PauseLogging() 297 | logMutex.Lock() 298 | for _, appender := range appenders { 299 | if app, ok := appender.(ClosableAppender); ok { 300 | app.Close() 301 | } 302 | } 303 | appenders = make([]LogAppender, 0) 304 | logMutex.Unlock() 305 | RestartLogging() 306 | } 307 | 308 | // ClearLoggers is provided so that an application can 309 | // completely reset its logging configuration, for example 310 | // on a SIGHUP. 311 | func ClearLoggers() { 312 | PauseLogging() 313 | logMutex.Lock() 314 | loggers = make(map[string]*LoggerImpl) 315 | logMutex.Unlock() 316 | RestartLogging() 317 | } 318 | 319 | /* 320 | AddTag creates a new array and adds a string to it. This insures that no 321 | slices are shared for tags. 322 | */ 323 | func AddTag(tags []string, newTag string) []string { 324 | newTags := make([]string, 0, len(tags)+1) 325 | newTags = append(newTags, tags...) 326 | newTags = append(newTags, newTag) 327 | return newTags 328 | } 329 | 330 | // SetLogLevel sets the level of messages allowed for a logger. This level can be 331 | // overriden for specific tags using SetTagLevel. Changing the level for a Logger 332 | // flushes its buffer in case messages are now free to be logged. This means that 333 | // buffered messages might be printed out of order, but will be formatted to indicate this. 334 | func (logger *LoggerImpl) SetLogLevel(l LogLevel) { 335 | logMutex.Lock() 336 | logger.level = l 337 | 338 | wait := new(sync.WaitGroup) 339 | 340 | if logger == defaultLogger { 341 | flushAllLoggers(wait) 342 | } else { 343 | wait.Add(1) 344 | logger.flushBuffer(wait) 345 | } 346 | logMutex.Unlock() 347 | wait.Wait() 348 | } 349 | 350 | // SetTagLevel assigns a log level to a specific tag. This level can override the general 351 | // level for a logger allowing specific log messages to slip through and be appended to the logs. 352 | func (logger *LoggerImpl) SetTagLevel(tag string, l LogLevel) { 353 | logMutex.Lock() 354 | logger.tagLevels = logger.tagLevels.setTagLevel(tag,l) 355 | 356 | wait := new(sync.WaitGroup) 357 | if logger == defaultLogger { 358 | flushAllLoggers(wait) 359 | } else { 360 | wait.Add(1) 361 | logger.flushBuffer(wait) 362 | } 363 | logMutex.Unlock() 364 | wait.Wait() 365 | } 366 | 367 | // SetBufferLength clears the buffer and creates a new one of the specified length. 368 | func (logger *LoggerImpl) SetBufferLength(length int) { 369 | logMutex.Lock() 370 | 371 | logger.setBufferLengthImpl(length) 372 | 373 | logMutex.Unlock() 374 | } 375 | 376 | // expects the lock. 377 | func (logger *LoggerImpl) setBufferLengthImpl(length int) { 378 | 379 | if length == 0 { 380 | logger.buffer = nil 381 | } else if length != logger.buffer.Len() { 382 | logger.buffer = ring.New(length) 383 | } 384 | } 385 | 386 | // NewLogRecord creates a log record object. 387 | func NewLogRecord(logger *LoggerImpl, level LogLevel, tags []string, message string, time time.Time, original time.Time) *LogRecord { 388 | record := new(LogRecord) 389 | record.Logger = logger 390 | record.Level = level 391 | record.Tags = tags 392 | record.Message = message 393 | record.Time = time 394 | record.Original = original 395 | return record 396 | } 397 | 398 | // should be called inside the logging lock, 399 | // puts the error on the logging error channel if one is set. 400 | func logError(err error) { 401 | if err != nil && logErrors != nil { 402 | 403 | select { 404 | case logErrors <- err: 405 | // write the error 406 | default: 407 | // don't write or block 408 | } 409 | } 410 | } 411 | 412 | // Check the tags for this logger, or the defaults, if any pass, then we pass. 413 | // Should be called inside the logging lock. 414 | func (logger *LoggerImpl) checkTagLevel(l LogLevel, tags []string) bool { 415 | 416 | return logger.tagLevels.checkTagLevel(l, tags) || 417 | (logger != defaultLogger && defaultLogger.tagLevels.checkTagLevel(l, tags)) 418 | 419 | return false 420 | } 421 | 422 | // CheckLevel tests the default logger for its permissions. 423 | func CheckLevel(l LogLevel, tags []string) bool { 424 | return defaultLogger.CheckLevel(l, tags) 425 | } 426 | 427 | // CheckLevel checks tags, then check the level on this , or the default level. 428 | func (logger *LoggerImpl) CheckLevel(l LogLevel, tags []string) bool { 429 | 430 | logMutex.RLock() 431 | defer logMutex.RUnlock() 432 | 433 | return logger.checkLevelWithTags(l, tags) 434 | } 435 | 436 | // requires the lock be acquired. 437 | func (logger *LoggerImpl) checkLevelWithTags(l LogLevel, tags []string) bool { 438 | 439 | if tags != nil { 440 | if logger.checkTagLevel(l, tags) { 441 | return true // otherwise check the general level 442 | } 443 | } 444 | 445 | if logger.level != DEFAULT { 446 | return logger.level <= l 447 | } 448 | 449 | return defaultLogger.level <= l 450 | } 451 | 452 | // flushAllLoggers expects the logging lock to be held by the caller. 453 | func flushAllLoggers(wait *sync.WaitGroup) { 454 | wait.Add(len(loggers) + 1) 455 | for _, val := range loggers { 456 | val.flushBuffer(wait) 457 | } 458 | defaultLogger.flushBuffer(wait) 459 | } 460 | 461 | // should be called witin the lock 462 | func logToAppenders(record *LogRecord) { 463 | for _, appender := range appenders { 464 | err := appender.Log(record) 465 | logError(err) 466 | } 467 | } 468 | 469 | func processLogRecord(record *LogRecord) { 470 | logMutex.RLock() 471 | defer logMutex.RUnlock() 472 | 473 | logger := record.Logger 474 | passed := logger.checkLevelWithTags(record.Level, record.Tags) 475 | 476 | if passed { 477 | logToAppenders(record) 478 | } else if logger.buffer != nil && record.Level > VERBOSE { 479 | logger.buffer.Next().Value = record 480 | logger.buffer = logger.buffer.Next() 481 | } 482 | atomic.AddUint64(&processed, 1) 483 | } 484 | 485 | // flushBuffer expects the logging lock to be held, and does not take the lock. 486 | // should call done on the wait group when the buffer is flushed. 487 | // does not 1 to the waitgroup. 488 | func (logger *LoggerImpl) flushBuffer(wait *sync.WaitGroup) { 489 | if logger.buffer != nil { 490 | now := time.Now() 491 | oldBuffer := logger.buffer 492 | logger.buffer = ring.New(oldBuffer.Len()) 493 | 494 | go func() { 495 | oldBuffer.Do(func(x interface{}) { 496 | 497 | if x == nil { 498 | return 499 | } 500 | 501 | record := x.(*LogRecord) 502 | record.Time = now 503 | 504 | atomic.AddUint64(&logged, 1) 505 | incomingChannel <- record 506 | }) 507 | 508 | wait.Done() 509 | }() 510 | } else { 511 | wait.Done() 512 | } 513 | } 514 | 515 | func (logger *LoggerImpl) logwithformat(level LogLevel, tags []string, format string, args ...interface{}) uint64 { 516 | 517 | if level == VERBOSE && atomic.LoadInt32(&enableVerbose) != 1 { 518 | return 0 519 | } 520 | 521 | now := time.Now() 522 | msg := "" 523 | 524 | if format == "" { 525 | msg = fmt.Sprint(args...) 526 | } else { 527 | msg = fmt.Sprintf(format, args...) 528 | } 529 | 530 | if level == PANIC { 531 | stack := make([]byte, 10 * 1024) 532 | size := runtime.Stack(stack, false) 533 | stackStr := strings.Replace(string(stack[:size]), "\n", "\n ", -1) 534 | msg = msg + "\n " + stackStr 535 | } 536 | 537 | logRecord := NewLogRecord(logger, level, tags, msg, now, now) 538 | logNum := atomic.AddUint64(&logged, 1) 539 | incomingChannel <- logRecord 540 | 541 | // return the logged number to track if it was processed 542 | return logNum 543 | } 544 | 545 | func (logger *LoggerImpl) log(level LogLevel, tags []string, args ...interface{}) uint64{ 546 | return logger.logwithformat(level, tags, "", args...) 547 | } 548 | 549 | // PanicWithTagsf logs a PANIC level message with the provided tags and formatted string. 550 | func (logger *LoggerImpl) PanicWithTagsf(tags []string, format string, args ...interface{}) { 551 | logNum := logger.logwithformat(PANIC, tags, format, args...) 552 | 553 | // ensure the panic message we just logged gets processed 554 | WaitForProcessed(logNum) 555 | 556 | // flush all logs before panicking 557 | logMutex.Lock() 558 | wait := new(sync.WaitGroup) 559 | if logger == defaultLogger { 560 | flushAllLoggers(wait) 561 | } else { 562 | wait.Add(1) 563 | logger.flushBuffer(wait) 564 | } 565 | logMutex.Unlock() 566 | wait.Wait() 567 | 568 | // continue with the panic 569 | if format == "" { 570 | panic(fmt.Sprint(args...)) 571 | } else { 572 | panic(fmt.Sprintf(format, args...)) 573 | } 574 | } 575 | 576 | // PanicWithTags logs a PANIC level message with the provided tags and provided arguments joined into a string. 577 | func (logger *LoggerImpl) PanicWithTags(tags []string, args ...interface{}) { 578 | logNum := logger.log(PANIC, tags, args...) 579 | 580 | // ensure the panic message we just logged gets processed 581 | WaitForProcessed(logNum) 582 | 583 | // flush all logs before panicking 584 | logMutex.Lock() 585 | wait := new(sync.WaitGroup) 586 | if logger == defaultLogger { 587 | flushAllLoggers(wait) 588 | } else { 589 | wait.Add(1) 590 | logger.flushBuffer(wait) 591 | } 592 | logMutex.Unlock() 593 | wait.Wait() 594 | 595 | // continue with panic 596 | panic(fmt.Sprint(args...)) 597 | } 598 | 599 | // Panicf logs a PANIC level message with the no tags and formatted string. 600 | func (logger *LoggerImpl) Panicf(format string, args ...interface{}) { 601 | logNum := logger.logwithformat(PANIC, nil, format, args...) 602 | 603 | // ensure the panic message we just logged gets processed 604 | WaitForProcessed(logNum) 605 | 606 | // flush all logs before panicking 607 | logMutex.Lock() 608 | wait := new(sync.WaitGroup) 609 | if logger == defaultLogger { 610 | flushAllLoggers(wait) 611 | } else { 612 | wait.Add(1) 613 | logger.flushBuffer(wait) 614 | } 615 | logMutex.Unlock() 616 | wait.Wait() 617 | 618 | // continue with the panic 619 | if format == "" { 620 | panic(fmt.Sprint(args...)) 621 | } else { 622 | panic(fmt.Sprintf(format, args...)) 623 | } 624 | } 625 | 626 | // Panic logs a PANIC level message with no tags and provided arguments joined into a string. 627 | func (logger *LoggerImpl) Panic(args ...interface{}) { 628 | logNum := logger.log(PANIC, nil, args...) 629 | 630 | // ensure the panic message we just logged gets processed 631 | WaitForProcessed(logNum) 632 | 633 | // flush all logs before panicking 634 | logMutex.Lock() 635 | wait := new(sync.WaitGroup) 636 | if logger == defaultLogger { 637 | flushAllLoggers(wait) 638 | } else { 639 | wait.Add(1) 640 | logger.flushBuffer(wait) 641 | } 642 | logMutex.Unlock() 643 | wait.Wait() 644 | 645 | // continue with the panic 646 | panic(fmt.Sprint(args...)) 647 | } 648 | 649 | // ErrorWithTagsf logs an ERROR level message with the provided tags and formatted string. 650 | func (logger *LoggerImpl) ErrorWithTagsf(tags []string, fmt string, args ...interface{}) { 651 | logger.logwithformat(ERROR, tags, fmt, args...) 652 | } 653 | 654 | // ErrorWithTags logs an ERROR level message with the provided tags and provided arguments joined into a string. 655 | func (logger *LoggerImpl) ErrorWithTags(tags []string, args ...interface{}) { 656 | logger.log(ERROR, tags, args...) 657 | } 658 | 659 | // Errorf logs an ERROR level message with the no tags and formatted string. 660 | func (logger *LoggerImpl) Errorf(fmt string, args ...interface{}) { 661 | logger.logwithformat(ERROR, nil, fmt, args...) 662 | } 663 | 664 | // Error logs an ERROR level message with no tags and provided arguments joined into a string. 665 | func (logger *LoggerImpl) Error(args ...interface{}) { 666 | logger.log(ERROR, nil, args...) 667 | } 668 | 669 | // WarnWithTagsf logs an WARN level message with the provided tags and formatted string. 670 | func (logger *LoggerImpl) WarnWithTagsf(tags []string, fmt string, args ...interface{}) { 671 | logger.logwithformat(WARN, tags, fmt, args...) 672 | } 673 | 674 | // WarnWithTags logs an WARN level message with the provided tags and provided arguments joined into a string. 675 | func (logger *LoggerImpl) WarnWithTags(tags []string, args ...interface{}) { 676 | logger.log(WARN, tags, args...) 677 | } 678 | 679 | // Warnf logs an WARN level message with the no tags and formatted string. 680 | func (logger *LoggerImpl) Warnf(fmt string, args ...interface{}) { 681 | logger.logwithformat(WARN, nil, fmt, args...) 682 | } 683 | 684 | // Warn logs an WARN level message with no tags and provided arguments joined into a string. 685 | func (logger *LoggerImpl) Warn(args ...interface{}) { 686 | logger.log(WARN, nil, args...) 687 | } 688 | 689 | // InfoWithTagsf logs an INFO level message with the provided tags and formatted string. 690 | func (logger *LoggerImpl) InfoWithTagsf(tags []string, fmt string, args ...interface{}) { 691 | logger.logwithformat(INFO, tags, fmt, args...) 692 | } 693 | 694 | // InfoWithTags logs an INFO level message with the provided tags and provided arguments joined into a string. 695 | func (logger *LoggerImpl) InfoWithTags(tags []string, args ...interface{}) { 696 | logger.log(INFO, tags, args...) 697 | } 698 | 699 | // Infof logs an INFO level message with the no tags and formatted string. 700 | func (logger *LoggerImpl) Infof(fmt string, args ...interface{}) { 701 | logger.logwithformat(INFO, nil, fmt, args...) 702 | } 703 | 704 | // Info logs an INFO level message with no tags and provided arguments joined into a string. 705 | func (logger *LoggerImpl) Info(args ...interface{}) { 706 | logger.log(INFO, nil, args...) 707 | } 708 | 709 | // DebugWithTagsf logs an DEBUG level message with the provided tags and formatted string. 710 | func (logger *LoggerImpl) DebugWithTagsf(tags []string, fmt string, args ...interface{}) { 711 | logger.logwithformat(DEBUG, tags, fmt, args...) 712 | } 713 | 714 | // DebugWithTags logs an DEBUG level message with the provided tags and provided arguments joined into a string. 715 | func (logger *LoggerImpl) DebugWithTags(tags []string, args ...interface{}) { 716 | logger.log(DEBUG, tags, args...) 717 | } 718 | 719 | // Debugf logs an DEBUG level message with the no tags and formatted string. 720 | func (logger *LoggerImpl) Debugf(fmt string, args ...interface{}) { 721 | logger.logwithformat(DEBUG, nil, fmt, args...) 722 | } 723 | 724 | // Debug logs an DEBUG level message with no tags and provided arguments joined into a string. 725 | func (logger *LoggerImpl) Debug(args ...interface{}) { 726 | logger.log(DEBUG, nil, args...) 727 | } 728 | 729 | // VerboseWithTagsf logs an VERBOSE level message with the provided tags and formatted string. 730 | // Verbose messages are not buffered 731 | func (logger *LoggerImpl) VerboseWithTagsf(tags []string, fmt string, args ...interface{}) { 732 | logger.logwithformat(VERBOSE, tags, fmt, args...) 733 | } 734 | 735 | // Verbosef logs an VERBOSE level message with the no tags and formatted string. 736 | // Verbose messages are not buffered 737 | func (logger *LoggerImpl) Verbosef(fmt string, args ...interface{}) { 738 | logger.logwithformat(VERBOSE, nil, fmt, args...) 739 | } 740 | 741 | // ErrorWithTagsf logs an ERROR level message with the provided tags and formatted string. Uses the default logger. 742 | func ErrorWithTagsf(tags []string, fmt string, args ...interface{}) { 743 | defaultLogger.logwithformat(ERROR, tags, fmt, args...) 744 | } 745 | 746 | // ErrorWithTags logs an ERROR level message with the provided tags and provided arguments joined into a string. Uses the default logger. 747 | func ErrorWithTags(tags []string, args ...interface{}) { 748 | defaultLogger.log(ERROR, tags, args...) 749 | } 750 | 751 | // Errorf logs an ERROR level message with the no tags and formatted string. Uses the default logger. 752 | func Errorf(fmt string, args ...interface{}) { 753 | defaultLogger.logwithformat(ERROR, nil, fmt, args...) 754 | } 755 | 756 | // Error logs an ERROR level message with no tags and provided arguments joined into a string. Uses the default logger. 757 | func Error(args ...interface{}) { 758 | defaultLogger.log(ERROR, nil, args...) 759 | } 760 | 761 | // WarnWithTagsf logs an WARN level message with the provided tags and formatted string. Uses the default logger. 762 | func WarnWithTagsf(tags []string, fmt string, args ...interface{}) { 763 | defaultLogger.logwithformat(WARN, tags, fmt, args...) 764 | } 765 | 766 | // WarnWithTags logs an WARN level message with the provided tags and provided arguments joined into a string. Uses the default logger. 767 | func WarnWithTags(tags []string, args ...interface{}) { 768 | defaultLogger.log(WARN, tags, args...) 769 | } 770 | 771 | // Warnf logs an WARN level message with the no tags and formatted string. Uses the default logger. 772 | func Warnf(fmt string, args ...interface{}) { 773 | defaultLogger.logwithformat(WARN, nil, fmt, args...) 774 | } 775 | 776 | // Warn logs an WARN level message with no tags and provided arguments joined into a string. Uses the default logger. 777 | func Warn(args ...interface{}) { 778 | defaultLogger.log(WARN, nil, args...) 779 | } 780 | 781 | // InfoWithTagsf logs an INFO level message with the provided tags and formatted string. Uses the default logger. 782 | func InfoWithTagsf(tags []string, fmt string, args ...interface{}) { 783 | defaultLogger.logwithformat(INFO, tags, fmt, args...) 784 | } 785 | 786 | // InfoWithTags logs an INFO level message with the provided tags and provided arguments joined into a string. Uses the default logger. 787 | func InfoWithTags(tags []string, args ...interface{}) { 788 | defaultLogger.log(INFO, tags, args...) 789 | } 790 | 791 | // Infof logs an INFO level message with the no tags and formatted string. Uses the default logger. 792 | func Infof(fmt string, args ...interface{}) { 793 | defaultLogger.logwithformat(INFO, nil, fmt, args...) 794 | } 795 | 796 | // Info logs an INFO level message with no tags and provided arguments joined into a string. Uses the default logger. 797 | func Info(args ...interface{}) { 798 | defaultLogger.log(INFO, nil, args...) 799 | } 800 | 801 | // DebugWithTagsf logs an DEBUG level message with the provided tags and formatted string. Uses the default logger. 802 | func DebugWithTagsf(tags []string, fmt string, args ...interface{}) { 803 | defaultLogger.logwithformat(DEBUG, tags, fmt, args...) 804 | } 805 | 806 | // DebugWithTags logs an DEBUG level message with the provided tags and provided arguments joined into a string. Uses the default logger. 807 | func DebugWithTags(tags []string, args ...interface{}) { 808 | defaultLogger.log(DEBUG, tags, args...) 809 | } 810 | 811 | // Debugf logs an DEBUG level message with the no tags and formatted string. Uses the default logger. 812 | func Debugf(fmt string, args ...interface{}) { 813 | defaultLogger.logwithformat(DEBUG, nil, fmt, args...) 814 | } 815 | 816 | // Debug logs an DEBUG level message with no tags and provided arguments joined into a string. Uses the default logger. 817 | func Debug(args ...interface{}) { 818 | defaultLogger.log(DEBUG, nil, args...) 819 | } 820 | 821 | // VerboseWithTagsf logs an VERBOSE level message with the provided tags and formatted string. Uses the default logger. 822 | // Verbose messages are not buffered 823 | func VerboseWithTagsf(tags []string, fmt string, args ...interface{}) { 824 | defaultLogger.logwithformat(VERBOSE, tags, fmt, args...) 825 | } 826 | 827 | // Verbosef logs an VERBOSE level message with the no tags and formatted string. Uses the default logger. 828 | // Verbose messages are not buffered 829 | func Verbosef(fmt string, args ...interface{}) { 830 | defaultLogger.logwithformat(VERBOSE, nil, fmt, args...) 831 | } 832 | -------------------------------------------------------------------------------- /logging_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "math/rand" 7 | "runtime" 8 | "strings" 9 | "sync" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | var count = 1 15 | 16 | func setup() (Logger, *MemoryAppender) { 17 | 18 | SetDefaultLogLevel(INFO) 19 | defaultLogger.SetBufferLength(0) 20 | memoryAppender := NewMemoryAppender() 21 | memoryAppender.SetFormatter(GetFormatter(MINIMAL)) 22 | logger := GetLogger(fmt.Sprintf("testLogger-%d", count)) 23 | count++ 24 | WaitForIncoming() 25 | ClearAppenders() 26 | AddAppender(memoryAppender) 27 | return logger, memoryAppender 28 | } 29 | 30 | func TestNamedLoggers(t *testing.T) { 31 | logger := GetLogger("named-logger") 32 | logger2 := GetLogger("named-logger") 33 | 34 | assert.True(t, logger == logger2, "named loggers should be the same") 35 | 36 | ClearLoggers() 37 | 38 | logger2 = GetLogger("named-logger") 39 | assert.False(t, logger == logger2, "named loggers should change when cleared") 40 | } 41 | 42 | func TestAddTag(t *testing.T) { 43 | t.Parallel() 44 | 45 | tags := []string{"one"} 46 | assert.Equal(t, AddTag(tags, "two"), []string{"one", "two"}, "Add tag should append to the array") 47 | } 48 | 49 | func TestClearAppenders(t *testing.T) { 50 | 51 | logger, memory := setup() 52 | logger.SetLogLevel(DEBUG) 53 | 54 | logger.Error("error") 55 | 56 | WaitForIncoming() 57 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Removed Appender should receive old messages.") 58 | 59 | secondAppender := NewMemoryAppender() 60 | ClearAppenders() 61 | AddAppender(secondAppender) 62 | 63 | logger.Error("error") 64 | 65 | WaitForIncoming() 66 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Removed Appender shouldn't receive new messages.") 67 | assert.Equal(t, len(secondAppender.GetLoggedMessages()), 1, "New Appender should only receive new messages.") 68 | } 69 | 70 | func TestLevelFilteringError(t *testing.T) { 71 | 72 | logger, memory := setup() 73 | logger.SetLogLevel(ERROR) 74 | 75 | logger.Error("error") 76 | logger.Warn("warn") 77 | logger.Info("info") 78 | logger.Debug("debug") 79 | 80 | WaitForIncoming() 81 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Only messages at level ERROR should be logged.") 82 | } 83 | 84 | func TestLevelFilteringDebug(t *testing.T) { 85 | 86 | logger, memory := setup() 87 | logger.SetLogLevel(DEBUG) 88 | 89 | logger.Error("error") 90 | logger.Warn("warn") 91 | logger.Info("info") 92 | logger.Debug("debug") 93 | logger.Verbosef("verbose") 94 | 95 | WaitForIncoming() 96 | assert.Equal(t, len(memory.GetLoggedMessages()), 4, "All messages at level DEBUG should be logged.") 97 | } 98 | 99 | func TestLevelFilteringVerbose(t *testing.T) { 100 | 101 | logger, memory := setup() 102 | logger.SetLogLevel(VERBOSE) 103 | SetDefaultLogLevel(VERBOSE) 104 | 105 | logger.Error("error") 106 | logger.Warn("warn") 107 | logger.Info("info") 108 | logger.Debug("debug") 109 | logger.Verbosef("verbose") 110 | 111 | WaitForIncoming() 112 | assert.Equal(t, len(memory.GetLoggedMessages()), 4, "All messages at level DEBUG should be logged, verbose isn't on.") 113 | 114 | EnableVerboseLogging() 115 | logger.Verbosef("verbose") 116 | logger.VerboseWithTagsf([]string{"tag"}, "verbose") 117 | Verbosef("verbose") 118 | VerboseWithTagsf([]string{"tag"}, "verbose") 119 | WaitForIncoming() 120 | assert.Equal(t, len(memory.GetLoggedMessages()), 8, "verbose is on.") 121 | 122 | DisableVerboseLogging() 123 | logger.Verbosef("verbose") 124 | logger.VerboseWithTagsf([]string{"tag"}, "verbose") 125 | Verbosef("verbose") 126 | VerboseWithTagsf([]string{"tag"}, "verbose") 127 | WaitForIncoming() 128 | assert.Equal(t, len(memory.GetLoggedMessages()), 8, "verbose is off.") 129 | } 130 | 131 | func TestTagLevelFilteringDebug(t *testing.T) { 132 | 133 | logger, memory := setup() 134 | logger.SetLogLevel(ERROR) 135 | logger.SetTagLevel("tag", DEBUG) 136 | 137 | tags := []string{"tag"} 138 | 139 | logger.ErrorWithTags(tags, "error") 140 | logger.WarnWithTags(tags, "warn") 141 | logger.InfoWithTags(tags, "info") 142 | logger.DebugWithTags(tags, "debug") 143 | 144 | logger.Error("error") 145 | logger.Warn("warn") 146 | logger.Info("info") 147 | logger.Debug("debug") 148 | 149 | WaitForIncoming() 150 | assert.Equal(t, len(memory.GetLoggedMessages()), 5, "All messages with the tag should be logged.") 151 | } 152 | 153 | func TestDefaultTagLevelFilteringDebug(t *testing.T) { 154 | 155 | _, memory := setup() 156 | SetDefaultLogLevel(ERROR) 157 | SetDefaultTagLogLevel("tag", DEBUG) 158 | 159 | tags := []string{"tag"} 160 | 161 | ErrorWithTags(tags, "error") 162 | WarnWithTags(tags, "warn") 163 | InfoWithTags(tags, "info") 164 | DebugWithTags(tags, "debug") 165 | 166 | Error("error") 167 | Warn("warn") 168 | Info("info") 169 | Debug("debug") 170 | 171 | WaitForIncoming() 172 | assert.Equal(t, len(memory.GetLoggedMessages()), 5, "All messages with the tag should be logged.") 173 | } 174 | 175 | func TestBuffer(t *testing.T) { 176 | 177 | logger, memory := setup() 178 | logger.SetLogLevel(ERROR) 179 | logger.SetBufferLength(10) 180 | 181 | logger.Error("error") 182 | logger.Warn("warn") 183 | logger.Info("info") 184 | logger.Debug("debug") 185 | 186 | WaitForIncoming() 187 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Only messages at level ERROR should be logged.") 188 | 189 | logger.SetLogLevel(WARN) 190 | 191 | WaitForIncoming() 192 | assert.Equal(t, len(memory.GetLoggedMessages()), 2, "Buffered messages should be logged after level change.") 193 | 194 | logger.SetLogLevel(DEBUG) 195 | 196 | WaitForIncoming() 197 | assert.Equal(t, len(memory.GetLoggedMessages()), 4, "Buffered messages should have been saved, then logged after level change.") 198 | } 199 | 200 | func TestBufferLength(t *testing.T) { 201 | 202 | logger, memory := setup() 203 | logger.SetLogLevel(ERROR) 204 | logger.SetBufferLength(2) 205 | 206 | logger.Error("error") 207 | logger.Warn("warn") 208 | logger.Info("info") 209 | logger.Debug("debug") 210 | 211 | WaitForIncoming() 212 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Only messages at level ERROR should be logged.") 213 | 214 | logger.SetLogLevel(DEBUG) 215 | 216 | WaitForIncoming() 217 | assert.Equal(t, len(memory.GetLoggedMessages()), 3, "Buffer should have only had 2 messages.") 218 | } 219 | 220 | func TestFormatMethods(t *testing.T) { 221 | logger, memory := setup() 222 | logger.SetLogLevel(DEBUG) 223 | 224 | logger.Error("one") 225 | logger.Errorf("%v %v", "one", 1) 226 | 227 | logger.Warn("one") 228 | logger.Warnf("%v %v", "one", 1) 229 | 230 | logger.Info("one") 231 | logger.Infof("%v %v", "one", 1) 232 | 233 | logger.Debug("one") 234 | logger.Debugf("%v %v", "one", 1) 235 | 236 | WaitForIncoming() 237 | assert.Equal(t, memory.GetLoggedMessages()[0], "one", "unformatted messages should be unchanged") 238 | assert.Equal(t, memory.GetLoggedMessages()[1], "one 1", "messages should be formatted") 239 | assert.Equal(t, memory.GetLoggedMessages()[2], "one", "unformatted messages should be unchanged") 240 | assert.Equal(t, memory.GetLoggedMessages()[3], "one 1", "messages should be formatted") 241 | assert.Equal(t, memory.GetLoggedMessages()[4], "one", "unformatted messages should be unchanged") 242 | assert.Equal(t, memory.GetLoggedMessages()[5], "one 1", "messages should be formatted") 243 | assert.Equal(t, memory.GetLoggedMessages()[6], "one", "unformatted messages should be unchanged") 244 | assert.Equal(t, memory.GetLoggedMessages()[7], "one 1", "messages should be formatted") 245 | } 246 | 247 | func TestFormatMethodsWithTags(t *testing.T) { 248 | logger, memory := setup() 249 | memory.SetFormatter(GetFormatter(MINIMALTAGGED)) 250 | logger.SetLogLevel(DEBUG) 251 | 252 | tags := []string{"blit", "blat"} 253 | 254 | logger.ErrorWithTags(tags, "one") 255 | logger.ErrorWithTagsf(tags, "%v %v", "one", 1) 256 | 257 | logger.WarnWithTags(tags, "one") 258 | logger.WarnWithTagsf(tags, "%v %v", "one", 1) 259 | 260 | logger.InfoWithTags(tags, "one") 261 | logger.InfoWithTagsf(tags, "%v %v", "one", 1) 262 | 263 | logger.DebugWithTags(tags, "one") 264 | logger.DebugWithTagsf(tags, "%v %v", "one", 1) 265 | 266 | WaitForIncoming() 267 | assert.Equal(t, memory.GetLoggedMessages()[0], "[ERROR] [blit blat] one", "unformatted messages should be unchanged") 268 | assert.Equal(t, memory.GetLoggedMessages()[1], "[ERROR] [blit blat] one 1", "messages should be formatted") 269 | assert.Equal(t, memory.GetLoggedMessages()[2], "[WARN] [blit blat] one", "unformatted messages should be unchanged") 270 | assert.Equal(t, memory.GetLoggedMessages()[3], "[WARN] [blit blat] one 1", "messages should be formatted") 271 | assert.Equal(t, memory.GetLoggedMessages()[4], "[INFO] [blit blat] one", "unformatted messages should be unchanged") 272 | assert.Equal(t, memory.GetLoggedMessages()[5], "[INFO] [blit blat] one 1", "messages should be formatted") 273 | assert.Equal(t, memory.GetLoggedMessages()[6], "[DEBUG] [blit blat] one", "unformatted messages should be unchanged") 274 | assert.Equal(t, memory.GetLoggedMessages()[7], "[DEBUG] [blit blat] one 1", "messages should be formatted") 275 | } 276 | 277 | func TestPanicLogging(t *testing.T) { 278 | logger, memory := setup() 279 | memory.SetFormatter(GetFormatter(MINIMALTAGGED)) 280 | logger.SetLogLevel(DEBUG) 281 | 282 | tags := []string{"blit", "blat"} 283 | 284 | defer func() { 285 | rec := recover() 286 | assert.NotNil(t, rec, "panic message") 287 | assert.Equal(t, "panic", rec, "panic message") 288 | 289 | assert.Equal(t, memory.GetLoggedMessages()[0], "[ERROR] [blit blat] one", "unformatted messages should be unchanged") 290 | assert.Equal(t, memory.GetLoggedMessages()[1], "[ERROR] [blit blat] one 1", "messages should be formatted") 291 | assert.Equal(t, memory.GetLoggedMessages()[2], "[WARN] [blit blat] one", "unformatted messages should be unchanged") 292 | assert.Equal(t, memory.GetLoggedMessages()[3], "[WARN] [blit blat] one 1", "messages should be formatted") 293 | assert.Equal(t, memory.GetLoggedMessages()[4], "[INFO] [blit blat] one", "unformatted messages should be unchanged") 294 | assert.Equal(t, memory.GetLoggedMessages()[5], "[INFO] [blit blat] one 1", "messages should be formatted") 295 | assert.Equal(t, memory.GetLoggedMessages()[6], "[DEBUG] [blit blat] one", "unformatted messages should be unchanged") 296 | assert.Equal(t, memory.GetLoggedMessages()[7], "[DEBUG] [blit blat] one 1", "messages should be formatted") 297 | 298 | msgWithStack := strings.Split(memory.GetLoggedMessages()[8], "\n") 299 | 300 | assert.Equal(t, msgWithStack[0], "[PANIC] [blit blat] panic", "unformatted messages should be unchanged") 301 | assert.True(t, len(msgWithStack) > 1, "panic messages should include stack trace") 302 | for i := 1; i < len(msgWithStack); i++ { 303 | assert.Equal(t, " ", msgWithStack[i][0:2], "every stack trace line should be indented two spaces") 304 | } 305 | 306 | }() 307 | 308 | logger.ErrorWithTags(tags, "one") 309 | logger.ErrorWithTagsf(tags, "%v %v", "one", 1) 310 | 311 | logger.WarnWithTags(tags, "one") 312 | logger.WarnWithTagsf(tags, "%v %v", "one", 1) 313 | 314 | logger.InfoWithTags(tags, "one") 315 | logger.InfoWithTagsf(tags, "%v %v", "one", 1) 316 | 317 | logger.DebugWithTags(tags, "one") 318 | logger.DebugWithTagsf(tags, "%v %v", "one", 1) 319 | 320 | logger.PanicWithTags(tags, "panic") 321 | } 322 | 323 | func TestPanicFormatLogging(t *testing.T) { 324 | logger, memory := setup() 325 | memory.SetFormatter(GetFormatter(MINIMALTAGGED)) 326 | logger.SetLogLevel(DEBUG) 327 | 328 | tags := []string{"blit", "blat"} 329 | 330 | defer func() { 331 | 332 | rec := recover() 333 | assert.NotNil(t, rec, "panic message") 334 | assert.Equal(t, "panic 1", rec, "panic message") 335 | 336 | assert.Equal(t, memory.GetLoggedMessages()[0], "[ERROR] [blit blat] one", "unformatted messages should be unchanged") 337 | assert.Equal(t, memory.GetLoggedMessages()[1], "[ERROR] [blit blat] one 1", "messages should be formatted") 338 | assert.Equal(t, memory.GetLoggedMessages()[2], "[WARN] [blit blat] one", "unformatted messages should be unchanged") 339 | assert.Equal(t, memory.GetLoggedMessages()[3], "[WARN] [blit blat] one 1", "messages should be formatted") 340 | assert.Equal(t, memory.GetLoggedMessages()[4], "[INFO] [blit blat] one", "unformatted messages should be unchanged") 341 | assert.Equal(t, memory.GetLoggedMessages()[5], "[INFO] [blit blat] one 1", "messages should be formatted") 342 | assert.Equal(t, memory.GetLoggedMessages()[6], "[DEBUG] [blit blat] one", "unformatted messages should be unchanged") 343 | assert.Equal(t, memory.GetLoggedMessages()[7], "[DEBUG] [blit blat] one 1", "messages should be formatted") 344 | 345 | msgWithStack := strings.Split(memory.GetLoggedMessages()[8], "\n") 346 | 347 | assert.Equal(t, msgWithStack[0], "[PANIC] [blit blat] panic 1", "messages should be formatted") 348 | assert.True(t, len(msgWithStack) > 1, "panic messages should include stack trace") 349 | for i := 1; i < len(msgWithStack); i++ { 350 | assert.Equal(t, " ", msgWithStack[i][0:2], "every stack trace line should be indented two spaces") 351 | } 352 | }() 353 | 354 | logger.ErrorWithTags(tags, "one") 355 | logger.ErrorWithTagsf(tags, "%v %v", "one", 1) 356 | 357 | logger.WarnWithTags(tags, "one") 358 | logger.WarnWithTagsf(tags, "%v %v", "one", 1) 359 | 360 | logger.InfoWithTags(tags, "one") 361 | logger.InfoWithTagsf(tags, "%v %v", "one", 1) 362 | 363 | logger.DebugWithTags(tags, "one") 364 | logger.DebugWithTagsf(tags, "%v %v", "one", 1) 365 | 366 | logger.PanicWithTagsf(tags, "%v %v", "panic", 1) 367 | } 368 | 369 | func TestFormatMethodsWithDefaultLogger(t *testing.T) { 370 | _, memory := setup() 371 | SetDefaultLogLevel(DEBUG) 372 | SetDefaultFormatter(GetFormatter(MINIMAL)) 373 | 374 | Error("one") 375 | Errorf("%v %v", "one", 1) 376 | 377 | Warn("one") 378 | Warnf("%v %v", "one", 1) 379 | 380 | Info("one") 381 | Infof("%v %v", "one", 1) 382 | 383 | Debug("one") 384 | Debugf("%v %v", "one", 1) 385 | 386 | WaitForIncoming() 387 | assert.Equal(t, memory.GetLoggedMessages()[0], "one", "unformatted messages should be unchanged") 388 | assert.Equal(t, memory.GetLoggedMessages()[1], "one 1", "messages should be formatted") 389 | assert.Equal(t, memory.GetLoggedMessages()[2], "one", "unformatted messages should be unchanged") 390 | assert.Equal(t, memory.GetLoggedMessages()[3], "one 1", "messages should be formatted") 391 | assert.Equal(t, memory.GetLoggedMessages()[4], "one", "unformatted messages should be unchanged") 392 | assert.Equal(t, memory.GetLoggedMessages()[5], "one 1", "messages should be formatted") 393 | assert.Equal(t, memory.GetLoggedMessages()[6], "one", "unformatted messages should be unchanged") 394 | assert.Equal(t, memory.GetLoggedMessages()[7], "one 1", "messages should be formatted") 395 | } 396 | 397 | func TestFormatMethodsWithTagsAndDefaultLogger(t *testing.T) { 398 | _, memory := setup() 399 | SetDefaultFormatter(GetFormatter(MINIMALTAGGED)) 400 | SetDefaultLogLevel(DEBUG) 401 | 402 | memory = NewMemoryAppender() //will get the default formatter 403 | ClearAppenders() 404 | AddAppender(memory) 405 | 406 | tags := []string{"blit", "blat"} 407 | 408 | ErrorWithTags(tags, "one") 409 | ErrorWithTagsf(tags, "%v %v", "one", 1) 410 | 411 | WarnWithTags(tags, "one") 412 | WarnWithTagsf(tags, "%v %v", "one", 1) 413 | 414 | InfoWithTags(tags, "one") 415 | InfoWithTagsf(tags, "%v %v", "one", 1) 416 | 417 | DebugWithTags(tags, "one") 418 | DebugWithTagsf(tags, "%v %v", "one", 1) 419 | 420 | WaitForIncoming() 421 | assert.Equal(t, memory.GetLoggedMessages()[0], "[ERROR] [blit blat] one", "unformatted messages should be unchanged") 422 | assert.Equal(t, memory.GetLoggedMessages()[1], "[ERROR] [blit blat] one 1", "messages should be formatted") 423 | assert.Equal(t, memory.GetLoggedMessages()[2], "[WARN] [blit blat] one", "unformatted messages should be unchanged") 424 | assert.Equal(t, memory.GetLoggedMessages()[3], "[WARN] [blit blat] one 1", "messages should be formatted") 425 | assert.Equal(t, memory.GetLoggedMessages()[4], "[INFO] [blit blat] one", "unformatted messages should be unchanged") 426 | assert.Equal(t, memory.GetLoggedMessages()[5], "[INFO] [blit blat] one 1", "messages should be formatted") 427 | assert.Equal(t, memory.GetLoggedMessages()[6], "[DEBUG] [blit blat] one", "unformatted messages should be unchanged") 428 | assert.Equal(t, memory.GetLoggedMessages()[7], "[DEBUG] [blit blat] one 1", "messages should be formatted") 429 | } 430 | 431 | func TestDefaultLogger(t *testing.T) { 432 | 433 | memory := NewMemoryAppender() 434 | ClearAppenders() 435 | AddAppender(memory) 436 | 437 | SetDefaultLogLevel(ERROR) 438 | SetDefaultBufferLength(10) 439 | 440 | Error("error") 441 | Warn("warn") 442 | Info("info") 443 | Debug("debug") 444 | 445 | WaitForIncoming() 446 | assert.Equal(t, len(memory.GetLoggedMessages()), 1, "Only messages at level ERROR should be logged.") 447 | 448 | SetDefaultLogLevel(WARN) 449 | 450 | WaitForIncoming() 451 | assert.Equal(t, len(memory.GetLoggedMessages()), 2, "Buffered messages should be logged after level change.") 452 | 453 | SetDefaultLogLevel(DEBUG) 454 | 455 | WaitForIncoming() 456 | assert.Equal(t, len(memory.GetLoggedMessages()), 4, "Buffered messages should have been saved, then logged after level change.") 457 | } 458 | 459 | func TestWaitForIncoming(t *testing.T) { 460 | count := 1000 461 | memory := NewMemoryAppender() 462 | ClearAppenders() 463 | AddAppender(memory) 464 | 465 | SetDefaultLogLevel(DEBUG) 466 | 467 | for i := 0; i < count; i++ { 468 | Error("error") 469 | } 470 | 471 | WaitForIncoming() 472 | assert.Equal(t, len(memory.GetLoggedMessages()), count, "All messages should be logged.") 473 | } 474 | 475 | func TestConcurrentLogging(t *testing.T) { 476 | runtime.GOMAXPROCS(8) 477 | count := 1000 478 | concur := 50 479 | memory := NewNullAppender() 480 | ClearAppenders() 481 | AddAppender(memory) 482 | 483 | SetDefaultLogLevel(DEBUG) 484 | 485 | waiter := sync.WaitGroup{} 486 | waiter.Add(concur) 487 | 488 | for gos := 0; gos < concur; gos++ { 489 | go func() { 490 | pauseAt := int(rand.Int31n(int32(count))) 491 | for i := 0; i < count; i++ { 492 | if pauseAt == i { 493 | PauseLogging() 494 | time.Sleep(1 * time.Millisecond) 495 | RestartLogging() 496 | } 497 | Error("error") 498 | time.Sleep(1 * time.Microsecond) 499 | } 500 | waiter.Done() 501 | }() 502 | } 503 | 504 | waiter.Wait() 505 | WaitForIncoming() 506 | assert.Equal(t, memory.Count(), int64(count*concur), "All messages should be logged.") 507 | } 508 | 509 | func TestStopStartLogging(t *testing.T) { 510 | memory := NewNullAppender() 511 | ClearAppenders() 512 | AddAppender(memory) 513 | 514 | SetDefaultLogLevel(DEBUG) 515 | 516 | Error("error") 517 | 518 | WaitForIncoming() 519 | assert.Equal(t, memory.Count(), int64(1), "Only messages at level ERROR should be logged.") 520 | 521 | PauseLogging() 522 | 523 | Error("error") 524 | assert.Equal(t, memory.Count(), int64(1), "Only messages at level ERROR should be logged.") 525 | 526 | RestartLogging() 527 | 528 | Error("error") 529 | WaitForIncoming() 530 | assert.Equal(t, memory.Count(), int64(3), "Only messages at level ERROR should be logged.") 531 | } 532 | 533 | func TestConfigWhileStoppedLogging(t *testing.T) { 534 | memory := NewMemoryAppender() 535 | ClearAppenders() 536 | AddAppender(memory) 537 | 538 | SetDefaultLogLevel(DEBUG) 539 | 540 | Error("error") 541 | 542 | WaitForIncoming() 543 | assert.Equal(t, len(memory.GetLoggedMessages()), int(1), "Only messages at level ERROR should be logged.") 544 | 545 | PauseLogging() 546 | 547 | memory2 := NewMemoryAppender() 548 | ClearAppenders() 549 | AddAppender(memory2) 550 | 551 | RestartLogging() 552 | 553 | Error("error") 554 | 555 | WaitForIncoming() 556 | assert.Equal(t, len(memory.GetLoggedMessages()), int(1), "Only old messages should be in the old log.") 557 | assert.Equal(t, len(memory2.GetLoggedMessages()), int(1), "Only new messages should be in the new log.") 558 | } 559 | 560 | func TestErrorChannel(t *testing.T) { 561 | 562 | errors := make(chan error, 10) 563 | logger, _ := setup() 564 | logger.SetLogLevel(DEBUG) 565 | 566 | errorApp := NewErrorAppender() 567 | ClearAppenders() 568 | AddAppender(errorApp) 569 | 570 | CaptureLoggingErrors(errors) 571 | 572 | logger.Error("error") 573 | logger.Warn("warn") 574 | logger.Info("info") 575 | logger.Debug("debug") 576 | 577 | err := <-errors 578 | assert.Equal(t, err.Error(), "error: error", "errors should be pushed to the channel in order.") 579 | err = <-errors 580 | assert.Equal(t, err.Error(), "error: warn", "errors should be pushed to the channel in order.") 581 | err = <-errors 582 | assert.Equal(t, err.Error(), "error: info", "errors should be pushed to the channel in order.") 583 | err = <-errors 584 | assert.Equal(t, err.Error(), "error: debug", "errors should be pushed to the channel in order.") 585 | 586 | WaitForIncoming() 587 | assert.Equal(t, errorApp.Count(), int64(4), "All messages should be logged.") 588 | } 589 | 590 | func TestErrorChannelWontBlock(t *testing.T) { 591 | 592 | errors := make(chan error) 593 | logger, _ := setup() 594 | logger.SetLogLevel(DEBUG) 595 | 596 | errorApp := NewErrorAppender() 597 | ClearAppenders() 598 | AddAppender(errorApp) 599 | 600 | CaptureLoggingErrors(errors) 601 | 602 | logger.Error("error") 603 | logger.Warn("warn") 604 | logger.Info("info") 605 | logger.Debug("debug") 606 | 607 | select { 608 | case err := <-errors: 609 | assert.Nil(t, err, "Errors should be empty since we don't block") 610 | default: 611 | //ok 612 | } 613 | 614 | WaitForIncoming() 615 | assert.Equal(t, errorApp.Count(), int64(4), "All messages should be logged.") 616 | } 617 | -------------------------------------------------------------------------------- /loglevel.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "strings" 4 | 5 | // LogLevel is the type used to indicate the importance of a logging request. 6 | type LogLevel uint8 7 | 8 | const ( 9 | // DEFAULT is the default log level, loggers with default level will use the default loggers level. 10 | DEFAULT LogLevel = iota 11 | // VERBOSE is the wordiest log level, useful for very big text output that may 12 | // be the last result during testing or debugging. 13 | VERBOSE 14 | // DEBUG is generally the lowest level used when testing. 15 | DEBUG 16 | // INFO is used for generally helpful but not important messages. 17 | INFO 18 | // WARN is provided for warnings that do not represent a major program error. 19 | WARN 20 | // ERROR is should only be used for exceptional conditions. 21 | ERROR 22 | // The highest log level only to be used when logging a panic. 23 | PANIC 24 | ) 25 | 26 | // String converts a log level to an upper case string. 27 | func (level LogLevel) String() string { 28 | switch { 29 | case level >= PANIC: 30 | return "PANIC" 31 | case level >= ERROR: 32 | return "ERROR" 33 | case level >= WARN: 34 | return "WARN" 35 | case level >= INFO: 36 | return "INFO" 37 | case level >= DEBUG: 38 | return "DEBUG" 39 | default: 40 | return "VERBOSE" 41 | } 42 | } 43 | 44 | 45 | // LevelFromString converts a level in any case to a LogLevel, valid values are 46 | // error, warning, warn, info, informative, debug and verbose. 47 | func LevelFromString(str string) LogLevel { 48 | str = strings.ToLower(str) 49 | 50 | switch str { 51 | case "panic": 52 | return PANIC 53 | case "error": 54 | return ERROR 55 | case "warning", "warn": 56 | return WARN 57 | case "info", "informative": 58 | return INFO 59 | case "debug": 60 | return DEBUG 61 | case "verbose": 62 | return VERBOSE 63 | default: 64 | return DEFAULT 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /loglevel_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestLevelToString(t *testing.T) { 9 | 10 | assert.Equal(t, DEBUG.String(), "DEBUG", "DEBUG.String() = %v, want %v", DEBUG, "DEBUG") 11 | assert.Equal(t, INFO.String(), "INFO", "INFO.String() = %v, want %v", INFO, "INFO") 12 | assert.Equal(t, WARN.String(), "WARN", "WARN.String() = %v, want %v", WARN, "WARN") 13 | assert.Equal(t, ERROR.String(), "ERROR", "ERROR.String() = %v, want %v", ERROR, "ERROR") 14 | assert.Equal(t, PANIC.String(), "PANIC", "PANIC.String() = %v, want %v", PANIC, "PANIC") 15 | assert.Equal(t, VERBOSE.String(), "VERBOSE", "VERBOSE.String() = %v, want %v", VERBOSE, "VERBOSE") 16 | assert.Equal(t, LogLevel(0).String(), "VERBOSE", "VERBOSE.String() = %v, want %v", VERBOSE, "VERBOSE") 17 | } 18 | 19 | func TestFromString(t *testing.T) { 20 | 21 | levelStrings := []string{"debug", "Debug", "warn", "Warning", "error", "Panic", "INFO", "Informative", "verBose", "none"} 22 | levels := []LogLevel{DEBUG, DEBUG, WARN, WARN, ERROR, PANIC, INFO, INFO, VERBOSE, DEFAULT} 23 | 24 | for i, levelString := range levelStrings { 25 | level := levels[i] 26 | assert.Equal(t, LevelFromString(levelString), level, "%v = %v, want %v", levelString, LevelFromString(levelString), level) 27 | } 28 | } 29 | 30 | func TestCheckLevel(t *testing.T) { 31 | 32 | logger := DefaultLogger() 33 | 34 | SetDefaultLogLevel(ERROR) 35 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Error") 36 | assert.False(t, logger.CheckLevel(WARN, nil), "Warning should not be valid when level set to Error") 37 | assert.False(t, logger.CheckLevel(INFO, nil), "Info should not be valid when level set to Error") 38 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Error") 39 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Error") 40 | 41 | SetDefaultLogLevel(WARN) 42 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Warn") 43 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Warn") 44 | assert.False(t, logger.CheckLevel(INFO, nil), "Info should not be valid when level set to Warn") 45 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Warn") 46 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Warn") 47 | 48 | SetDefaultLogLevel(INFO) 49 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Info") 50 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Info") 51 | assert.True(t, logger.CheckLevel(INFO, nil), "Info should be valid when level set to Info") 52 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Info") 53 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Info") 54 | 55 | SetDefaultLogLevel(DEBUG) 56 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Debug") 57 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Debug") 58 | assert.True(t, logger.CheckLevel(INFO, nil), "Info should be valid when level set to Debug") 59 | assert.True(t, logger.CheckLevel(DEBUG, nil), "Debug should be valid when level set to Debug") 60 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Debug") 61 | 62 | SetDefaultLogLevel(VERBOSE) 63 | assert.True(t, logger.CheckLevel(VERBOSE, nil), "Verbose should be valid when level set to Verbose") 64 | } 65 | 66 | func TestCheckTagLevel(t *testing.T) { 67 | 68 | logger := DefaultLogger() 69 | SetDefaultLogLevel(ERROR) 70 | 71 | tags := []string{"tag"} 72 | SetDefaultTagLogLevel("tag", ERROR) 73 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Error") 74 | assert.False(t, logger.CheckLevel(WARN, tags), "Warning should not be valid when tag level set to Error") 75 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Error") 76 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Error") 77 | 78 | SetDefaultTagLogLevel("tag", WARN) 79 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Warn") 80 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Warn") 81 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Warn") 82 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Warn") 83 | 84 | SetDefaultTagLogLevel("tag", INFO) 85 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Info") 86 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Info") 87 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Info") 88 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Info") 89 | 90 | SetDefaultTagLogLevel("tag", DEBUG) 91 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Debug") 92 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Debug") 93 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Debug") 94 | assert.True(t, logger.CheckLevel(DEBUG, tags), "Debug should be valid when tag level set to Debug") 95 | } 96 | 97 | func TestCheckLevelDefault(t *testing.T) { 98 | 99 | SetDefaultLogLevel(ERROR) 100 | assert.True(t, CheckLevel(ERROR, nil), "Error should be valid when level set to Error") 101 | assert.False(t, CheckLevel(WARN, nil), "Warning should not be valid when level set to Error") 102 | assert.False(t, CheckLevel(INFO, nil), "Info should not be valid when level set to Error") 103 | assert.False(t, CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Error") 104 | assert.False(t, CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Error") 105 | 106 | SetDefaultLogLevel(WARN) 107 | assert.True(t, CheckLevel(ERROR, nil), "Error should be valid when level set to Warn") 108 | assert.True(t, CheckLevel(WARN, nil), "Warning should be valid when level set to Warn") 109 | assert.False(t, CheckLevel(INFO, nil), "Info should not be valid when level set to Warn") 110 | assert.False(t, CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Warn") 111 | assert.False(t, CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Warn") 112 | } 113 | 114 | func TestCheckInstanceLevel(t *testing.T) { 115 | 116 | logger := GetLogger("test") 117 | 118 | SetDefaultLogLevel(ERROR) 119 | 120 | logger.SetLogLevel(ERROR) 121 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Error") 122 | assert.False(t, logger.CheckLevel(WARN, nil), "Warning should not be valid when level set to Error") 123 | assert.False(t, logger.CheckLevel(INFO, nil), "Info should not be valid when level set to Error") 124 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Error") 125 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Error") 126 | 127 | logger.SetLogLevel(WARN) 128 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Warn") 129 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Warn") 130 | assert.False(t, logger.CheckLevel(INFO, nil), "Info should not be valid when level set to Warn") 131 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Warn") 132 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Warn") 133 | 134 | logger.SetLogLevel(INFO) 135 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Info") 136 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Info") 137 | assert.True(t, logger.CheckLevel(INFO, nil), "Info should be valid when level set to Info") 138 | assert.False(t, logger.CheckLevel(DEBUG, nil), "Debug should not be valid when level set to Info") 139 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Info") 140 | 141 | logger.SetLogLevel(DEBUG) 142 | assert.True(t, logger.CheckLevel(ERROR, nil), "Error should be valid when level set to Debug") 143 | assert.True(t, logger.CheckLevel(WARN, nil), "Warning should be valid when level set to Debug") 144 | assert.True(t, logger.CheckLevel(INFO, nil), "Info should be valid when level set to Debug") 145 | assert.True(t, logger.CheckLevel(DEBUG, nil), "Debug should be valid when level set to Debug") 146 | assert.False(t, logger.CheckLevel(VERBOSE, nil), "Verbose should not be valid when level set to Debug") 147 | 148 | logger.SetLogLevel(VERBOSE) 149 | assert.True(t, logger.CheckLevel(VERBOSE, nil), "Verbose should be valid when level set to Verbose") 150 | } 151 | 152 | func TestCheckInstanceTagLevel(t *testing.T) { 153 | 154 | logger := GetLogger("test2") 155 | 156 | SetDefaultLogLevel(ERROR) 157 | logger.SetLogLevel(ERROR) 158 | SetDefaultTagLogLevel("tag2", ERROR) 159 | 160 | tags := []string{"tag2"} 161 | logger.SetTagLevel("tag2", ERROR) 162 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Error") 163 | assert.False(t, logger.CheckLevel(WARN, tags), "Warning should not be valid when tag level set to Error") 164 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Error") 165 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Error") 166 | assert.False(t, logger.CheckLevel(VERBOSE, tags), "Verbose should not be valid when level set to Error") 167 | 168 | logger.SetTagLevel("tag2", WARN) 169 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Warn") 170 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Warn") 171 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Warn") 172 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Warn") 173 | assert.False(t, logger.CheckLevel(VERBOSE, tags), "Verbose should not be valid when level set to Warn") 174 | 175 | logger.SetTagLevel("tag2", INFO) 176 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Info") 177 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Info") 178 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Info") 179 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Info") 180 | assert.False(t, logger.CheckLevel(VERBOSE, tags), "Verbose should not be valid when level set to Info") 181 | 182 | logger.SetTagLevel("tag2", DEBUG) 183 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Debug") 184 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Debug") 185 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Debug") 186 | assert.True(t, logger.CheckLevel(DEBUG, tags), "Debug should be valid when tag level set to Debug") 187 | assert.False(t, logger.CheckLevel(VERBOSE, tags), "Verbose should not be valid when level set to Debug") 188 | 189 | logger.SetTagLevel("tag2", VERBOSE) 190 | assert.True(t, logger.CheckLevel(VERBOSE, tags), "Verbose should be valid when level set to Verbose") 191 | } 192 | 193 | func TestCheckInstanceTagLevelMany(t *testing.T) { 194 | 195 | logger := GetLogger("test2") 196 | 197 | SetDefaultLogLevel(ERROR) 198 | logger.SetLogLevel(ERROR) 199 | SetDefaultTagLogLevel("tag2", ERROR) 200 | 201 | tags := []string{"tag2", "tag3", "tag4", "tag5", "tag6", "tag7"} 202 | logger.SetTagLevel("tag2", ERROR) 203 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Error") 204 | assert.False(t, logger.CheckLevel(WARN, tags), "Warning should not be valid when tag level set to Error") 205 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Error") 206 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Error") 207 | 208 | logger.SetTagLevel("tag2", WARN) 209 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Warn") 210 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Warn") 211 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Warn") 212 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Warn") 213 | 214 | logger.SetTagLevel("tag2", INFO) 215 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Info") 216 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Info") 217 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Info") 218 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Info") 219 | 220 | logger.SetTagLevel("tag2", DEBUG) 221 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Debug") 222 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Debug") 223 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should be valid when tag level set to Debug") 224 | assert.True(t, logger.CheckLevel(DEBUG, tags), "Debug should be valid when tag level set to Debug") 225 | } 226 | 227 | func TestCheckInstanceTagLevelVsOther(t *testing.T) { 228 | 229 | logger := GetLogger("test3") 230 | 231 | SetDefaultLogLevel(ERROR) 232 | logger.SetLogLevel(ERROR) 233 | SetDefaultTagLogLevel("tag3", ERROR) 234 | 235 | GetLogger("test4").SetTagLevel("tag3", DEBUG) //Check for tag level leaking 236 | 237 | tags := []string{"tag3"} 238 | 239 | logger.SetTagLevel("tag3", WARN) 240 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when tag level set to Warn") 241 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should be valid when tag level set to Warn") 242 | assert.False(t, logger.CheckLevel(INFO, tags), "Info should not be valid when tag level set to Warn") 243 | assert.False(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when tag level set to Warn") 244 | } 245 | 246 | func TestDefaultLevelOveridesTagLevel(t *testing.T) { 247 | 248 | logger := GetLogger("test5") 249 | SetDefaultLogLevel(DEBUG) 250 | 251 | tags := []string{"tag5"} 252 | SetDefaultTagLogLevel("tag5", ERROR) 253 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when default level set to Debug") 254 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should not be valid when default level set to Debug") 255 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should not be valid when default level set to Debug") 256 | assert.True(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when default level set to Debug") 257 | } 258 | 259 | func TestLevelOveridesTagLevel(t *testing.T) { 260 | 261 | logger := GetLogger("test5") 262 | SetDefaultLogLevel(ERROR) 263 | logger.SetLogLevel(DEBUG) 264 | 265 | tags := []string{"tag6"} 266 | SetDefaultTagLogLevel("tag6", ERROR) 267 | assert.True(t, logger.CheckLevel(ERROR, tags), "Error should be valid when level set to Debug") 268 | assert.True(t, logger.CheckLevel(WARN, tags), "Warning should not be valid when level set to Debug") 269 | assert.True(t, logger.CheckLevel(INFO, tags), "Info should not be valid when level set to Debug") 270 | assert.True(t, logger.CheckLevel(DEBUG, tags), "Debug should not be valid when level set to Debug") 271 | } 272 | 273 | func BenchmarkCheckPassingLogLevel(b *testing.B) { 274 | logger := GetLogger("BenchmarkCheckPassingLogLevel") 275 | SetDefaultLogLevel(ERROR) 276 | logger.SetLogLevel(ERROR) 277 | for n := 0; n < b.N; n++ { 278 | logger.CheckLevel(ERROR, nil) 279 | } 280 | } 281 | 282 | func BenchmarkCheckFailingLogLevel(b *testing.B) { 283 | logger := GetLogger("BenchmarkCheckFailingLogLevel") 284 | SetDefaultLogLevel(ERROR) 285 | logger.SetLogLevel(ERROR) 286 | for n := 0; n < b.N; n++ { 287 | logger.CheckLevel(WARN, nil) 288 | } 289 | } 290 | 291 | func BenchmarkCheckPassingTagLevel(b *testing.B) { 292 | logger := GetLogger("BenchmarkCheckPassingTagLevel") 293 | SetDefaultLogLevel(ERROR) 294 | logger.SetLogLevel(ERROR) 295 | tags := []string{"tag2"} 296 | logger.SetTagLevel("tag2", ERROR) 297 | for n := 0; n < b.N; n++ { 298 | logger.CheckLevel(ERROR, tags) 299 | } 300 | } 301 | 302 | func BenchmarkCheckFailingTagLevel(b *testing.B) { 303 | logger := GetLogger("BenchmarkCheckFailingTagLevel") 304 | SetDefaultLogLevel(ERROR) 305 | logger.SetLogLevel(ERROR) 306 | tags := []string{"tag2"} 307 | logger.SetTagLevel("tag2", ERROR) 308 | for n := 0; n < b.N; n++ { 309 | logger.CheckLevel(WARN, tags) 310 | } 311 | } 312 | 313 | func BenchmarkCheckPassingTagLevelThree(b *testing.B) { 314 | logger := GetLogger("BenchmarkCheckPassingTagLevel3") 315 | SetDefaultLogLevel(ERROR) 316 | logger.SetLogLevel(ERROR) 317 | tags := []string{"alpha", "beta", "phi"} 318 | logger.SetTagLevel("alpha", ERROR) 319 | SetDefaultTagLogLevel("beta", ERROR) 320 | for n := 0; n < b.N; n++ { 321 | logger.CheckLevel(ERROR, tags) 322 | } 323 | } 324 | 325 | func BenchmarkCheckPassingTagLevelThreeUnsorted(b *testing.B) { 326 | logger := GetLogger("BenchmarkCheckPassingTagLevel3u") 327 | SetDefaultLogLevel(ERROR) 328 | logger.SetLogLevel(ERROR) 329 | tags := []string{"phi", "beta", "alpha"} 330 | logger.SetTagLevel("alpha", ERROR) 331 | SetDefaultTagLogLevel("beta", ERROR) 332 | for n := 0; n < b.N; n++ { 333 | logger.CheckLevel(ERROR, tags) 334 | } 335 | } 336 | 337 | func BenchmarkCheckFailingTagLevelThree(b *testing.B) { 338 | logger := GetLogger("BenchmarkCheckFailingTagLevel3") 339 | SetDefaultLogLevel(ERROR) 340 | logger.SetLogLevel(ERROR) 341 | tags := []string{"alpha", "beta", "phi"} 342 | logger.SetTagLevel("alpha", ERROR) 343 | SetDefaultTagLogLevel("beta", ERROR) 344 | for n := 0; n < b.N; n++ { 345 | logger.CheckLevel(WARN, tags) 346 | } 347 | } 348 | 349 | func BenchmarkCheckPassingTagLevelNine(b *testing.B) { 350 | logger := GetLogger("BenchmarkCheckPassingTagLevel9") 351 | SetDefaultLogLevel(ERROR) 352 | logger.SetLogLevel(ERROR) 353 | tags := []string{"alpha", "beta", "gamma", "delta", 354 | "epsilon", "tau", "pi", "phi", "psi"} 355 | logger.SetTagLevel("alpha", ERROR) 356 | SetDefaultTagLogLevel("beta", ERROR) 357 | for n := 0; n < b.N; n++ { 358 | logger.CheckLevel(ERROR, tags) 359 | } 360 | } 361 | 362 | func BenchmarkCheckFailingTagLevelNine(b *testing.B) { 363 | logger := GetLogger("BenchmarkCheckFailingTagLevel9") 364 | SetDefaultLogLevel(ERROR) 365 | logger.SetLogLevel(ERROR) 366 | tags := []string{"alpha", "beta", "gamma", "delta", 367 | "epsilon", "tau", "pi", "phi", "psi"} 368 | logger.SetTagLevel("alpha", ERROR) 369 | SetDefaultTagLogLevel("beta", ERROR) 370 | for n := 0; n < b.N; n++ { 371 | logger.CheckLevel(WARN, tags) 372 | } 373 | } 374 | 375 | func BenchmarkCheckPassingTagLevelNineByNine(b *testing.B) { 376 | logger := GetLogger("BenchmarkCheckPassingTagLevel99") 377 | SetDefaultLogLevel(ERROR) 378 | logger.SetLogLevel(ERROR) 379 | tags := []string{"alpha", "beta", "gamma", "delta", 380 | "epsilon", "tau", "pi", "phi", "psi"} 381 | logger.SetTagLevel("alpha", ERROR) 382 | logger.SetTagLevel("beta1", ERROR) 383 | logger.SetTagLevel("gamma1", ERROR) 384 | logger.SetTagLevel("delta1", ERROR) 385 | logger.SetTagLevel("epsilon1", ERROR) 386 | logger.SetTagLevel("tau1", ERROR) 387 | logger.SetTagLevel("pi1", ERROR) 388 | logger.SetTagLevel("phi1", ERROR) 389 | logger.SetTagLevel("psi1", ERROR) 390 | SetDefaultTagLogLevel("beta", ERROR) 391 | for n := 0; n < b.N; n++ { 392 | logger.CheckLevel(ERROR, tags) 393 | } 394 | } 395 | 396 | func BenchmarkCheckFailingTagLevelNineByNine(b *testing.B) { 397 | logger := GetLogger("BenchmarkCheckFailingTagLevel99") 398 | SetDefaultLogLevel(ERROR) 399 | logger.SetLogLevel(ERROR) 400 | tags := []string{"alpha", "beta", "gamma", "delta", 401 | "epsilon", "tau", "pi", "phi", "psi"} 402 | logger.SetTagLevel("alpha", ERROR) 403 | logger.SetTagLevel("beta1", ERROR) 404 | logger.SetTagLevel("gamma1", ERROR) 405 | logger.SetTagLevel("delta1", ERROR) 406 | logger.SetTagLevel("epsilon1", ERROR) 407 | logger.SetTagLevel("tau1", ERROR) 408 | logger.SetTagLevel("pi1", ERROR) 409 | logger.SetTagLevel("phi1", ERROR) 410 | logger.SetTagLevel("psi1", ERROR) 411 | SetDefaultTagLogLevel("beta", ERROR) 412 | for n := 0; n < b.N; n++ { 413 | logger.CheckLevel(WARN, tags) 414 | } 415 | } 416 | 417 | func BenchmarkCheckFailingTagLevelEightteen(b *testing.B) { 418 | logger := GetLogger("BenchmarkCheckFailingTagLevel18") 419 | SetDefaultLogLevel(ERROR) 420 | logger.SetLogLevel(ERROR) 421 | tags := []string{"alpha", "beta", "gamma", 422 | "delta", "epsilon", "tau", "pi", "phi", "psi", 423 | "zeta", "omega", "upsilon", "one", "two", "three", 424 | "four", "five", "six", 425 | } 426 | logger.SetTagLevel("alpha", ERROR) 427 | SetDefaultTagLogLevel("beta", ERROR) 428 | for n := 0; n < b.N; n++ { 429 | logger.CheckLevel(WARN, tags) 430 | } 431 | } 432 | 433 | func BenchmarkTagList(b *testing.B) { 434 | one := []string{"alpha", "beta", "gamma", "delta", 435 | "epsilon", "tau", "pi", "phi", "psi", 436 | "zeta", "omega", "upsilon", "one", "two", "three", 437 | "four", "five", "six", 438 | } 439 | 440 | two := tagList{ 441 | tagLevel{ 442 | tag: "tau", 443 | level: DEBUG, 444 | }, 445 | tagLevel{ 446 | tag: "upsilon", 447 | level: WARN, 448 | }, 449 | } 450 | 451 | for n := 0; n < b.N; n++ { 452 | two.checkTagLevel(WARN, one) 453 | } 454 | } 455 | 456 | -------------------------------------------------------------------------------- /rollingFileAppender.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | /* 11 | RollingFileAppender is the wrapper for a rolling file log appender. 12 | 13 | A RollingFile appender will log to a file specified by prefix, which can contain a path, and a suffix, like "log". The appender will 14 | concatenate the prefix and suffix using the following format "prefix.#.suffix" where # is the log file number. The current file will be "prefix.suffix". 15 | Note, the . between the elements, the prefix and suffix should not include these. 16 | 17 | Files can be rolled on size or manually by calling Roll(). 18 | 19 | The maxFiles must be at least 1 20 | MaxFileSize must be at least 1024 - and is measured in bytes, if the max files is 1 the max file size is ignored 21 | 22 | The actual file size will exceed maxFileSize, because the roller will not roll until a log message pushes the file past the size. 23 | */ 24 | type RollingFileAppender struct { 25 | BaseLogAppender 26 | prefix string 27 | suffix string 28 | maxFileSize int64 29 | maxFiles int16 30 | firstTime bool 31 | currentFile *os.File 32 | currentWriter *bufio.Writer 33 | mutex *sync.RWMutex 34 | } 35 | 36 | // NewRollingFileAppender is used to create a rolling file appender. 37 | func NewRollingFileAppender(prefix string, suffix string, maxFileSize int64, maxFiles int16) *RollingFileAppender { 38 | 39 | if maxFiles <= 0 { 40 | maxFiles = 1 41 | } 42 | 43 | if maxFileSize < 1024 { 44 | maxFileSize = 1024 45 | } 46 | 47 | appender := new(RollingFileAppender) 48 | appender.level = DEFAULT 49 | appender.maxFileSize = maxFileSize 50 | appender.prefix = prefix 51 | appender.suffix = suffix 52 | appender.maxFiles = maxFiles 53 | appender.firstTime = true 54 | 55 | appender.mutex = new(sync.RWMutex) 56 | return appender 57 | } 58 | 59 | // currentFileName should be called inside the lock. 60 | func (appender *RollingFileAppender) currentFileName() string { 61 | return fmt.Sprintf("%v.%v", appender.prefix, appender.suffix) 62 | } 63 | 64 | func (appender *RollingFileAppender) open() error { 65 | appender.mutex.Lock() 66 | defer appender.mutex.Unlock() 67 | 68 | if appender.currentWriter != nil { 69 | return nil 70 | } 71 | 72 | f, err := os.OpenFile(appender.currentFileName(), os.O_APPEND|os.O_WRONLY, 0644) 73 | 74 | if err != nil { 75 | if !os.IsNotExist(err) { 76 | return err 77 | } 78 | 79 | f, err = os.Create(appender.currentFileName()) 80 | 81 | if err != nil { 82 | return err 83 | } 84 | } 85 | 86 | appender.currentFile = f 87 | appender.currentWriter = bufio.NewWriter(appender.currentFile) 88 | 89 | return nil 90 | } 91 | 92 | // Close closes the current file after flushing any buffered data. 93 | func (appender *RollingFileAppender) Close() error { 94 | appender.mutex.Lock() 95 | defer appender.mutex.Unlock() 96 | 97 | var err error 98 | 99 | if appender.currentWriter != nil { 100 | err = appender.currentWriter.Flush() 101 | appender.currentWriter = nil 102 | } 103 | 104 | if appender.currentFile != nil { 105 | err = appender.currentFile.Close() 106 | appender.currentFile = nil 107 | } 108 | 109 | return err 110 | } 111 | 112 | // needsRoll should be called inside the lock. 113 | func (appender *RollingFileAppender) needsRoll() bool { 114 | 115 | if appender.maxFiles == 1 { 116 | _, err := os.Stat(appender.currentFileName()) 117 | if err != nil { 118 | return os.IsNotExist(err) 119 | } 120 | return false 121 | } 122 | 123 | if appender.firstTime { 124 | return true 125 | } 126 | 127 | info, err := os.Stat(appender.currentFileName()) 128 | 129 | if err != nil { 130 | return true 131 | } 132 | 133 | if info.Size() >= appender.maxFileSize { 134 | return true 135 | } 136 | 137 | return false 138 | } 139 | 140 | // Roll moves the file to the next number, up to the max files. 141 | func (appender *RollingFileAppender) Roll() error { 142 | appender.Close() 143 | 144 | appender.mutex.Lock() 145 | defer appender.mutex.Unlock() 146 | 147 | appender.firstTime = false 148 | 149 | for i := appender.maxFiles - 2; i >= 0; i-- { 150 | 151 | var fileName string 152 | 153 | if i == 0 { 154 | fileName = appender.currentFileName() 155 | } else { 156 | fileName = fmt.Sprintf("%v.%d.%v", appender.prefix, i, appender.suffix) 157 | } 158 | 159 | _, err := os.Stat(fileName) 160 | 161 | if err != nil { 162 | if os.IsNotExist(err) { 163 | continue // do'nt have this file yet 164 | } else { 165 | return err 166 | } 167 | } 168 | 169 | // we work backward so the only time the next file should exist is for the truly last file 170 | nextFileName := fmt.Sprintf("%v.%d.%v", appender.prefix, i+1, appender.suffix) 171 | _, err = os.Stat(nextFileName) 172 | 173 | if err != nil && !os.IsNotExist(err) { 174 | err = os.Remove(nextFileName) 175 | 176 | if err != nil { 177 | return err 178 | } 179 | } 180 | 181 | err = os.Rename(fileName, nextFileName) 182 | 183 | if err != nil { 184 | return err 185 | } 186 | } 187 | 188 | return nil 189 | } 190 | 191 | // Log a record to the current file. 192 | func (appender *RollingFileAppender) Log(record *LogRecord) error { 193 | 194 | if !appender.CheckLevel(record.Level) { 195 | return nil 196 | } 197 | 198 | appender.mutex.RLock() 199 | 200 | if appender.needsRoll() { 201 | appender.mutex.RUnlock() // release the read lock to upgrade 202 | err := appender.Roll() 203 | 204 | if err != nil { 205 | return err 206 | } 207 | 208 | err = appender.open() 209 | 210 | if err != nil { 211 | return err 212 | } 213 | appender.mutex.RLock() 214 | } 215 | 216 | if appender.currentWriter == nil { 217 | appender.mutex.RUnlock() // release the read lock to upgrade 218 | err := appender.open() 219 | if err != nil { 220 | return err 221 | } 222 | appender.mutex.RLock() 223 | } 224 | 225 | if appender.currentWriter != nil { 226 | _, err := appender.currentWriter.Write([]byte(appender.format(record))) 227 | 228 | if err != nil { 229 | return err 230 | } 231 | 232 | _, err = appender.currentWriter.Write([]byte("\n")) 233 | 234 | if err != nil { 235 | return err 236 | } 237 | 238 | appender.currentWriter.Flush() 239 | } 240 | 241 | appender.mutex.RUnlock() 242 | 243 | return nil 244 | } 245 | -------------------------------------------------------------------------------- /rollingFileAppender_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "os" 7 | "path" 8 | "testing" 9 | ) 10 | 11 | func TestRollingAppender(t *testing.T) { 12 | 13 | filepath := path.Join(os.TempDir(), "appendtest") 14 | app := NewRollingFileAppender(filepath, "log", int64(2048), 5) 15 | app.SetFormatter(GetFormatter(MINIMAL)) 16 | 17 | memoryAppender := NewMemoryAppender() 18 | memoryAppender.SetFormatter(GetFormatter(MINIMAL)) 19 | 20 | ClearAppenders() 21 | AddAppender(app) 22 | AddAppender(memoryAppender) 23 | 24 | SetDefaultLogLevel(INFO) 25 | 26 | for i := 0; i < 2548; i++ { 27 | Warn("1") 28 | Debug("1") //none of these should get logged 29 | } 30 | 31 | WaitForIncoming() 32 | ClearAppenders() //will close the rolling log appender 33 | 34 | assert.Equal(t, len(memoryAppender.GetLoggedMessages()), 2548, "should have logged all the messages") 35 | 36 | pathOne := fmt.Sprintf("%s.log", filepath) 37 | info, err := os.Stat(pathOne) 38 | assert.Nil(t, err, "Stat should be able to find the log file") 39 | assert.Equal(t, info.Size(), int64(1000), "new file should have 1000 bytes, 500 1's and new lines") 40 | 41 | pathTwo := fmt.Sprintf("%s.1.log", filepath) 42 | info, err = os.Stat(pathTwo) 43 | assert.Nil(t, err, "Stat should be able to find the rolled log file") 44 | assert.Equal(t, info.Size(), int64(2048), "rolled file should have 2048 bytes") 45 | } 46 | 47 | func TestRollingAppenderOneFile(t *testing.T) { 48 | 49 | filepath := path.Join(os.TempDir(), "appendtest") 50 | app := NewRollingFileAppender(filepath, "log", int64(2048), 1) 51 | app.SetFormatter(GetFormatter(MINIMAL)) 52 | 53 | memoryAppender := NewMemoryAppender() 54 | memoryAppender.SetFormatter(GetFormatter(MINIMAL)) 55 | 56 | pathOne := fmt.Sprintf("%s.log", filepath) 57 | err := os.Remove(pathOne) //we won't roll so make sure we start with nothing 58 | 59 | if err != nil && !os.IsNotExist(err) { 60 | assert.Nil(t, err, "Should be able to delete") 61 | } 62 | 63 | ClearAppenders() 64 | AddAppender(app) 65 | AddAppender(memoryAppender) 66 | 67 | SetDefaultLogLevel(INFO) 68 | 69 | for i := 0; i < 2548; i++ { 70 | Warn("1") 71 | Debug("1") //none of these should get logged 72 | } 73 | 74 | WaitForIncoming() 75 | ClearAppenders() //will close the rolling log appender 76 | 77 | assert.Equal(t, len(memoryAppender.GetLoggedMessages()), 2548, "should have logged all the messages") 78 | 79 | pathOne = fmt.Sprintf("%s.log", filepath) 80 | info, err := os.Stat(pathOne) 81 | assert.Nil(t, err, "Stat should be able to find the log file") 82 | assert.Equal(t, info.Size(), int64(2548*2), "new file should have all the data, since there isn't any rolling") 83 | } 84 | 85 | func TestRollingAppenderNew(t *testing.T) { 86 | 87 | filepath := path.Join(os.TempDir(), "appendtest") 88 | app := NewRollingFileAppender(filepath, "log", int64(100), -1) 89 | 90 | assert.Equal(t, app.maxFiles, int16(1), "max files defaults to 1") 91 | assert.Equal(t, app.maxFileSize, int64(1024), "max filesize defaults to 1024") 92 | assert.Equal(t, app.currentFileName(), fmt.Sprintf("%s.%s", filepath, "log"), "current file name is always prefix.suffix") 93 | } 94 | -------------------------------------------------------------------------------- /syslog_appender.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package logging 4 | 5 | import ( 6 | "log/syslog" 7 | ) 8 | 9 | // SysLogAppender is the logging appender for appending to the syslog service. 10 | type SysLogAppender struct { 11 | BaseLogAppender 12 | syslogger *syslog.Writer 13 | } 14 | 15 | // NewSysLogAppender creates a sys log appender. 16 | func NewSysLogAppender() *SysLogAppender { 17 | appender := new(SysLogAppender) 18 | appender.level = DEFAULT 19 | return appender 20 | } 21 | 22 | // Log adds a record to the sys log. 23 | func (appender *SysLogAppender) Log(record *LogRecord) error { 24 | 25 | if !appender.CheckLevel(record.Level) { 26 | return nil 27 | } 28 | 29 | if appender.syslogger == nil { 30 | logWriter, e := syslog.New(syslog.LOG_DEBUG, "") 31 | 32 | if e == nil { 33 | appender.syslogger = logWriter 34 | } else { 35 | return e 36 | } 37 | } 38 | 39 | if appender.syslogger != nil { 40 | 41 | formatted := appender.format(record) 42 | 43 | switch record.Level { 44 | case DEBUG: 45 | return appender.syslogger.Debug(formatted) 46 | case INFO: 47 | return appender.syslogger.Info(formatted) 48 | case WARN: 49 | return appender.syslogger.Warning(formatted) 50 | case ERROR: 51 | return appender.syslogger.Err(formatted) 52 | default: 53 | return appender.syslogger.Debug(formatted) 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | 60 | // Close shuts down the syslog connection. 61 | func (appender *SysLogAppender) Close() error { 62 | 63 | if appender.syslogger != nil { 64 | return appender.syslogger.Close() 65 | } 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /syslog_appender_win.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package logging 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | type SysLogAppender struct { 10 | BaseLogAppender 11 | } 12 | 13 | func NewSysLogAppender() *SysLogAppender { 14 | panic(errors.New("Syslog is not supported on Windows")) 15 | return nil 16 | } 17 | 18 | func (appender *SysLogAppender) Log(record *LogRecord) error { 19 | 20 | if !appender.CheckLevel(record.Level) { 21 | return nil 22 | } 23 | 24 | return errors.New("Syslog is not supported on Windows") 25 | } 26 | 27 | func (appender *SysLogAppender) Close() error { 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /taglist.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // A tagLevel represents a single setting for a logger. 8 | type tagLevel struct { 9 | tag string 10 | level LogLevel 11 | } 12 | 13 | // A tagList holds a full set of tagLevels for a logger. 14 | type tagList []tagLevel 15 | 16 | // tagLists are sortable, this helps in some situations, 17 | // in the future we may provide a "fast" version of checkTagLevel 18 | // for presorted arrays of tags. 19 | func (p tagList) Len() int { return len(p) } 20 | func (p tagList) Less(i, j int) bool { return p[i].tag < p[j].tag } 21 | func (p tagList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 22 | 23 | func (p tagList) checkTagLevel(level LogLevel, o []string) bool { 24 | if p == nil { 25 | return false 26 | } 27 | 28 | lp, lo := len(p), len(o) 29 | 30 | for i:=0; i