/
hook.go
115 lines (100 loc) · 2.99 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
package apmlogrus
import (
"context"
"time"
"github.com/sirupsen/logrus"
"go.elastic.co/apm"
"go.elastic.co/apm/stacktrace"
)
var (
// DefaultLogLevels is the log levels for which errors are reported by Hook, if Hook.LogLevels is not set.
DefaultLogLevels = []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
}
)
const (
// DefaultFatalFlushTimeout is the default value for Hook.FatalFlushTimeout.
DefaultFatalFlushTimeout = 5 * time.Second
)
func init() {
stacktrace.RegisterLibraryPackage("github.com/sirupsen/logrus")
}
// Hook implements logrus.Hook, reporting log records as errors
// to the APM Server. If TraceContext is used to add trace IDs
// to the log records, the errors reported will be associated
// with them.
type Hook struct {
// Tracer is the apm.Tracer to use for reporting errors.
// If Tracer is nil, then apm.DefaultTracer will be used.
Tracer *apm.Tracer
// LogLevels holds the log levels to report as errors.
// If LogLevels is nil, then the DefaultLogLevels will
// be used.
LogLevels []logrus.Level
// FatalFlushTimeout is the amount of time to wait while
// flushing a fatal log message to the APM Server before
// the process is exited. If this is 0, then
// DefaultFatalFlushTimeout will be used. If the timeout
// is a negative value, then no flushing will be performed.
FatalFlushTimeout time.Duration
}
func (h *Hook) tracer() *apm.Tracer {
tracer := h.Tracer
if tracer == nil {
tracer = apm.DefaultTracer
}
return tracer
}
// Levels returns h.LogLevels, satisfying the logrus.Hook interface.
func (h *Hook) Levels() []logrus.Level {
levels := h.LogLevels
if levels == nil {
levels = DefaultLogLevels
}
return levels
}
// Fire reports the log entry as an error to the APM Server.
func (h *Hook) Fire(entry *logrus.Entry) error {
tracer := h.tracer()
if !tracer.Active() {
return nil
}
err, _ := entry.Data[logrus.ErrorKey].(error)
errlog := tracer.NewErrorLog(apm.ErrorLogRecord{
Message: entry.Message,
Level: entry.Level.String(),
Error: err,
})
errlog.Handled = true
errlog.Timestamp = entry.Time
errlog.SetStacktrace(1)
// Extract trace context added with apmlogrus.TraceContext,
// and include it in the reported error.
if traceID, ok := entry.Data[FieldKeyTraceID].(apm.TraceID); ok {
errlog.TraceID = traceID
}
if transactionID, ok := entry.Data[FieldKeyTransactionID].(apm.SpanID); ok {
errlog.TransactionID = transactionID
errlog.ParentID = transactionID
}
if spanID, ok := entry.Data[FieldKeySpanID].(apm.SpanID); ok {
errlog.ParentID = spanID
}
errlog.Send()
if entry.Level == logrus.FatalLevel {
// In its default configuration, logrus will exit the process
// following a fatal log message, so we flush the tracer.
flushTimeout := h.FatalFlushTimeout
if flushTimeout == 0 {
flushTimeout = DefaultFatalFlushTimeout
}
if flushTimeout >= 0 {
ctx, cancel := context.WithTimeout(context.Background(), flushTimeout)
defer cancel()
tracer.Flush(ctx.Done())
}
}
return nil
}