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