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 }