1package gocbcore
2
3import (
4	"fmt"
5	"log"
6	"os"
7	"strings"
8)
9
10// LogLevel specifies the severity of a log message.
11type LogLevel int
12
13// Various logging levels (or subsystems) which can categorize the message.
14// Currently these are ordered in decreasing severity.
15const (
16	LogError LogLevel = iota
17	LogWarn
18	LogInfo
19	LogDebug
20	LogTrace
21	LogSched
22	LogMaxVerbosity
23)
24
25func redactUserData(v interface{}) string {
26	return fmt.Sprintf("<ud>%v<ud>", v)
27}
28
29func redactMetaData(v interface{}) string {
30	return fmt.Sprintf("<md>%v<md>", v)
31}
32
33func redactSystemData(v interface{}) string {
34	return fmt.Sprintf("<sd>%v<sd>", v)
35}
36
37// LogRedactLevel specifies the degree with which to redact the logs.
38type LogRedactLevel int
39
40const (
41	// RedactNone indicates to perform no redactions
42	RedactNone LogRedactLevel = iota
43
44	// RedactPartial indicates to redact all possible user-identifying information from logs.
45	RedactPartial
46
47	// RedactFull indicates to fully redact all possible identifying information from logs.
48	RedactFull
49)
50
51// SetLogRedactionLevel specifies the level with which logs should be redacted.
52func SetLogRedactionLevel(level LogRedactLevel) {
53	globalLogRedactionLevel = level
54}
55
56func isLogRedactionLevelNone() bool {
57	return globalLogRedactionLevel == RedactNone
58}
59
60func isLogRedactionLevelPartial() bool {
61	return globalLogRedactionLevel == RedactPartial
62}
63
64func isLogRedactionLevelFull() bool {
65	return globalLogRedactionLevel == RedactFull
66}
67
68func logLevelToString(level LogLevel) string {
69	switch level {
70	case LogError:
71		return "error"
72	case LogWarn:
73		return "warn"
74	case LogInfo:
75		return "info"
76	case LogDebug:
77		return "debug"
78	case LogTrace:
79		return "trace"
80	case LogSched:
81		return "sched"
82	}
83
84	return fmt.Sprintf("unknown (%d)", level)
85}
86
87// Logger defines a logging interface. You can either use one of the default loggers
88// (DefaultStdioLogger(), VerboseStdioLogger()) or implement your own.
89type Logger interface {
90	// Outputs logging information:
91	// level is the verbosity level
92	// offset is the position within the calling stack from which the message
93	// originated. This is useful for contextual loggers which retrieve file/line
94	// information.
95	Log(level LogLevel, offset int, format string, v ...interface{}) error
96}
97
98type defaultLogger struct {
99	Level    LogLevel
100	GoLogger *log.Logger
101}
102
103func (l *defaultLogger) Log(level LogLevel, offset int, format string, v ...interface{}) error {
104	if level > l.Level {
105		return nil
106	}
107	s := fmt.Sprintf(format, v...)
108	return l.GoLogger.Output(offset+2, s)
109}
110
111var (
112	globalDefaultLogger = defaultLogger{
113		GoLogger: log.New(os.Stderr, "GOCB ", log.Lmicroseconds|log.Lshortfile), Level: LogDebug,
114	}
115
116	globalVerboseLogger = defaultLogger{
117		GoLogger: globalDefaultLogger.GoLogger, Level: LogMaxVerbosity,
118	}
119
120	globalLogger            Logger
121	globalLogRedactionLevel LogRedactLevel
122)
123
124// DefaultStdioLogger gets the default standard I/O logger.
125//  gocbcore.SetLogger(gocbcore.DefaultStdioLogger())
126func DefaultStdioLogger() Logger {
127	return &globalDefaultLogger
128}
129
130// VerboseStdioLogger is a more verbose level of DefaultStdioLogger(). Messages
131// pertaining to the scheduling of ordinary commands (and their responses) will
132// also be emitted.
133//  gocbcore.SetLogger(gocbcore.VerboseStdioLogger())
134func VerboseStdioLogger() Logger {
135	return &globalVerboseLogger
136}
137
138// SetLogger sets a logger to be used by the library. A logger can be obtained via
139// the DefaultStdioLogger() or VerboseStdioLogger() functions. You can also implement
140// your own logger using the Logger interface.
141func SetLogger(logger Logger) {
142	globalLogger = logger
143}
144
145type redactableLogValue interface {
146	redacted() interface{}
147}
148
149func logExf(level LogLevel, offset int, format string, v ...interface{}) {
150	if globalLogger != nil {
151		if level <= LogInfo && !isLogRedactionLevelNone() {
152			// We only redact at info level or below.
153			for i, iv := range v {
154				if redactable, ok := iv.(redactableLogValue); ok {
155					v[i] = redactable.redacted()
156				}
157			}
158		}
159
160		err := globalLogger.Log(level, offset+1, format, v...)
161		if err != nil {
162			log.Printf("Logger error occurred (%s)\n", err)
163		}
164	}
165}
166
167func logDebugf(format string, v ...interface{}) {
168	logExf(LogDebug, 1, format, v...)
169}
170
171func logSchedf(format string, v ...interface{}) {
172	logExf(LogSched, 1, format, v...)
173}
174
175func logWarnf(format string, v ...interface{}) {
176	logExf(LogWarn, 1, format, v...)
177}
178
179func logErrorf(format string, v ...interface{}) {
180	logExf(LogError, 1, format, v...)
181}
182
183func logInfof(format string, v ...interface{}) {
184	logExf(LogInfo, 1, format, v...)
185}
186
187func reindentLog(indent, message string) string {
188	reindentedMessage := strings.Replace(message, "\n", "\n"+indent, -1)
189	return fmt.Sprintf("%s%s", indent, reindentedMessage)
190}
191