-
Notifications
You must be signed in to change notification settings - Fork 3
/
log.go
170 lines (149 loc) · 4.29 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
package main
import (
"bytes"
"encoding/json"
"expvar"
"fmt"
"log"
)
// utilities for json logging
// all messages should be logged as json, which allows us to more
// effectively filter logs. The functions here will serve to faciltate
// that through the code, and help guide all messages to contain a
// similar shape.
type LogCategory int32
const (
LogCategory_Nil LogCategory = iota
LogCategory_InvalidCodeState
LogCategory_ParseError
LogCategory_ConditionError
LogCategory_StorageError
LogCategory_MetatileError
LogCategory_ResponseError
LogCategory_ConfigError
LogCategory_Metrics
LogCategory_ExpVars
LogCategory_TileJson
)
func (lc LogCategory) String() string {
switch lc {
case LogCategory_Nil:
return "nil"
case LogCategory_InvalidCodeState:
return "invalid_code_state"
case LogCategory_ParseError:
return "parse"
case LogCategory_ConditionError:
return "condition"
case LogCategory_StorageError:
return "storage"
case LogCategory_MetatileError:
return "metatile"
case LogCategory_ResponseError:
return "response"
case LogCategory_ConfigError:
return "config"
case LogCategory_Metrics:
return "metrics"
case LogCategory_ExpVars:
return "expvars"
case LogCategory_TileJson:
return "tilejson"
}
panic(fmt.Sprintf("Unknown json category: %d\n", int32(lc)))
}
type JsonLogger interface {
// helpful for basic one liners
Info(string, ...interface{})
Warning(LogCategory, string, ...interface{})
Error(LogCategory, string, ...interface{})
// for logging metrics specifically
Metrics(map[string]interface{})
// for logging tilejson metrics
TileJson(map[string]interface{})
// for logging expvars specifically
ExpVars()
// allows adding more metadata, and will remain *mostly*
// unperturbed, will add minimal supplemental metadata before logging
Log(map[string]interface{}, ...interface{})
}
type JsonLoggerImpl struct {
Hostname string
Logger *log.Logger
}
func (l *JsonLoggerImpl) Log(jsonMap map[string]interface{}, xs ...interface{}) {
if _, ok := jsonMap["hostname"]; !ok {
jsonMap["hostname"] = l.Hostname
}
// if there are args, interpolate into the "message"
// that key is assumed to be the string that gets interpolated
if len(xs) > 0 {
if msgValue, ok := jsonMap["message"]; ok {
if msgStr, ok := msgValue.(string); ok {
jsonMap["message"] = fmt.Sprintf(msgStr, xs...)
}
}
}
jsonBytes, err := json.Marshal(jsonMap)
if err != nil {
panic("ERROR creating json")
}
jsonStr := string(jsonBytes)
l.Logger.Printf(jsonStr)
}
func (l *JsonLoggerImpl) Info(msg string, xs ...interface{}) {
l.Log(map[string]interface{}{
"type": "info",
"message": msg,
}, xs...)
}
func (l *JsonLoggerImpl) Warning(category LogCategory, msg string, xs ...interface{}) {
l.Log(map[string]interface{}{
"type": "warning",
"category": category.String(),
"message": msg,
}, xs...)
}
func (l *JsonLoggerImpl) Error(category LogCategory, msg string, xs ...interface{}) {
l.Log(map[string]interface{}{
"type": "error",
"category": category.String(),
"message": msg,
}, xs...)
}
func (l *JsonLoggerImpl) Metrics(metricsData map[string]interface{}) {
metricsData["type"] = "info"
metricsData["category"] = LogCategory_Metrics.String()
l.Log(metricsData)
}
func (l *JsonLoggerImpl) TileJson(metricsData map[string]interface{}) {
metricsData["type"] = "info"
metricsData["category"] = LogCategory_TileJson.String()
l.Log(metricsData)
}
func (l *JsonLoggerImpl) ExpVars() {
// The issue here is that getting the value of the Vars returns back
// the json encoded representation (eg strings have "" around them). So
// if we stick that in a map and call json.Marshal on it, it'll escape
// those. This is why we manually generate the json here, instead of
// creating a map and calling Log on it like the other paths.
var buffer bytes.Buffer
buffer.WriteString("{")
first := true
expvar.Do(func(kv expvar.KeyValue) {
if first {
first = false
} else {
buffer.WriteString(",")
}
fmt.Fprintf(&buffer, "\"%s\":%s", kv.Key, kv.Value.String())
})
buffer.WriteString("}")
l.Logger.Printf("{\"type\":\"info\",\"category\":\"%s\",\"expvars\":%s}\n", LogCategory_ExpVars.String(), buffer.String())
}
func NewJsonLogger(logger *log.Logger, hostname string) JsonLogger {
return &JsonLoggerImpl{
Logger: logger,
Hostname: hostname,
}
}