1// Copyright (c) 2012-2013 Couchbase, Inc. 2// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 3// except in compliance with the License. You may obtain a copy of the License at 4// http://www.apache.org/licenses/LICENSE-2.0 5// Unless required by applicable law or agreed to in writing, software distributed under the 6// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 7// either express or implied. See the License for the specific language governing permissions 8// and limitations under the License. 9 10package clog 11 12import ( 13 "fmt" 14 "io" 15 "log" 16 "os" 17 "runtime" 18 "strings" 19 "sync/atomic" 20 "unsafe" 21) 22 23// Log level type. 24type LogLevel int32 25 26const ( 27 LevelDebug = LogLevel(iota) 28 LevelNormal 29 LevelWarning 30 LevelError 31 LevelPanic 32) 33 34// Logging package level (Setting Level directly isn't thread-safe). 35var Level = LevelNormal 36 37// Should caller be included in log messages (stored as 0 or 1 to enable thread-safe access) 38var includeCaller = int32(1) 39 40// Set of To() key strings that are enabled. 41var keys unsafe.Pointer = unsafe.Pointer(&map[string]bool{}) 42 43var logger *log.Logger = log.New(os.Stderr, "", log.LstdFlags) 44var logCallBack func(level, format string, args ...interface{}) string 45 46// Thread-safe API for setting log level. 47func SetLevel(to LogLevel) { 48 for { 49 if atomic.CompareAndSwapInt32((*int32)(&Level), 50 int32(GetLevel()), int32(to)) { 51 break 52 } 53 } 54} 55 56// Thread-safe API for fetching log level. 57func GetLevel() LogLevel { 58 return LogLevel(atomic.LoadInt32((*int32)(&Level))) 59} 60 61// Thread-safe API for configuring whether caller information is included in log output. (default true) 62func SetIncludeCaller(enabled bool) { 63 for { 64 if atomic.CompareAndSwapInt32((*int32)(&includeCaller), 65 btoi(IsIncludeCaller()), btoi(enabled)) { 66 break 67 } 68 } 69} 70 71// Thread-safe API for indicating whether caller information is included in log output. 72func IsIncludeCaller() bool { 73 return atomic.LoadInt32((*int32)(&includeCaller)) == 1 74} 75 76// Category that the data falls under. 77type ContentCategory int32 78 79const ( 80 UserData = ContentCategory(iota) 81 MetaData 82 SystemData 83 numTypes // This is to always be the last entry. 84) 85 86var tags []string 87 88func init() { 89 tags = []string{ 90 "ud", // Couchbase UserData 91 "md", // Couchbase MetaData 92 "sd", // Couchbase SystemData 93 } 94} 95 96func Tag(category ContentCategory, data interface{}) interface{} { 97 if category < numTypes { 98 return fmt.Sprintf("<%s>%s</%s>", tags[category], data, tags[category]) 99 } 100 101 return data 102} 103 104// Flags returns the output flags for clog. 105func Flags() int { 106 return logger.Flags() 107} 108 109// SetFlags sets the output flags for clog. 110func SetFlags(flags int) { 111 logger.SetFlags(flags) 112} 113 114// Disables ANSI color in log output. 115func DisableColor() { 116 reset, dim, fgRed, fgYellow = "", "", "", "" 117} 118 119// Disable timestamps in logs. 120func DisableTime() { 121 logger.SetFlags(logger.Flags() &^ (log.Ldate | log.Ltime | log.Lmicroseconds)) 122} 123 124// SetOutput sets the output destination for clog 125func SetOutput(w io.Writer) { 126 logger = log.New(w, "", logger.Flags()) 127} 128 129// Parses a comma-separated list of log keys, probably coming from an argv flag. 130// The key "bw" is interpreted as a call to NoColor, not a key. 131func ParseLogFlag(flag string) { 132 ParseLogFlags(strings.Split(flag, ",")) 133} 134 135// Set a prefix function for the log message. Prefix function is called for 136// each log message and it returns a prefix which is logged before each message 137func SetLoggerCallback(k func(level, format string, args ...interface{}) string) { 138 // Clear the date and time flag 139 DisableTime() 140 logCallBack = k 141} 142 143// Parses an array of log keys, probably coming from a argv flags. 144// The key "bw" is interpreted as a call to NoColor, not a key. 145func ParseLogFlags(flags []string) { 146 for _, key := range flags { 147 switch key { 148 case "bw": 149 DisableColor() 150 case "notime": 151 DisableTime() 152 default: 153 EnableKey(key) 154 for strings.HasSuffix(key, "+") { 155 key = key[:len(key)-1] 156 EnableKey(key) // "foo+" also enables "foo" 157 } 158 } 159 } 160 Log("Enabling logging: %s", flags) 161} 162 163// Enable logging messages sent to this key 164func EnableKey(key string) { 165 for { 166 opp := atomic.LoadPointer(&keys) 167 oldk := (*map[string]bool)(opp) 168 newk := map[string]bool{key: true} 169 for k := range *oldk { 170 newk[k] = true 171 } 172 if atomic.CompareAndSwapPointer(&keys, opp, unsafe.Pointer(&newk)) { 173 return 174 } 175 } 176} 177 178// Disable logging messages sent to this key 179func DisableKey(key string) { 180 for { 181 opp := atomic.LoadPointer(&keys) 182 oldk := (*map[string]bool)(opp) 183 newk := map[string]bool{} 184 for k := range *oldk { 185 if k != key { 186 newk[k] = true 187 } 188 } 189 if atomic.CompareAndSwapPointer(&keys, opp, unsafe.Pointer(&newk)) { 190 return 191 } 192 } 193} 194 195// Check to see if logging is enabled for a key 196func KeyEnabled(key string) bool { 197 m := *(*map[string]bool)(atomic.LoadPointer(&keys)) 198 return m[key] 199} 200 201type callInfo struct { 202 funcname, filename string 203 line int 204} 205 206func (c callInfo) String() string { 207 if c.funcname == "" { 208 return "???" 209 } 210 return fmt.Sprintf("%s() at %s:%d", lastComponent(c.funcname), 211 lastComponent(c.filename), c.line) 212} 213 214// Returns a string identifying a function on the call stack. 215// Use depth=1 for the caller of the function that calls GetCallersName, etc. 216func getCallersName(depth int) callInfo { 217 pc, file, line, ok := runtime.Caller(depth + 1) 218 if !ok { 219 return callInfo{} 220 } 221 222 fnname := "" 223 if fn := runtime.FuncForPC(pc); fn != nil { 224 fnname = fn.Name() 225 } 226 227 return callInfo{fnname, file, line} 228} 229 230// Logs a message to the console, but only if the corresponding key is true in keys. 231func To(key string, format string, args ...interface{}) { 232 if GetLevel() <= LevelNormal && KeyEnabled(key) { 233 if logCallBack != nil { 234 str := logCallBack("INFO", format, args...) 235 if str != "" { 236 logger.Print(str) 237 } 238 } else { 239 logger.Printf(fgYellow+key+": "+reset+format, args...) 240 } 241 } 242} 243 244// Logs a message to the console. 245func Log(format string, args ...interface{}) { 246 if GetLevel() <= LevelNormal { 247 if logCallBack != nil { 248 str := logCallBack("INFO", format, args...) 249 if str != "" { 250 logger.Print(str) 251 } 252 } else { 253 logger.Printf(format, args...) 254 } 255 } 256} 257 258// Prints a formatted message to the console. 259func Printf(format string, args ...interface{}) { 260 if GetLevel() <= LevelNormal { 261 if logCallBack != nil { 262 str := logCallBack("INFO", format, args...) 263 if str != "" { 264 logger.Print(str) 265 } 266 } else { 267 logger.Printf(format, args...) 268 } 269 } 270} 271 272// Prints a message to the console. 273func Print(args ...interface{}) { 274 if GetLevel() <= LevelNormal { 275 if logCallBack != nil { 276 str := logCallBack("INFO", "", args...) 277 if str != "" { 278 logger.Print(str) 279 } 280 } else { 281 logger.Print(args...) 282 } 283 } 284} 285 286// If the error is not nil, logs error to the console. Returns the input error 287// for easy chaining. 288func Error(err error) error { 289 if GetLevel() <= LevelError && err != nil { 290 doLogf(fgRed, "ERRO", "%v", err) 291 } 292 return err 293} 294 295// Logs a formatted error message to the console 296func Errorf(format string, args ...interface{}) { 297 if GetLevel() <= LevelError { 298 doLogf(fgRed, "ERRO", format, args...) 299 } 300} 301 302// Logs a formatted warning to the console 303func Warnf(format string, args ...interface{}) { 304 if GetLevel() <= LevelWarning { 305 doLogf(fgRed, "WARN", format, args...) 306 } 307} 308 309// Logs a warning to the console 310func Warn(args ...interface{}) { 311 if GetLevel() <= LevelWarning { 312 doLog(fgRed, "WARN", args...) 313 } 314} 315 316// Logs a formatted debug message to the console 317func Debugf(format string, args ...interface{}) { 318 if GetLevel() <= LevelDebug { 319 doLogf(fgRed, "DEBU", format, args...) 320 } 321} 322 323// Logs a debug message to the console 324func Debug(args ...interface{}) { 325 if GetLevel() <= LevelDebug { 326 doLog(fgRed, "DEBU", args...) 327 } 328} 329 330// Logs a highlighted message prefixed with "TEMP". This function is intended for 331// temporary logging calls added during development and not to be checked in, hence its 332// distinctive name (which is visible and easy to search for before committing.) 333func TEMPf(format string, args ...interface{}) { 334 doLogf(fgYellow, "TEMP", format, args...) 335} 336 337// Logs a highlighted message prefixed with "TEMP". This function is intended for 338// temporary logging calls added during development and not to be checked in, hence its 339// distinctive name (which is visible and easy to search for before committing.) 340func TEMP(args ...interface{}) { 341 doLog(fgYellow, "TEMP", args...) 342} 343 344// Logs a formatted warning to the console, then panics. 345func Panicf(format string, args ...interface{}) { 346 doLogf(fgRed, "CRIT", format, args...) 347 panic(fmt.Sprintf(format, args...)) 348} 349 350// Logs a warning to the console, then panics. 351func Panic(args ...interface{}) { 352 doLog(fgRed, "CRIT", args...) 353 panic(fmt.Sprint(args...)) 354} 355 356// For test fixture 357var exit = os.Exit 358 359// Logs a formatted warning to the console, then exits the process. 360func Fatalf(format string, args ...interface{}) { 361 doLogf(fgRed, "FATA", format, args...) 362 exit(1) 363} 364 365// Logs a warning to the console, then exits the process. 366func Fatal(args ...interface{}) { 367 doLog(fgRed, "FATA", args...) 368 exit(1) 369} 370 371func doLog(color string, prefix string, args ...interface{}) { 372 message := fmt.Sprint(args...) 373 if logCallBack != nil { 374 str := logCallBack(prefix, "", args...) 375 if str != "" { 376 if IsIncludeCaller() { 377 logger.Print(str, " -- ", getCallersName(2)) 378 } else { 379 logger.Print(str) 380 } 381 } 382 } else { 383 if IsIncludeCaller() { 384 logger.Print(color, prefix, ": ", message, reset, 385 dim, " -- ", getCallersName(2), reset) 386 } else { 387 logger.Print(color, prefix, ": ", message, reset, dim) 388 } 389 } 390} 391 392func doLogf(color string, prefix string, format string, args ...interface{}) { 393 message := fmt.Sprintf(format, args...) 394 if logCallBack != nil { 395 str := logCallBack(prefix, format, args...) 396 if str != "" { 397 if IsIncludeCaller() { 398 logger.Print(str, " -- ", getCallersName(2)) 399 } else { 400 logger.Print(str) 401 } 402 } 403 } else { 404 if IsIncludeCaller() { 405 logger.Print(color, prefix, ": ", message, reset, 406 dim, " -- ", getCallersName(2), reset) 407 } else { 408 logger.Print(color, prefix, ": ", message, reset, dim) 409 } 410 } 411} 412 413func lastComponent(path string) string { 414 if index := strings.LastIndex(path, "/"); index >= 0 { 415 path = path[index+1:] 416 } else if index = strings.LastIndex(path, "\\"); index >= 0 { 417 path = path[index+1:] 418 } 419 return path 420} 421 422func btoi(boolean bool) int32 { 423 if boolean { 424 return 1 425 } 426 return 0 427} 428 429// ANSI color control escape sequences. 430// Shamelessly copied from https://github.com/sqp/godock/blob/master/libs/log/colors.go 431var ( 432 reset = "\x1b[0m" 433 bright = "\x1b[1m" 434 dim = "\x1b[2m" 435 underscore = "\x1b[4m" 436 blink = "\x1b[5m" 437 reverse = "\x1b[7m" 438 hidden = "\x1b[8m" 439 fgBlack = "\x1b[30m" 440 fgRed = "\x1b[31m" 441 fgGreen = "\x1b[32m" 442 fgYellow = "\x1b[33m" 443 fgBlue = "\x1b[34m" 444 fgMagenta = "\x1b[35m" 445 fgCyan = "\x1b[36m" 446 fgWhite = "\x1b[37m" 447 bgBlack = "\x1b[40m" 448 bgRed = "\x1b[41m" 449 bgGreen = "\x1b[42m" 450 bgYellow = "\x1b[43m" 451 bgBlue = "\x1b[44m" 452 bgMagenta = "\x1b[45m" 453 bgCyan = "\x1b[46m" 454 bgWhite = "\x1b[47m" 455) 456