github.com/dhax/go-base@v0.0.0-20231004214136-8be7e5c1972b/logging/logger.go (about)

     1  // Package logging provides structured logging with logrus.
     2  package logging
     3  
     4  import (
     5  	"fmt"
     6  	"log"
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/go-chi/chi/v5/middleware"
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/spf13/viper"
    13  )
    14  
    15  var (
    16  	// Logger is a configured logrus.Logger.
    17  	Logger *logrus.Logger
    18  )
    19  
    20  // StructuredLogger is a structured logrus Logger.
    21  type StructuredLogger struct {
    22  	Logger *logrus.Logger
    23  }
    24  
    25  // NewLogger creates and configures a new logrus Logger.
    26  func NewLogger() *logrus.Logger {
    27  	Logger = logrus.New()
    28  	if viper.GetBool("log_textlogging") {
    29  		Logger.Formatter = &logrus.TextFormatter{
    30  			DisableTimestamp: true,
    31  		}
    32  	} else {
    33  		Logger.Formatter = &logrus.JSONFormatter{
    34  			DisableTimestamp: true,
    35  		}
    36  	}
    37  
    38  	level := viper.GetString("log_level")
    39  	if level == "" {
    40  		level = "error"
    41  	}
    42  	l, err := logrus.ParseLevel(level)
    43  	if err != nil {
    44  		log.Fatal(err)
    45  	}
    46  	Logger.Level = l
    47  	return Logger
    48  }
    49  
    50  // NewStructuredLogger implements a custom structured logrus Logger.
    51  func NewStructuredLogger(logger *logrus.Logger) func(next http.Handler) http.Handler {
    52  	return middleware.RequestLogger(&StructuredLogger{Logger})
    53  }
    54  
    55  // NewLogEntry sets default request log fields.
    56  func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
    57  	entry := &StructuredLoggerEntry{Logger: logrus.NewEntry(l.Logger)}
    58  	logFields := logrus.Fields{}
    59  
    60  	logFields["ts"] = time.Now().UTC().Format(time.RFC1123)
    61  
    62  	if reqID := middleware.GetReqID(r.Context()); reqID != "" {
    63  		logFields["req_id"] = reqID
    64  	}
    65  
    66  	scheme := "http"
    67  	if r.TLS != nil {
    68  		scheme = "https"
    69  	}
    70  
    71  	logFields["http_scheme"] = scheme
    72  	logFields["http_proto"] = r.Proto
    73  	logFields["http_method"] = r.Method
    74  
    75  	logFields["remote_addr"] = r.RemoteAddr
    76  	logFields["user_agent"] = r.UserAgent()
    77  
    78  	logFields["uri"] = fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)
    79  	logFields["uri"] = r.RequestURI
    80  
    81  	entry.Logger = entry.Logger.WithFields(logFields)
    82  
    83  	entry.Logger.Infoln("request started")
    84  
    85  	return entry
    86  }
    87  
    88  // StructuredLoggerEntry is a logrus.FieldLogger.
    89  type StructuredLoggerEntry struct {
    90  	Logger logrus.FieldLogger
    91  }
    92  
    93  func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
    94  	l.Logger = l.Logger.WithFields(logrus.Fields{
    95  		"resp_status":       status,
    96  		"resp_bytes_length": bytes,
    97  		"resp_elapsed_ms":   float64(elapsed.Nanoseconds()) / 1000000.0,
    98  	})
    99  
   100  	l.Logger.Infoln("request complete")
   101  }
   102  
   103  // Panic prints stack trace
   104  func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) {
   105  	l.Logger = l.Logger.WithFields(logrus.Fields{
   106  		"stack": string(stack),
   107  		"panic": fmt.Sprintf("%+v", v),
   108  	})
   109  }
   110  
   111  // Helper methods used by the application to get the request-scoped
   112  // logger entry and set additional fields between handlers.
   113  
   114  // GetLogEntry return the request scoped logrus.FieldLogger.
   115  func GetLogEntry(r *http.Request) logrus.FieldLogger {
   116  	entry := middleware.GetLogEntry(r).(*StructuredLoggerEntry)
   117  	return entry.Logger
   118  }
   119  
   120  // LogEntrySetField adds a field to the request scoped logrus.FieldLogger.
   121  func LogEntrySetField(r *http.Request, key string, value interface{}) {
   122  	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
   123  		entry.Logger = entry.Logger.WithField(key, value)
   124  	}
   125  }
   126  
   127  // LogEntrySetFields adds multiple fields to the request scoped logrus.FieldLogger.
   128  func LogEntrySetFields(r *http.Request, fields map[string]interface{}) {
   129  	if entry, ok := r.Context().Value(middleware.LogEntryCtxKey).(*StructuredLoggerEntry); ok {
   130  		entry.Logger = entry.Logger.WithFields(fields)
   131  	}
   132  }