-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlogrus.go
137 lines (117 loc) · 3.49 KB
/
logrus.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
package logformatter
import (
"bytes"
"encoding/json"
"fmt"
"runtime"
"strings"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"google.golang.org/genproto/googleapis/devtools/clouderrorreporting/v1beta1"
"google.golang.org/genproto/googleapis/logging/type"
)
type (
// Interface for inspecting error objects recursively
// Ref: https://godoc.org/github.com/pkg/errors#hdr-Retrieving_the_cause_of_an_error
causer interface {
Cause() error
}
// Interface for retrieving stack frame for each error object
// Ref: https://godoc.org/github.com/pkg/errors#hdr-Retrieving_the_stack_trace_of_an_error_or_wrapper
stackTracer interface {
StackTrace() errors.StackTrace
}
)
// Adapt from https://github.com/googleapis/google-cloud-go/issues/1084#issuecomment-474565019
// FormatStack formats the error object to adhere to runtime.Stack required by the stackdriver errorreporting
// (ref: https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report#ReportedErrorEvent)
// FormatStack should accept the error implements stackTracer interface or the stackframe cannot be retrieved (i.e, return nil).
// Suggest using with pkg/errors to create the error object.
func FormatStack(err error) (buffer []byte) {
if err == nil {
return nil
}
// find the inner most error with a stack
inner := err
for inner != nil {
if cause, ok := inner.(causer); ok {
inner = cause.Cause()
if _, ok := inner.(stackTracer); ok {
err = inner
}
} else {
break
}
}
if stackTrace, ok := err.(stackTracer); ok {
buf := bytes.Buffer{}
buf.WriteString(getGoroutineState() + "\n")
// format each frame of the stack to match runtime.Stack's format
var lines []string
for _, frame := range stackTrace.StackTrace() {
pc := uintptr(frame) - 1
fn := runtime.FuncForPC(pc)
if fn != nil {
file, line := fn.FileLine(pc)
lines = append(lines, fmt.Sprintf("%s()\n\t%s:%d +%#x", fn.Name(), file, line, fn.Entry()))
}
}
buf.WriteString(strings.Join(lines, "\n"))
buffer = buf.Bytes()
}
return
}
func NewStackdriverFormatter(service, version string) *Stackdriver {
return &Stackdriver{
ErrorEvent: clouderrorreporting.ErrorEvent{
ServiceContext: &clouderrorreporting.ServiceContext{
Service: service,
Version: version,
},
},
}
}
func (s *Stackdriver) Format(entry *logrus.Entry) ([]byte, error) {
// Copy customized fields
s.Payload = make(logrus.Fields, len(entry.Data)+4)
for k, v := range entry.Data {
switch v := v.(type) {
case error:
s.Payload[k] = v.Error()
default:
s.Payload[k] = v
}
}
s.Message = entry.Message
s.Severity = convertLevelToLogSeverity(entry.Level)
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = new(bytes.Buffer)
}
encoder := json.NewEncoder(b)
if err := encoder.Encode(s); err != nil {
return nil, fmt.Errorf("failed to marshal fields to JSON, %+v", err)
}
return b.Bytes(), nil
}
func convertLevelToLogSeverity(lvl logrus.Level) ltype.LogSeverity {
switch lvl {
case logrus.InfoLevel:
return ltype.LogSeverity_INFO
case logrus.DebugLevel:
return ltype.LogSeverity_DEBUG
default:
// Omit intentionally
}
return ltype.LogSeverity_ERROR
}
// As the goroutine ID and status cannot be retrieved through the public API,
// capture these information from the first line of runtime.Stack().
func getGoroutineState() string {
stack := make([]byte, 64)
stack = stack[:runtime.Stack(stack, false)]
stack = stack[:bytes.Index(stack, []byte("\n"))]
return string(stack)
}