123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- package logger
- // Copyright 2017 Microsoft Corporation
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "strings"
- "sync"
- "time"
- )
- // LevelType tells a logger the minimum level to log. When code reports a log entry,
- // the LogLevel indicates the level of the log entry. The logger only records entries
- // whose level is at least the level it was told to log. See the Log* constants.
- // For example, if a logger is configured with LogError, then LogError, LogPanic,
- // and LogFatal entries will be logged; lower level entries are ignored.
- type LevelType uint32
- const (
- // LogNone tells a logger not to log any entries passed to it.
- LogNone LevelType = iota
- // LogFatal tells a logger to log all LogFatal entries passed to it.
- LogFatal
- // LogPanic tells a logger to log all LogPanic and LogFatal entries passed to it.
- LogPanic
- // LogError tells a logger to log all LogError, LogPanic and LogFatal entries passed to it.
- LogError
- // LogWarning tells a logger to log all LogWarning, LogError, LogPanic and LogFatal entries passed to it.
- LogWarning
- // LogInfo tells a logger to log all LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
- LogInfo
- // LogDebug tells a logger to log all LogDebug, LogInfo, LogWarning, LogError, LogPanic and LogFatal entries passed to it.
- LogDebug
- )
- const (
- logNone = "NONE"
- logFatal = "FATAL"
- logPanic = "PANIC"
- logError = "ERROR"
- logWarning = "WARNING"
- logInfo = "INFO"
- logDebug = "DEBUG"
- logUnknown = "UNKNOWN"
- )
- // ParseLevel converts the specified string into the corresponding LevelType.
- func ParseLevel(s string) (lt LevelType, err error) {
- switch strings.ToUpper(s) {
- case logFatal:
- lt = LogFatal
- case logPanic:
- lt = LogPanic
- case logError:
- lt = LogError
- case logWarning:
- lt = LogWarning
- case logInfo:
- lt = LogInfo
- case logDebug:
- lt = LogDebug
- default:
- err = fmt.Errorf("bad log level '%s'", s)
- }
- return
- }
- // String implements the stringer interface for LevelType.
- func (lt LevelType) String() string {
- switch lt {
- case LogNone:
- return logNone
- case LogFatal:
- return logFatal
- case LogPanic:
- return logPanic
- case LogError:
- return logError
- case LogWarning:
- return logWarning
- case LogInfo:
- return logInfo
- case LogDebug:
- return logDebug
- default:
- return logUnknown
- }
- }
- // Filter defines functions for filtering HTTP request/response content.
- type Filter struct {
- // URL returns a potentially modified string representation of a request URL.
- URL func(u *url.URL) string
- // Header returns a potentially modified set of values for the specified key.
- // To completely exclude the header key/values return false.
- Header func(key string, val []string) (bool, []string)
- // Body returns a potentially modified request/response body.
- Body func(b []byte) []byte
- }
- func (f Filter) processURL(u *url.URL) string {
- if f.URL == nil {
- return u.String()
- }
- return f.URL(u)
- }
- func (f Filter) processHeader(k string, val []string) (bool, []string) {
- if f.Header == nil {
- return true, val
- }
- return f.Header(k, val)
- }
- func (f Filter) processBody(b []byte) []byte {
- if f.Body == nil {
- return b
- }
- return f.Body(b)
- }
- // Writer defines methods for writing to a logging facility.
- type Writer interface {
- // Writeln writes the specified message with the standard log entry header and new-line character.
- Writeln(level LevelType, message string)
- // Writef writes the specified format specifier with the standard log entry header and no new-line character.
- Writef(level LevelType, format string, a ...interface{})
- // WriteRequest writes the specified HTTP request to the logger if the log level is greater than
- // or equal to LogInfo. The request body, if set, is logged at level LogDebug or higher.
- // Custom filters can be specified to exclude URL, header, and/or body content from the log.
- // By default no request content is excluded.
- WriteRequest(req *http.Request, filter Filter)
- // WriteResponse writes the specified HTTP response to the logger if the log level is greater than
- // or equal to LogInfo. The response body, if set, is logged at level LogDebug or higher.
- // Custom filters can be specified to exclude URL, header, and/or body content from the log.
- // By default no response content is excluded.
- WriteResponse(resp *http.Response, filter Filter)
- }
- // Instance is the default log writer initialized during package init.
- // This can be replaced with a custom implementation as required.
- var Instance Writer
- // default log level
- var logLevel = LogNone
- // Level returns the value specified in AZURE_GO_AUTOREST_LOG_LEVEL.
- // If no value was specified the default value is LogNone.
- // Custom loggers can call this to retrieve the configured log level.
- func Level() LevelType {
- return logLevel
- }
- func init() {
- // separated for testing purposes
- initDefaultLogger()
- }
- func initDefaultLogger() {
- // init with nilLogger so callers don't have to do a nil check on Default
- Instance = nilLogger{}
- llStr := strings.ToLower(os.Getenv("AZURE_GO_SDK_LOG_LEVEL"))
- if llStr == "" {
- return
- }
- var err error
- logLevel, err = ParseLevel(llStr)
- if err != nil {
- fmt.Fprintf(os.Stderr, "go-autorest: failed to parse log level: %s\n", err.Error())
- return
- }
- if logLevel == LogNone {
- return
- }
- // default to stderr
- dest := os.Stderr
- lfStr := os.Getenv("AZURE_GO_SDK_LOG_FILE")
- if strings.EqualFold(lfStr, "stdout") {
- dest = os.Stdout
- } else if lfStr != "" {
- lf, err := os.Create(lfStr)
- if err == nil {
- dest = lf
- } else {
- fmt.Fprintf(os.Stderr, "go-autorest: failed to create log file, using stderr: %s\n", err.Error())
- }
- }
- Instance = fileLogger{
- logLevel: logLevel,
- mu: &sync.Mutex{},
- logFile: dest,
- }
- }
- // the nil logger does nothing
- type nilLogger struct{}
- func (nilLogger) Writeln(LevelType, string) {}
- func (nilLogger) Writef(LevelType, string, ...interface{}) {}
- func (nilLogger) WriteRequest(*http.Request, Filter) {}
- func (nilLogger) WriteResponse(*http.Response, Filter) {}
- // A File is used instead of a Logger so the stream can be flushed after every write.
- type fileLogger struct {
- logLevel LevelType
- mu *sync.Mutex // for synchronizing writes to logFile
- logFile *os.File
- }
- func (fl fileLogger) Writeln(level LevelType, message string) {
- fl.Writef(level, "%s\n", message)
- }
- func (fl fileLogger) Writef(level LevelType, format string, a ...interface{}) {
- if fl.logLevel >= level {
- fl.mu.Lock()
- defer fl.mu.Unlock()
- fmt.Fprintf(fl.logFile, "%s %s", entryHeader(level), fmt.Sprintf(format, a...))
- fl.logFile.Sync()
- }
- }
- func (fl fileLogger) WriteRequest(req *http.Request, filter Filter) {
- if req == nil || fl.logLevel < LogInfo {
- return
- }
- b := &bytes.Buffer{}
- fmt.Fprintf(b, "%s REQUEST: %s %s\n", entryHeader(LogInfo), req.Method, filter.processURL(req.URL))
- // dump headers
- for k, v := range req.Header {
- if ok, mv := filter.processHeader(k, v); ok {
- fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
- }
- }
- if fl.shouldLogBody(req.Header, req.Body) {
- // dump body
- body, err := ioutil.ReadAll(req.Body)
- if err == nil {
- fmt.Fprintln(b, string(filter.processBody(body)))
- if nc, ok := req.Body.(io.Seeker); ok {
- // rewind to the beginning
- nc.Seek(0, io.SeekStart)
- } else {
- // recreate the body
- req.Body = ioutil.NopCloser(bytes.NewReader(body))
- }
- } else {
- fmt.Fprintf(b, "failed to read body: %v\n", err)
- }
- }
- fl.mu.Lock()
- defer fl.mu.Unlock()
- fmt.Fprint(fl.logFile, b.String())
- fl.logFile.Sync()
- }
- func (fl fileLogger) WriteResponse(resp *http.Response, filter Filter) {
- if resp == nil || fl.logLevel < LogInfo {
- return
- }
- b := &bytes.Buffer{}
- fmt.Fprintf(b, "%s RESPONSE: %d %s\n", entryHeader(LogInfo), resp.StatusCode, filter.processURL(resp.Request.URL))
- // dump headers
- for k, v := range resp.Header {
- if ok, mv := filter.processHeader(k, v); ok {
- fmt.Fprintf(b, "%s: %s\n", k, strings.Join(mv, ","))
- }
- }
- if fl.shouldLogBody(resp.Header, resp.Body) {
- // dump body
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err == nil {
- fmt.Fprintln(b, string(filter.processBody(body)))
- resp.Body = ioutil.NopCloser(bytes.NewReader(body))
- } else {
- fmt.Fprintf(b, "failed to read body: %v\n", err)
- }
- }
- fl.mu.Lock()
- defer fl.mu.Unlock()
- fmt.Fprint(fl.logFile, b.String())
- fl.logFile.Sync()
- }
- // returns true if the provided body should be included in the log
- func (fl fileLogger) shouldLogBody(header http.Header, body io.ReadCloser) bool {
- ct := header.Get("Content-Type")
- return fl.logLevel >= LogDebug && body != nil && !strings.Contains(ct, "application/octet-stream")
- }
- // creates standard header for log entries, it contains a timestamp and the log level
- func entryHeader(level LevelType) string {
- // this format provides a fixed number of digits so the size of the timestamp is constant
- return fmt.Sprintf("(%s) %s:", time.Now().Format("2006-01-02T15:04:05.0000000Z07:00"), level.String())
- }
|