-
Notifications
You must be signed in to change notification settings - Fork 18
/
logging_sink.go
105 lines (89 loc) · 2.95 KB
/
logging_sink.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
package stats
import (
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"time"
)
type loggingSink struct {
writer io.Writer
now func() time.Time
}
type logLine struct {
Level string `json:"level"`
Timestamp sixDecimalPlacesFloat `json:"ts"`
Logger string `json:"logger"`
Message string `json:"msg"`
JSON map[string]string `json:"json"`
}
type sixDecimalPlacesFloat float64
func (f sixDecimalPlacesFloat) MarshalJSON() ([]byte, error) {
var ret []byte
ret = strconv.AppendFloat(ret, float64(f), 'f', 6, 64)
return ret, nil
}
// NewLoggingSink returns a "default" logging Sink that flushes stats
// to os.StdErr. This sink is not fast, or flexible, it doesn't
// buffer, it exists merely to be convenient to use by default, with
// no configuration.
//
// The format of this logger is similar to Zap, but not explicitly
// importing Zap to avoid the dependency. The format is as if you used
// a zap.NewProduction-generated logger, but also added a
// log.With(zap.Namespace("json")). This does not include any
// stacktrace for errors at the moment.
//
// If these defaults do not work for you, users should provide their
// own logger, conforming to FlushableSink, instead.
func NewLoggingSink() FlushableSink {
return &loggingSink{writer: os.Stderr, now: time.Now}
}
// this is allocated outside of logMessage, even though its only used
// there, to avoid allocing a map every time we log.
var emptyMap = map[string]string{}
func (s *loggingSink) logMessage(level string, msg string) {
nanos := s.now().UnixNano()
sec := sixDecimalPlacesFloat(float64(nanos) / float64(time.Second))
enc := json.NewEncoder(s.writer)
enc.Encode(logLine{
Message: msg,
Level: level,
Timestamp: sec,
Logger: "gostats.loggingsink",
// intentional empty map used to avoid any null parsing issues
// on the log collection side
JSON: emptyMap,
})
}
func (s *loggingSink) log(name, typ string, value float64) {
nanos := s.now().UnixNano()
sec := sixDecimalPlacesFloat(float64(nanos) / float64(time.Second))
enc := json.NewEncoder(s.writer)
kv := map[string]string{
"type": typ,
"value": fmt.Sprintf("%f", value),
}
if name != "" {
kv["name"] = name
}
enc.Encode(logLine{
Message: fmt.Sprintf("flushing %s", typ),
Level: "debug",
Timestamp: sec,
Logger: "gostats.loggingsink",
JSON: kv,
})
}
func (s *loggingSink) FlushCounter(name string, value uint64) { s.log(name, "counter", float64(value)) }
func (s *loggingSink) FlushGauge(name string, value uint64) { s.log(name, "gauge", float64(value)) }
func (s *loggingSink) FlushTimer(name string, value float64) { s.log(name, "timer", value) }
func (s *loggingSink) Flush() { s.log("", "all stats", 0) }
// Logger
func (s *loggingSink) Errorf(msg string, args ...interface{}) {
s.logMessage("error", fmt.Sprintf(msg, args...))
}
func (s *loggingSink) Warnf(msg string, args ...interface{}) {
s.logMessage("warn", fmt.Sprintf(msg, args...))
}