forked from unacast/logger
-
Notifications
You must be signed in to change notification settings - Fork 0
/
logger.go
230 lines (199 loc) · 6.6 KB
/
logger.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package logger
import (
"io"
"os"
"context"
"sync"
"cloud.google.com/go/errorreporting"
log "github.com/mgutz/logxi/v1"
"github.com/pkg/errors"
)
// UnaLogger wraps a logxi logger
// and delegate to some of it's logging methods
type UnaLogger interface {
Debug(msg string, args ...interface{})
Info(msg string, args ...interface{})
Error(msg string, err error, args ...interface{})
Fatal(msg string, err error, args ...interface{})
Underlying() log.Logger
}
type unaLogger struct {
Logger log.Logger
name string
}
// Config contains Name and FileName for the logger
type Config struct {
Name string
FileName string
}
// Keep a list of loggers that we can use in the SetLevel func
var loggers []UnaLogger
var errorClient *errorreporting.Client
// logger for internal use
var lgr UnaLogger
// ExitOnPanic makes it possible to disable exiting in ReportPanics
var ExitOnPanic = true
// InitErrorReporting will enable the errors of all calls to Error and Fatal to be sent to Google Error Reporting
// It also enables the
func InitErrorReporting(ctx context.Context, projectID, serviceName, serviceVersion string) error {
lgr = New("unalogger")
client, err := errorreporting.NewClient(ctx, projectID,
errorreporting.Config{
ServiceName: serviceName,
ServiceVersion: serviceVersion})
if err != nil {
return err
}
errorClient = client
return nil
}
// ReportPanics should be defered in every new scope where you want to catch pancis and have them pass on to Stackdriver
// Error Reporting
func ReportPanics(ctx context.Context) func() {
return func() {
if errorClient == nil {
panic("The errorClient was nil, initialize it with InitErrorReporting before deferring this function")
}
x := recover()
if x == nil {
return
}
switch e := x.(type) {
case string:
err := errorClient.ReportSync(ctx, errorreporting.Entry{Error: errors.New(e)})
if err != nil {
lgr.Error("Couldn't do a ReportSync to Stackdriver Error Reporting", err)
}
case error:
err := errorClient.ReportSync(ctx, errorreporting.Entry{Error: e})
if err != nil {
lgr.Error("Couldn't do a ReportSync to Stackdriver Error Reporting", err)
}
default:
panic(x)
}
// exits with a non-zero code so the app execution stops
if ExitOnPanic {
os.Exit(1337)
}
}
}
// CloseClient should be deferred right after calling InitErrorReporting to enure that the client is
// closed down gracefully
func CloseClient() {
if errorClient == nil {
panic("The errorClient was nil, initialize it with InitErrorReporting before deferring this function")
}
var _ = errorClient.Close() // Ignoring this error
}
// Deprecated: The functionality is split into InitErrorReporting, ReportPanics and CloseClient instead
// SetUpErrorReporting creates an ErrorReporting client and returns that client together with a reportPanics function.
// That function should be defered in every new scope where you want to catch pancis and have them pass on to Stackdriver
// Error Reporting
func SetUpErrorReporting(ctx context.Context, projectID, serviceName, serviceVersion string) (client *errorreporting.Client, reportPanics func()) {
lgr := New("errorreporting")
errClient := InitErrorReporting(ctx, projectID, serviceName, serviceVersion)
if errClient != nil {
lgr.Fatal("Couldn't create an errorreporting client", errClient)
}
return errorClient, func() {
x := recover()
if x == nil {
return
}
switch e := x.(type) {
case string:
err := errorClient.ReportSync(ctx, errorreporting.Entry{Error: errors.New(e)})
if err != nil {
lgr.Error("Couldn't do a ReportSync to Stackdriver Error Reporting", err)
}
}
// repanics so the app execution stops
lgr.Info("Re-panicking ", "err", x)
panic(x)
}
}
var defaultsSet bool
var mutex = sync.Mutex{}
func setDefaults() {
// These configurations are made to make the
// log payload compatible with the LogEntry format used in Google Cloud Logging
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
log.KeyMap.Level = "severity"
log.KeyMap.Message = "message"
log.KeyMap.Time = "timestamp"
log.LevelMap[log.LevelEmergency] = "EMERGENCY"
log.LevelMap[log.LevelAlert] = "ALERT"
log.LevelMap[log.LevelFatal] = "CRITICAL"
log.LevelMap[log.LevelError] = "ERROR"
log.LevelMap[log.LevelWarn] = "WARNING"
log.LevelMap[log.LevelNotice] = "NOTICE"
log.LevelMap[log.LevelInfo] = "INFO"
log.LevelMap[log.LevelDebug] = "DEBUG"
defaultsSet = true
}
// New creates a new logger with the given (string) name
func New(name string) UnaLogger {
return NewLogger(Config{Name: name})
}
// NewLogger creates a new logger with the given (Config) name
func NewLogger(conf Config) UnaLogger {
logxiLogger := log.New(conf.Name)
if conf.FileName != "" {
if file, err := os.Create(conf.FileName); err == nil {
logxiLogger = log.NewLogger(file, conf.Name)
}
}
unaLogger := &unaLogger{
Logger: logxiLogger,
name: conf.Name,
}
// Add the logger to the list of loggers and set some defaults
// Needs to use a mutex here so loggers can be created in different goroutines
mutex.Lock()
loggers = append(loggers, unaLogger)
if !defaultsSet {
setDefaults()
}
mutex.Unlock()
return unaLogger
}
// SetWriter overrides the io.Writer of the underlying logxi logger
func (ul *unaLogger) SetWriter(writer io.Writer) {
ul.Logger = log.NewLogger(writer, ul.name)
}
// Underlying returns the underlying logxi logger
func (ul unaLogger) Underlying() log.Logger {
return ul.Logger
}
// Info logs to Stdout with an "INFO" prefix
func (ul unaLogger) Info(msg string, args ...interface{}) {
ul.Logger.Info(msg, args...)
}
// Debug logs to Stdout with an "DEBUG" prefix if Debug level is enabled
func (ul unaLogger) Debug(msg string, args ...interface{}) {
ul.Logger.Debug(msg, args...)
}
// Error logs to Stdout with an "Error" prefix
// It also adds an "error" key to the provided err(error) argument
func (ul unaLogger) Error(msg string, err error, args ...interface{}) {
if errorClient != nil {
errorClient.Report(errorreporting.Entry{
Error: err,
})
}
_ = ul.Logger.Error(msg, appendErrorToArgs(args, err)...)
}
// Fatal logs to Stdout with an "Fatal" prefix
// It also adds an "error" key to the provided err(error) argument
func (ul unaLogger) Fatal(msg string, err error, args ...interface{}) {
if errorClient != nil {
defer ReportPanics(context.Background())()
// Logging at level Fatal without the panic since we have reported to Errorreporting already
ul.Logger.Log(log.LevelFatal, msg, appendErrorToArgs(args, err))
}
ul.Logger.Fatal(msg, appendErrorToArgs(args, err)...)
}
func appendErrorToArgs(args []interface{}, err error) []interface{} {
return append(args, "error", err)
}