1// Copyright (C) 2014 Space Monkey, Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package spacelog 16 17import ( 18 "regexp" 19 "runtime" 20 "strings" 21 "sync" 22 "text/template" 23) 24 25var ( 26 // If set, these prefixes will be stripped out of automatic logger names. 27 IgnoredPrefixes []string 28 29 badChars = regexp.MustCompile("[^a-zA-Z0-9_.-]") 30 slashes = regexp.MustCompile("[/]") 31) 32 33func callerName() string { 34 pc, _, _, ok := runtime.Caller(2) 35 if !ok { 36 return "unknown.unknown" 37 } 38 f := runtime.FuncForPC(pc) 39 if f == nil { 40 return "unknown.unknown" 41 } 42 name := f.Name() 43 for _, prefix := range IgnoredPrefixes { 44 name = strings.TrimPrefix(name, prefix) 45 } 46 return badChars.ReplaceAllLiteralString( 47 slashes.ReplaceAllLiteralString(name, "."), "_") 48} 49 50// LoggerCollections contain all of the loggers a program might use. Typically 51// a codebase will just use the default logger collection. 52type LoggerCollection struct { 53 mtx sync.Mutex 54 loggers map[string]*Logger 55 level LogLevel 56 handler Handler 57} 58 59// NewLoggerCollection creates a new logger collection. It's unlikely you will 60// ever practically need this method. Use the DefaultLoggerCollection instead. 61func NewLoggerCollection() *LoggerCollection { 62 return &LoggerCollection{ 63 loggers: make(map[string]*Logger), 64 level: DefaultLevel, 65 handler: defaultHandler} 66} 67 68// GetLogger returns a new Logger with a name automatically generated using 69// the callstack. If you want to avoid automatic name generation check out 70// GetLoggerNamed 71func (c *LoggerCollection) GetLogger() *Logger { 72 return c.GetLoggerNamed(callerName()) 73} 74 75func (c *LoggerCollection) getLogger(name string, level LogLevel, 76 handler Handler) *Logger { 77 c.mtx.Lock() 78 defer c.mtx.Unlock() 79 80 logger, exists := c.loggers[name] 81 if !exists { 82 logger = &Logger{level: level, 83 collection: c, 84 name: name, 85 handler: handler} 86 c.loggers[name] = logger 87 } 88 return logger 89} 90 91// ConfigureLoggers configures loggers according to the given string 92// specification, which specifies a set of loggers and their associated 93// logging levels. Loggers are semicolon-separated; each 94// configuration is specified as <logger>=<level>. White space outside of 95// logger names and levels is ignored. The default level is specified 96// with the name "DEFAULT". 97// 98// An example specification: 99// `DEFAULT=ERROR; foo.bar=WARNING` 100func (c *LoggerCollection) ConfigureLoggers(specification string) error { 101 confs := strings.Split(strings.TrimSpace(specification), ";") 102 for i := range confs { 103 conf := strings.SplitN(confs[i], "=", 2) 104 levelstr := strings.TrimSpace(conf[1]) 105 name := strings.TrimSpace(conf[0]) 106 level, err := LevelFromString(levelstr) 107 if err != nil { 108 return err 109 } 110 if name == "DEFAULT" { 111 c.SetLevel(nil, level) 112 continue 113 } 114 logger := c.GetLoggerNamed(name) 115 logger.setLevel(level) 116 } 117 return nil 118} 119 120// GetLoggerNamed returns a new Logger with the provided name. GetLogger is 121// more frequently used. 122func (c *LoggerCollection) GetLoggerNamed(name string) *Logger { 123 c.mtx.Lock() 124 defer c.mtx.Unlock() 125 126 logger, exists := c.loggers[name] 127 if !exists { 128 logger = &Logger{level: c.level, 129 collection: c, 130 name: name, 131 handler: c.handler} 132 c.loggers[name] = logger 133 } 134 return logger 135} 136 137// SetLevel will set the current log level for all loggers with names that 138// match a provided regular expression. If the regular expression is nil, then 139// all loggers match. 140func (c *LoggerCollection) SetLevel(re *regexp.Regexp, level LogLevel) { 141 c.mtx.Lock() 142 defer c.mtx.Unlock() 143 144 if re == nil { 145 c.level = level 146 } 147 for name, logger := range c.loggers { 148 if re == nil || re.MatchString(name) { 149 logger.setLevel(level) 150 } 151 } 152} 153 154// SetHandler will set the current log handler for all loggers with names that 155// match a provided regular expression. If the regular expression is nil, then 156// all loggers match. 157func (c *LoggerCollection) SetHandler(re *regexp.Regexp, handler Handler) { 158 c.mtx.Lock() 159 defer c.mtx.Unlock() 160 161 if re == nil { 162 c.handler = handler 163 } 164 for name, logger := range c.loggers { 165 if re == nil || re.MatchString(name) { 166 logger.setHandler(handler) 167 } 168 } 169} 170 171// SetTextTemplate will set the current text template for all loggers with 172// names that match a provided regular expression. If the regular expression 173// is nil, then all loggers match. Note that not every handler is guaranteed 174// to support text templates and a text template will only apply to 175// text-oriented and unstructured handlers. 176func (c *LoggerCollection) SetTextTemplate(re *regexp.Regexp, 177 t *template.Template) { 178 c.mtx.Lock() 179 defer c.mtx.Unlock() 180 181 if re == nil { 182 c.handler.SetTextTemplate(t) 183 } 184 for name, logger := range c.loggers { 185 if re == nil || re.MatchString(name) { 186 logger.getHandler().SetTextTemplate(t) 187 } 188 } 189} 190 191// SetTextOutput will set the current output interface for all loggers with 192// names that match a provided regular expression. If the regular expression 193// is nil, then all loggers match. Note that not every handler is guaranteed 194// to support text output and a text output interface will only apply to 195// text-oriented and unstructured handlers. 196func (c *LoggerCollection) SetTextOutput(re *regexp.Regexp, 197 output TextOutput) { 198 c.mtx.Lock() 199 defer c.mtx.Unlock() 200 201 if re == nil { 202 c.handler.SetTextOutput(output) 203 } 204 for name, logger := range c.loggers { 205 if re == nil || re.MatchString(name) { 206 logger.getHandler().SetTextOutput(output) 207 } 208 } 209} 210 211var ( 212 // It's unlikely you'll need to use this directly 213 DefaultLoggerCollection = NewLoggerCollection() 214) 215 216// GetLogger returns an automatically-named logger on the default logger 217// collection. 218func GetLogger() *Logger { 219 return DefaultLoggerCollection.GetLoggerNamed(callerName()) 220} 221 222// GetLoggerNamed returns a new Logger with the provided name on the default 223// logger collection. GetLogger is more frequently used. 224func GetLoggerNamed(name string) *Logger { 225 return DefaultLoggerCollection.GetLoggerNamed(name) 226} 227 228// ConfigureLoggers configures loggers according to the given string 229// specification, which specifies a set of loggers and their associated 230// logging levels. Loggers are colon- or semicolon-separated; each 231// configuration is specified as <logger>=<level>. White space outside of 232// logger names and levels is ignored. The DEFAULT module is specified 233// with the name "DEFAULT". 234// 235// An example specification: 236// `DEFAULT=ERROR; foo.bar=WARNING` 237func ConfigureLoggers(specification string) error { 238 return DefaultLoggerCollection.ConfigureLoggers(specification) 239} 240 241// SetLevel will set the current log level for all loggers on the default 242// collection with names that match a provided regular expression. If the 243// regular expression is nil, then all loggers match. 244func SetLevel(re *regexp.Regexp, level LogLevel) { 245 DefaultLoggerCollection.SetLevel(re, level) 246} 247 248// SetHandler will set the current log handler for all loggers on the default 249// collection with names that match a provided regular expression. If the 250// regular expression is nil, then all loggers match. 251func SetHandler(re *regexp.Regexp, handler Handler) { 252 DefaultLoggerCollection.SetHandler(re, handler) 253} 254 255// SetTextTemplate will set the current text template for all loggers on the 256// default collection with names that match a provided regular expression. If 257// the regular expression is nil, then all loggers match. Note that not every 258// handler is guaranteed to support text templates and a text template will 259// only apply to text-oriented and unstructured handlers. 260func SetTextTemplate(re *regexp.Regexp, t *template.Template) { 261 DefaultLoggerCollection.SetTextTemplate(re, t) 262} 263 264// SetTextOutput will set the current output interface for all loggers on the 265// default collection with names that match a provided regular expression. If 266// the regular expression is nil, then all loggers match. Note that not every 267// handler is guaranteed to support text output and a text output interface 268// will only apply to text-oriented and unstructured handlers. 269func SetTextOutput(re *regexp.Regexp, output TextOutput) { 270 DefaultLoggerCollection.SetTextOutput(re, output) 271} 272