-
Notifications
You must be signed in to change notification settings - Fork 4
/
log.go
247 lines (207 loc) · 8.56 KB
/
log.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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Package log implements multiple string interceptors to stream logs initiated and tagged from the server to
// the targeted clients.
//
// This package allows then to multiplex logs, while only sending the parts for each stream connection to the
// destination client. Each client can have its own log level, independently of the daemon one.
//
// There are also privilege clients where all stream connection could be forwarded.
package log
import (
"context"
"fmt"
"strings"
"sync"
"github.com/canonical/ubuntu-pro-for-wsl/common/i18n"
"github.com/sirupsen/logrus"
)
const (
localLogFormatWithID = "[[%s]] %s"
logFormatWithCaller = "%s %s"
)
// Debug logs at the DEBUG level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Print; a newline is appended to local log if missing.
func Debug(ctx context.Context, args ...interface{}) {
log(ctx, logrus.DebugLevel, args...)
}
// Info logs at the INFO level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Print; a newline is appended to local log if missing.
func Info(ctx context.Context, args ...interface{}) {
log(ctx, logrus.InfoLevel, args...)
}
// Warning logs at the WARNING level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Print; a newline is appended to local log if missing.
func Warning(ctx context.Context, args ...interface{}) {
log(ctx, logrus.WarnLevel, args...)
}
// Error logs at the ERROR level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Print; a newline is appended to local log if missing.
func Error(ctx context.Context, args ...interface{}) {
log(ctx, logrus.ErrorLevel, args...)
}
// Debugf logs at the DEBUG level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Printf; a newline is appended to local log if missing.
func Debugf(ctx context.Context, format string, args ...interface{}) {
logf(ctx, logrus.DebugLevel, format, args...)
}
// Infof logs at the INFO level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Printf; a newline is appended to local log if missing.
func Infof(ctx context.Context, format string, args ...interface{}) {
logf(ctx, logrus.InfoLevel, format, args...)
}
// Warningf logs at the WARNING level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Printf; a newline is appended to local log if missing.
func Warningf(ctx context.Context, format string, args ...interface{}) {
logf(ctx, logrus.WarnLevel, format, args...)
}
// Errorf logs at the ERROR level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Printf; a newline is appended to local log if missing.
func Errorf(ctx context.Context, format string, args ...interface{}) {
logf(ctx, logrus.ErrorLevel, format, args...)
}
// Debugln logs at the DEBUG level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Println; a newline is appended to local log if missing.
func Debugln(ctx context.Context, args ...interface{}) {
logln(ctx, logrus.DebugLevel, args...)
}
// Infoln logs at the INFO level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Println; a newline is appended to local log if missing.
func Infoln(ctx context.Context, args ...interface{}) {
logln(ctx, logrus.InfoLevel, args...)
}
// Warningln logs at the WARNING level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Println; a newline is appended to local log if missing.
func Warningln(ctx context.Context, args ...interface{}) {
logln(ctx, logrus.WarnLevel, args...)
}
// Errorln logs at the ERROR level.
// If the context contains a stream, it will stream there and use associated local logger.
// Arguments are handled in the manner of fmt.Println; a newline is appended to local log if missing.
func Errorln(ctx context.Context, args ...interface{}) {
logln(ctx, logrus.ErrorLevel, args...)
}
func logln(ctx context.Context, level logrus.Level, args ...interface{}) {
log(ctx, level, sprintln(args...))
}
func logf(ctx context.Context, level logrus.Level, format string, args ...interface{}) {
log(ctx, level, fmt.Sprintf(format, args...))
}
var (
localLoggerMu = sync.RWMutex{}
)
// AddHook adds a hook to the logger.
func AddHook(ctx context.Context, hook logrus.Hook) {
localLogger := logrus.StandardLogger()
localLogger.AddHook(hook)
}
// SetReportCaller set if we want to report caller to standard logger.
func SetReportCaller(reportCaller bool) {
localLogger := logrus.StandardLogger()
localLoggerMu.Lock()
defer localLoggerMu.Unlock()
localLogger.SetReportCaller(reportCaller)
}
// WithoutRemoteSend takes a context with a logContext and returns a new context without the remote send.
// It is useful when we want a log to show what client it relates to, but we don't want to send it to the
// client.
func WithoutRemoteSend(ctx context.Context) context.Context {
logCtx, withRemote := ctx.Value(logContextKey).(logContext)
if !withRemote {
return ctx
}
// Disable sending it to the client
logCtx.sendStream = nil
return context.WithValue(ctx, logContextKey, logCtx)
}
func log(ctx context.Context, level logrus.Level, args ...interface{}) {
msg := fmt.Sprint(args...)
var callerForRemote bool
var sendStream sendStreamFn
var idRequest string
localLogger := logrus.StandardLogger()
logCtx, withRemote := ctx.Value(logContextKey).(logContext)
if withRemote {
sendStream = logCtx.sendStream
callerForRemote = logCtx.withCallerForRemote
localLogger = logCtx.localLogger
idRequest = logCtx.idRequest
}
// We are controlling and unwrapping the caller ourself outside of this package.
// As logrus doesn't allow to specify which package to exclude manually, do it there.
// https://github.com/sirupsen/logrus/issues/867
localLoggerMu.RLock()
callerForLocal := localLogger.ReportCaller
localLoggerMu.RUnlock()
streamsForwarders.mu.RLock()
callerForForwarders := streamsForwarders.showCaller
streamsForwarders.mu.RUnlock()
// Handle call stack collect
var caller string
if callerForLocal || callerForRemote || callerForForwarders {
f := getCaller()
fqfn := strings.Split(f.Function, "/")
fqfn = strings.Split(fqfn[len(fqfn)-1], ".")
funcName := strings.Join(fqfn[1:], ".")
caller = fmt.Sprintf("%s:%d %s()", f.File, f.Line, funcName)
}
if err := logLocallyMaybeRemote(level, caller, msg, localLogger, idRequest, sendStream); err != nil {
localLogger.Warningf(localLogFormatWithID, idRequest, i18n.G("couldn't send logs to client"))
}
}
func logLocallyMaybeRemote(level logrus.Level, caller, msg string, localLogger *logrus.Logger, idRequest string, sendStream sendStreamFn) (err error) {
// decorate depends on logstreamer: we can’t use it here
defer func() {
if err != nil {
err = fmt.Errorf(i18n.G("can't send logs to client: %v"), err)
}
}()
localMsg := msg
if idRequest != "" {
localMsg = fmt.Sprintf(localLogFormatWithID, idRequest, msg)
}
forwardMsg := localMsg
localLoggerMu.Lock()
callerForLocal := localLogger.ReportCaller
localLogger.SetReportCaller(false)
if callerForLocal {
localMsg = fmt.Sprintf(logFormatWithCaller, caller, localMsg)
}
localLogger.Log(level, localMsg)
// Reset value for next call
localLogger.SetReportCaller(callerForLocal)
localLoggerMu.Unlock()
if sendStream != nil {
if err = sendStream(level.String(), caller, msg); err != nil {
return err
}
}
// Send remotely local message to global listeners
streamsForwarders.mu.RLock()
for stream := range streamsForwarders.fw {
if err := stream.SendMsg(&Log{
LogHeader: logIdentifier,
Level: level.String(),
Caller: caller,
Msg: forwardMsg,
}); err != nil {
localLogger.Warningf("Couldn't send log to one or more listener: %v", err)
}
}
streamsForwarders.mu.RUnlock()
return nil
}
// sprintln called fmt.Sprintln, but stripped last empty space after the new line.
func sprintln(args ...interface{}) string {
msg := fmt.Sprintln(args...)
return msg[:len(msg)-1]
}