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