3 // Copyright 2017 Microsoft Corporation
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
30 // LevelType tells a logger the minimum level to log. When code reports a log entry,
31 // the LogLevel indicates the level of the log entry. The logger only records entries
32 // whose level is at least the level it was told to log. See the Log* constants.
33 // For example, if a logger is configured with LogError, then LogError, LogPanic,
34 // and LogFatal entries will be logged; lower level entries are ignored.
38 // LogNone tells a logger not to log any entries passed to it.
39 LogNone LevelType = iota
41 // LogFatal tells a logger to log all LogFatal entries passed to it.
44 // LogPanic tells a logger to log all LogPanic and LogFatal entries passed to it.
47 // LogError tells a logger to log all LogError, LogPanic and LogFatal entries passed to it.
50 // LogWarning tells a logger to log all LogWarning, LogError, LogPanic and LogFatal entries passed to it.
53 // LogInfo tells a logger to log all LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
56 // LogDebug tells a logger to log all LogDebug, LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
65 logWarning = "WARNING"
68 logUnknown = "UNKNOWN"
71 // ParseLevel converts the specified string into the corresponding LevelType.
72 func ParseLevel(s string) (lt LevelType, err error) {
73 switch strings.ToUpper(s) {
87 err = fmt.Errorf("bad log level '%s'", s)
92 // String implements the stringer interface for LevelType.
93 func (lt LevelType) String() string {
114 // Filter defines functions for filtering HTTP request/response content.
116 // URL returns a potentially modified string representation of a request URL.
117 URL func(u *url.URL) string
119 // Header returns a potentially modified set of values for the specified key.
120 // To completely exclude the header key/values return false.
121 Header func(key string, val []string) (bool, []string)
123 // Body returns a potentially modified request/response body.
124 Body func(b []byte) []byte
127 func (f Filter) processURL(u *url.URL) string {
134 func (f Filter) processHeader(k string, val []string) (bool, []string) {
138 return f.Header(k, val)
141 func (f Filter) processBody(b []byte) []byte {
148 // Writer defines methods for writing to a logging facility.
149 type Writer interface {
150 // Writeln writes the specified message with the standard log entry header and new-line character.
151 Writeln(level LevelType, message string)
153 // Writef writes the specified format specifier with the standard log entry header and no new-line character.
154 Writef(level LevelType, format string, a ...interface{})
156 // WriteRequest writes the specified HTTP request to the logger if the log level is greater than
157 // or equal to LogInfo. The request body, if set, is logged at level LogDebug or higher.
158 // Custom filters can be specified to exclude URL, header, and/or body content from the log.
159 // By default no request content is excluded.
160 WriteRequest(req *http.Request, filter Filter)
162 // WriteResponse writes the specified HTTP response to the logger if the log level is greater than
163 // or equal to LogInfo. The response body, if set, is logged at level LogDebug or higher.
164 // Custom filters can be specified to exclude URL, header, and/or body content from the log.
165 // By default no response content is excluded.
166 WriteResponse(resp *http.Response, filter Filter)
169 // Instance is the default log writer initialized during package init.
170 // This can be replaced with a custom implementation as required.
174 var logLevel = LogNone
176 // Level returns the value specified in AZURE_GO_AUTOREST_LOG_LEVEL.
177 // If no value was specified the default value is LogNone.
178 // Custom loggers can call this to retrieve the configured log level.
179 func Level() LevelType {
184 // separated for testing purposes
188 func initDefaultLogger() {
189 // init with nilLogger so callers don't have to do a nil check on Default
190 Instance = nilLogger{}
191 llStr := strings.ToLower(os.Getenv("AZURE_GO_SDK_LOG_LEVEL"))
196 logLevel, err = ParseLevel(llStr)
198 fmt.Fprintf(os.Stderr, "go-autorest: failed to parse log level: %s\n", err.Error())
201 if logLevel == LogNone {
206 lfStr := os.Getenv("AZURE_GO_SDK_LOG_FILE")
207 if strings.EqualFold(lfStr, "stdout") {
209 } else if lfStr != "" {
210 lf, err := os.Create(lfStr)
214 fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error())
217 Instance = fileLogger{
224 // the nil logger does nothing
225 type nilLogger struct{}
227 func (nilLogger) Writeln(LevelType, string) {}
229 func (nilLogger) Writef(LevelType, string, ...interface{}) {}
231 func (nilLogger) WriteRequest(*http.Request, Filter) {}
233 func (nilLogger) WriteResponse(*http.Response, Filter) {}
235 // A File is used instead of a Logger so the stream can be flushed after every write.
236 type fileLogger struct {
238 mu *sync.Mutex // for synchronizing writes to logFile
242 func (fl fileLogger) Writeln(level LevelType, message string) {
243 fl.Writef(level, "%s\n", message)
246 func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) {
247 if fl.logLevel >= level {
250 fmt.Fprintf(fl.logFile, "%s %s", entryHeader(level), fmt.Sprintf(format, a...))
255 func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) {
256 if req == nil || fl.logLevel < LogInfo {
260 fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(LogInfo), req.Method, filter.processURL(req.URL))
262 for k, v := range req.Header {
263 if ok, mv := filter.processHeader(k, v); ok {
264 fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
267 if fl.shouldLogBody(req.Header, req.Body) {
269 body, err := ioutil.ReadAll(req.Body)
271 fmt.Fprintln(b, string(filter.processBody(body)))
272 if nc, ok := req.Body.(io.Seeker); ok {
273 // rewind to the beginning
274 nc.Seek(0, io.SeekStart)
277 req.Body = ioutil.NopCloser(bytes.NewReader(body))
280 fmt.Fprintf(b, "failed to read body: %v\n", err)
285 fmt.Fprint(fl.logFile, b.String())
289 func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) {
290 if resp == nil || fl.logLevel < LogInfo {
294 fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(LogInfo), resp.StatusCode, filter.processURL(resp.Request.URL))
296 for k, v := range resp.Header {
297 if ok, mv := filter.processHeader(k, v); ok {
298 fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
301 if fl.shouldLogBody(resp.Header, resp.Body) {
303 defer resp.Body.Close()
304 body, err := ioutil.ReadAll(resp.Body)
306 fmt.Fprintln(b, string(filter.processBody(body)))
307 resp.Body = ioutil.NopCloser(bytes.NewReader(body))
309 fmt.Fprintf(b, "failed to read body: %v\n", err)
314 fmt.Fprint(fl.logFile, b.String())
318 // returns true if the provided body should be included in the log
319 func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool {
320 ct := header.Get("Content-Type")
321 return fl.logLevel >= LogDebug && body != nil && !strings.Contains(ct, "application/octet-stream")
324 // creates standard header for log entries, it contains a timestamp and the log level
325 func entryHeader(level LevelType) string {
326 // this format provides a fixed number of digits so the size of the timestamp is constant
327 return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String())