-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog.go
183 lines (159 loc) · 4.24 KB
/
log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package main
// structured logger
import (
"encoding/json"
"fmt"
"io"
"os"
"time"
)
type LogLevel int
const (
//nolint
NOLOG LogLevel = iota
CRITICAL
ERROR
WARNING
INFO
DEBUG
)
type logger struct {
level LogLevel
handle io.Writer
}
type LogContext map[string]string
type LogMessage struct {
// the level at which this message should be logged
Level LogLevel
// structured log data - all keys/values will be output
Context LogContext
// This function can run arbitrary code in a callback IFF the log is turned up to 'DEBUG'
// use this for heavyweight debugging code that you only want to run under special circumstances
DebugDetails func() string
}
// 0 value will disable logging
// the main logger is for diagnostics and debug logging
var Logger logger
// this logger is for query logging
var QueryLogger logger
// performs a sprintf with a given format string and arguments iff the message is printable
// at the logger's current level, this allows flexible log messages that can be turned on and
// off easily without performing expensive sprintfs
func (l logger) Sprintf(level LogLevel, format string, args ...interface{}) string {
if level <= l.level {
return fmt.Sprintf(format, args...)
}
return "[message suppressed by log system]"
}
// takes a structured message, checks log level, outputs it in a set format
func (l logger) Log(message LogMessage) {
if l.handle == nil {
// this logger was never initialized, just bail
return
}
if message.Level <= l.level {
message.Context["level"] = levelToString(message.Level)
now := time.Now()
message.Context["when"] = fmt.Sprintf("%d-%02d-%02d:%02d:%02d:%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
output := parseKeys(message.Context)
if l.level >= DEBUG && message.DebugDetails != nil {
message.Context["debug"] = message.DebugDetails()
}
l.output(output)
}
}
func (l logger) output(output string) {
fmt.Fprintf(l.handle, "%s\n", output)
}
func (l logger) SetLevel(level LogLevel) {
l.level = level
}
func parseKeys(context map[string]string) string {
output, err := json.Marshal(context)
if err != nil {
return fmt.Sprintf("could not marshal [%v] to JSON", context)
}
return string(output)
}
func levelToString(level LogLevel) string {
switch level {
case CRITICAL:
return "CRITICAL"
case ERROR:
return "ERROR"
case WARNING:
return "WARNING"
case INFO:
return "INFO"
case DEBUG:
return "DEBUG"
}
return "UNDEFINED"
}
// constructor, enforces format
func NewLogMessage(level LogLevel, context LogContext, debugDetails func() string) LogMessage {
return LogMessage{
Level: level,
Context: context,
DebugDetails: debugDetails,
}
}
// helper function to open a file to log to
// default behavior is to open 'location', other options include:
// "": /dev/null
// "/dev/stderr": os.Stderr
// "/dev/stdout": os.Stdout
func getLoggerHandle(location string) (*os.File, error) {
var handle *os.File
var err error
switch location {
case "":
handle, err = os.Open(os.DevNull)
case "/dev/stderr":
handle = os.Stderr
case "/dev/stdout":
handle = os.Stdout
default:
handle, err = os.OpenFile(location, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}
if err != nil {
return &os.File{}, fmt.Errorf("could not query logging file [%s]: %s", location, err)
}
return handle, nil
}
// initializes loggers
func InitLoggers() error {
config := GetConfiguration()
handle, err := getLoggerHandle(config.ServerLog.Location)
if err != nil {
return fmt.Errorf("could not open server log location [%s]: [%s]", config.ServerLog.Location, err)
}
l := logger{
level: config.ServerLog.Level,
handle: handle,
}
l.Log(NewLogMessage(
INFO,
LogContext{
"what": fmt.Sprintf("initialized new server logger at level [%s]", levelToString(l.level)),
},
func() string { return fmt.Sprintf("%v", l) },
))
Logger = l
handle, err = getLoggerHandle(config.QueryLog.Location)
if err != nil {
return fmt.Errorf("error opening query log file [%s]: [%s]", config.QueryLog.Location, err)
}
QueryLogger = logger{
level: DEBUG,
handle: handle,
}
l.Log(LogMessage{
Level: INFO,
Context: LogContext{
"what": "initialized new query logger",
"logger": Logger.Sprintf(DEBUG, "%v", QueryLogger),
},
})
return nil
}