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