1 package logger
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import (
18 "bytes"
19 "fmt"
20 "io"
21 "io/ioutil"
22 "net/http"
23 "net/url"
24 "os"
25 "strings"
26 "sync"
27 "time"
28 )
29
30
31
32
33
34
35 type LevelType uint32
36
37 const (
38
39 LogNone LevelType = iota
40
41
42 LogFatal
43
44
45 LogPanic
46
47
48 LogError
49
50
51 LogWarning
52
53
54 LogInfo
55
56
57 LogDebug
58
59
60
61 LogAuth
62 )
63
64 const (
65 logNone = "NONE"
66 logFatal = "FATAL"
67 logPanic = "PANIC"
68 logError = "ERROR"
69 logWarning = "WARNING"
70 logInfo = "INFO"
71 logDebug = "DEBUG"
72 logAuth = "AUTH"
73 logUnknown = "UNKNOWN"
74 )
75
76
77 func ParseLevel(s string) (lt LevelType, err error) {
78 switch strings.ToUpper(s) {
79 case logFatal:
80 lt = LogFatal
81 case logPanic:
82 lt = LogPanic
83 case logError:
84 lt = LogError
85 case logWarning:
86 lt = LogWarning
87 case logInfo:
88 lt = LogInfo
89 case logDebug:
90 lt = LogDebug
91 case logAuth:
92 lt = LogAuth
93 default:
94 err = fmt.Errorf("bad log level '%s'", s)
95 }
96 return
97 }
98
99
100 func (lt LevelType) String() string {
101 switch lt {
102 case LogNone:
103 return logNone
104 case LogFatal:
105 return logFatal
106 case LogPanic:
107 return logPanic
108 case LogError:
109 return logError
110 case LogWarning:
111 return logWarning
112 case LogInfo:
113 return logInfo
114 case LogDebug:
115 return logDebug
116 case LogAuth:
117 return logAuth
118 default:
119 return logUnknown
120 }
121 }
122
123
124 type Filter struct {
125
126 URL func(u *url.URL) string
127
128
129
130 Header func(key string, val []string) (bool, []string)
131
132
133 Body func(b []byte) []byte
134 }
135
136 func (f Filter) processURL(u *url.URL) string {
137 if f.URL == nil {
138 return u.String()
139 }
140 return f.URL(u)
141 }
142
143 func (f Filter) processHeader(k string, val []string) (bool, []string) {
144 if f.Header == nil {
145 return true, val
146 }
147 return f.Header(k, val)
148 }
149
150 func (f Filter) processBody(b []byte) []byte {
151 if f.Body == nil {
152 return b
153 }
154 return f.Body(b)
155 }
156
157
158 type Writer interface {
159
160 Writeln(level LevelType, message string)
161
162
163 Writef(level LevelType, format string, a ...interface{})
164
165
166
167
168
169 WriteRequest(req *http.Request, filter Filter)
170
171
172
173
174
175 WriteResponse(resp *http.Response, filter Filter)
176 }
177
178
179
180 var Instance Writer
181
182
183 var logLevel = LogNone
184
185
186
187
188 func Level() LevelType {
189 return logLevel
190 }
191
192 func init() {
193
194 initDefaultLogger()
195 }
196
197 func initDefaultLogger() {
198
199 Instance = nilLogger{}
200 llStr := strings.ToLower(os.Getenv("AZURE_GO_SDK_LOG_LEVEL"))
201 if llStr == "" {
202 return
203 }
204 var err error
205 logLevel, err = ParseLevel(llStr)
206 if err != nil {
207 fmt.Fprintf(os.Stderr, "go-autorest: failed to parse log level: %s\n", err.Error())
208 return
209 }
210 if logLevel == LogNone {
211 return
212 }
213
214 dest := os.Stderr
215 lfStr := os.Getenv("AZURE_GO_SDK_LOG_FILE")
216 if strings.EqualFold(lfStr, "stdout") {
217 dest = os.Stdout
218 } else if lfStr != "" {
219 lf, err := os.Create(lfStr)
220 if err == nil {
221 dest = lf
222 } else {
223 fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error())
224 }
225 }
226 Instance = fileLogger{
227 logLevel: logLevel,
228 mu: &sync.Mutex{},
229 logFile: dest,
230 }
231 }
232
233
234 type nilLogger struct{}
235
236 func (nilLogger) Writeln(LevelType, string) {}
237
238 func (nilLogger) Writef(LevelType, string, ...interface{}) {}
239
240 func (nilLogger) WriteRequest(*http.Request, Filter) {}
241
242 func (nilLogger) WriteResponse(*http.Response, Filter) {}
243
244
245 type fileLogger struct {
246 logLevel LevelType
247 mu *sync.Mutex
248 logFile *os.File
249 }
250
251 func (fl fileLogger) Writeln(level LevelType, message string) {
252 fl.Writef(level, "%s\n", message)
253 }
254
255 func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) {
256 if fl.logLevel >= level {
257 fl.mu.Lock()
258 defer fl.mu.Unlock()
259 fmt.Fprintf(fl.logFile, "%s %s", entryHeader(level), fmt.Sprintf(format, a...))
260 fl.logFile.Sync()
261 }
262 }
263
264 func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) {
265 if req == nil || fl.logLevel < LogInfo {
266 return
267 }
268 b := &bytes.Buffer{}
269 fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(LogInfo), req.Method, filter.processURL(req.URL))
270
271 for k, v := range req.Header {
272 if ok, mv := filter.processHeader(k, v); ok {
273 fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
274 }
275 }
276 if fl.shouldLogBody(req.Header, req.Body) {
277
278 body, err := ioutil.ReadAll(req.Body)
279 if err == nil {
280 fmt.Fprintln(b, string(filter.processBody(body)))
281 if nc, ok := req.Body.(io.Seeker); ok {
282
283 nc.Seek(0, io.SeekStart)
284 } else {
285
286 req.Body = ioutil.NopCloser(bytes.NewReader(body))
287 }
288 } else {
289 fmt.Fprintf(b, "failed to read body: %v\n", err)
290 }
291 }
292 fl.mu.Lock()
293 defer fl.mu.Unlock()
294 fmt.Fprint(fl.logFile, b.String())
295 fl.logFile.Sync()
296 }
297
298 func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) {
299 if resp == nil || fl.logLevel < LogInfo {
300 return
301 }
302 b := &bytes.Buffer{}
303 fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(LogInfo), resp.StatusCode, filter.processURL(resp.Request.URL))
304
305 for k, v := range resp.Header {
306 if ok, mv := filter.processHeader(k, v); ok {
307 fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
308 }
309 }
310 if fl.shouldLogBody(resp.Header, resp.Body) {
311
312 defer resp.Body.Close()
313 body, err := ioutil.ReadAll(resp.Body)
314 if err == nil {
315 fmt.Fprintln(b, string(filter.processBody(body)))
316 resp.Body = ioutil.NopCloser(bytes.NewReader(body))
317 } else {
318 fmt.Fprintf(b, "failed to read body: %v\n", err)
319 }
320 }
321 fl.mu.Lock()
322 defer fl.mu.Unlock()
323 fmt.Fprint(fl.logFile, b.String())
324 fl.logFile.Sync()
325 }
326
327
328 func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool {
329 ct := header.Get("Content-Type")
330 return fl.logLevel >= LogDebug && body != nil && !strings.Contains(ct, "application/octet-stream")
331 }
332
333
334 func entryHeader(level LevelType) string {
335
336 return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String())
337 }
338
View as plain text