-
Notifications
You must be signed in to change notification settings - Fork 82
/
hook.go
138 lines (122 loc) · 3.72 KB
/
hook.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
package logrustash
import (
"fmt"
"io"
"sync"
"time"
"github.com/sirupsen/logrus"
)
// Hook represents a Logstash hook.
// It has two fields: writer to write the entry to Logstash and
// formatter to format the entry to a Logstash format before sending.
//
// To initialize it use the `New` function.
type Hook struct {
writer io.Writer
formatter logrus.Formatter
}
// New returns a new logrus.Hook for Logstash.
//
// To create a new hook that sends logs to `tcp://logstash.corp.io:9999`:
//
// conn, _ := net.Dial("tcp", "logstash.corp.io:9999")
// hook := logrustash.New(conn, logrustash.DefaultFormatter())
func New(w io.Writer, f logrus.Formatter) logrus.Hook {
return Hook{
writer: w,
formatter: f,
}
}
// Fire takes, formats and sends the entry to Logstash.
// Hook's formatter is used to format the entry into Logstash format
// and Hook's writer is used to write the formatted entry to the Logstash instance.
func (h Hook) Fire(e *logrus.Entry) error {
dataBytes, err := h.formatter.Format(e)
if err != nil {
return err
}
_, err = h.writer.Write(dataBytes)
return err
}
// Levels returns all logrus levels.
func (h Hook) Levels() []logrus.Level {
return logrus.AllLevels
}
// Using a pool to re-use of old entries when formatting Logstash messages.
// It is used in the Fire function.
var entryPool = sync.Pool{
New: func() interface{} {
return &logrus.Entry{}
},
}
// copyEntry copies the entry `e` to a new entry and then adds all the fields in `fields` that are missing in the new entry data.
// It uses `entryPool` to re-use allocated entries.
func copyEntry(e *logrus.Entry, fields logrus.Fields) *logrus.Entry {
ne := entryPool.Get().(*logrus.Entry)
ne.Message = e.Message
ne.Level = e.Level
ne.Time = e.Time
ne.Data = logrus.Fields{}
if e.HasCaller() {
ne.Caller = e.Caller
ne.Logger = e.Logger
ne.Data["function"] = e.Caller.Function
ne.Data["file"] = fmt.Sprintf("%s:%d", e.Caller.File, e.Caller.Line)
}
for k, v := range fields {
ne.Data[k] = v
}
for k, v := range e.Data {
ne.Data[k] = v
}
return ne
}
// releaseEntry puts the given entry back to `entryPool`. It must be called if copyEntry is called.
func releaseEntry(e *logrus.Entry) {
entryPool.Put(e)
}
// LogstashFormatter represents a Logstash format.
// It has logrus.Formatter which formats the entry and logrus.Fields which
// are added to the JSON message if not given in the entry data.
//
// Note: use the `DefaultFormatter` function to set a default Logstash formatter.
type LogstashFormatter struct {
logrus.Formatter
logrus.Fields
}
var (
logstashFields = logrus.Fields{"@version": "1", "type": "log"}
logstashFieldMap = logrus.FieldMap{
logrus.FieldKeyTime: "@timestamp",
logrus.FieldKeyMsg: "message",
}
)
// DefaultFormatter returns a default Logstash formatter:
// A JSON format with "@version" set to "1" (unless set differently in `fields`,
// "type" to "log" (unless set differently in `fields`),
// "@timestamp" to the log time and "message" to the log message.
//
// Note: to set a different configuration use the `LogstashFormatter` structure.
func DefaultFormatter(fields logrus.Fields) logrus.Formatter {
for k, v := range logstashFields {
if _, ok := fields[k]; !ok {
fields[k] = v
}
}
return LogstashFormatter{
Formatter: &logrus.JSONFormatter{
FieldMap: logstashFieldMap,
TimestampFormat: time.RFC3339Nano,
},
Fields: fields,
}
}
// Format formats an entry to a Logstash format according to the given Formatter and Fields.
//
// Note: the given entry is copied and not changed during the formatting process.
func (f LogstashFormatter) Format(e *logrus.Entry) ([]byte, error) {
ne := copyEntry(e, f.Fields)
dataBytes, err := f.Formatter.Format(ne)
releaseEntry(ne)
return dataBytes, err
}