Last active
September 25, 2025 17:24
-
-
Save IAmRiteshKoushik/d9897c03f309f78af83a21f1e4c48289 to your computer and use it in GitHub Desktop.
logger-implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package util | |
| import ( | |
| "errors" | |
| "fmt" | |
| "io" | |
| "net" | |
| "net/http" | |
| "os" | |
| "strings" | |
| "time" | |
| "github.com/rs/zerolog" | |
| ) | |
| var Log *LoggerService | |
| type LoggerService struct { | |
| Logger zerolog.Logger | |
| Env string | |
| } | |
| type ILoggerService interface { | |
| // Function to enrich each log with data | |
| enrich(req *http.Request, e *zerolog.Event) *zerolog.Event | |
| // This set of functions is to be used in the context of the web-server | |
| // where there is a server context involved | |
| DebugCtx(req *http.Request, msg string) | |
| InfoCtx(req *http.Request, msg string) | |
| WarnCtx(req *http.Request, msg string) | |
| ErrorCtx(req *http.Request, msg string, err error) | |
| FatalCtx(req *http.Request, msg string, err error) | |
| PanicCtx(req *http.Request, msg string, r any, trace string) // r = recover() | |
| SuccessCtx(req *http.Request) | |
| // This set of functions can be used in scenarios where there is no | |
| // server context involved | |
| Debug(msg string) | |
| Info(msg string) | |
| Warn(msg string) | |
| Error(msg string, err error) | |
| Fatal(msg string, err error) | |
| // Logging middleware to be used only as a global middleware during router | |
| // initialization | |
| LogMiddleware(next http.Handler) http.Handler | |
| } | |
| func InitLogger(env string) (*LoggerService, error) { | |
| var output io.Writer | |
| switch env { | |
| case "DEVELOPMENT": | |
| file, err := os.OpenFile("dev.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) | |
| if err != nil { | |
| return nil, err | |
| } | |
| consoleWriter := zerolog.ConsoleWriter{ | |
| Out: os.Stderr, | |
| TimeFormat: time.RFC3339, | |
| } | |
| fileWriter := zerolog.ConsoleWriter{ | |
| Out: file, | |
| TimeFormat: "", | |
| FormatFieldName: func(i any) string { | |
| return fmt.Sprintf("%s=", i) | |
| }, | |
| FormatFieldValue: func(i any) string { | |
| s := fmt.Sprintf("%v", i) | |
| if strings.ContainsAny(s, "") { | |
| return fmt.Sprintf("%q", s) | |
| } | |
| return s | |
| }, | |
| FormatTimestamp: func(i any) string { | |
| t, err := time.Parse(time.RFC3339, i.(string)) | |
| if err != nil { | |
| return fmt.Sprintf("time=%q", i) // Fallback if parsing fails | |
| } | |
| return fmt.Sprintf("time=%d", t.UnixMilli()) | |
| }, | |
| FormatLevel: func(i any) string { | |
| return fmt.Sprintf("level=%q", i) | |
| }, | |
| FormatMessage: func(i any) string { | |
| return fmt.Sprintf("msg=%q", i) // Quoting the message automatically | |
| }, | |
| NoColor: true, | |
| } | |
| output = zerolog.MultiLevelWriter(consoleWriter, fileWriter) | |
| case "PRODUCTION": | |
| file, err := os.OpenFile("prod.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) | |
| if err != nil { | |
| return nil, err | |
| } | |
| output = zerolog.ConsoleWriter{ | |
| Out: file, | |
| TimeFormat: "", | |
| FormatFieldName: func(i any) string { | |
| return fmt.Sprintf("%s=", i) | |
| }, | |
| FormatFieldValue: func(i any) string { | |
| s := fmt.Sprintf("%v", i) | |
| if strings.ContainsAny(s, "") { | |
| return fmt.Sprintf("%q", s) | |
| } | |
| return s | |
| }, | |
| FormatTimestamp: func(i any) string { | |
| t, err := time.Parse(time.RFC3339, i.(string)) | |
| if err != nil { | |
| return fmt.Sprintf("time=%q", i) // Fallback if parsing fails | |
| } | |
| return fmt.Sprintf("time=%d", t.UnixMilli()) | |
| }, | |
| FormatLevel: func(i interface{}) string { | |
| return fmt.Sprintf("level=%s", i) | |
| }, | |
| FormatMessage: func(i interface{}) string { | |
| return fmt.Sprintf("msg=%q", i) // Quoting the message automatically | |
| }, | |
| NoColor: true, | |
| } | |
| default: | |
| return nil, errors.New("invalid environment for logger setup") | |
| } | |
| logger := zerolog.New(output).With().Timestamp().Logger() | |
| zerolog.TimeFieldFormat = time.RFC3339Nano | |
| return &LoggerService{ | |
| Logger: logger, | |
| Env: env, | |
| }, nil | |
| } | |
| func (l *LoggerService) enrich(req *http.Request, e *zerolog.Event) *zerolog.Event { | |
| queryParams := req.URL.Query() | |
| clientIP := req.Header.Get("X-Forwarded-For") | |
| if clientIP == "" { | |
| clientIP = req.Header.Get("X-Real-IP") | |
| } | |
| if clientIP == "" { | |
| clientIP, _, _ = net.SplitHostPort(req.RemoteAddr) | |
| } | |
| // In case of multiple IPs in X-Forwarded-For, take the first one | |
| if strings.Contains(clientIP, ",") { | |
| clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) | |
| } | |
| return e. | |
| Str("route", req.URL.Path). | |
| Str("method", req.Method). | |
| Interface("query-params", queryParams). | |
| Str("ip", clientIP). | |
| Str("user-agent", req.Header.Get("User-Agent")) | |
| } | |
| func (l *LoggerService) DebugCtx(req *http.Request, msg string) { | |
| if l.Env == "PRODUCTION" { | |
| return | |
| } | |
| event := l.Logger.WithLevel(zerolog.DebugLevel) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) InfoCtx(req *http.Request, msg string) { | |
| event := l.Logger.WithLevel(zerolog.InfoLevel) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) WarnCtx(req *http.Request, msg string) { | |
| event := l.Logger.WithLevel(zerolog.WarnLevel) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) ErrorCtx(req *http.Request, msg string, err error) { | |
| event := l.Logger.WithLevel(zerolog.ErrorLevel).Err(err) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) FatalCtx(req *http.Request, msg string, err error) { | |
| event := l.Logger.WithLevel(zerolog.FatalLevel).Err(err) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) PanicCtx(req *http.Request, msg string, r any, trace string) { | |
| event := l.Logger.WithLevel(zerolog.InfoLevel). | |
| Str("panic_value", fmt.Sprintf("%v", r)). | |
| Str("trace", trace) | |
| l.enrich(req, event).Msg(msg) | |
| } | |
| func (l *LoggerService) SuccessCtx(req *http.Request) { | |
| event := l.Logger.WithLevel(zerolog.InfoLevel) | |
| l.enrich(req, event).Msg("request successful") | |
| } | |
| func (l *LoggerService) Debug(msg string) { | |
| if l.Env == "PRODUCTION" { | |
| return | |
| } | |
| l.Logger.WithLevel(zerolog.DebugLevel).Msg(msg) | |
| } | |
| func (l *LoggerService) Info(msg string) { | |
| l.Logger.WithLevel(zerolog.InfoLevel).Msg(msg) | |
| } | |
| func (l *LoggerService) Warn(msg string) { | |
| l.Logger.WithLevel(zerolog.InfoLevel).Msg(msg) | |
| } | |
| func (l *LoggerService) Error(msg string, err error) { | |
| l.Logger.WithLevel(zerolog.InfoLevel).Err(err).Msg(msg) | |
| } | |
| func (l *LoggerService) Fatal(msg string, err error) { | |
| l.Logger.WithLevel(zerolog.FatalLevel).Err(err).Msg(msg) | |
| } | |
| // loggingResponseWriter is a wrapper around http.ResponseWriter to capture status code and response size. | |
| type loggingResponseWriter struct { | |
| http.ResponseWriter | |
| statusCode int | |
| size int | |
| } | |
| func (lrw *loggingResponseWriter) WriteHeader(code int) { | |
| lrw.statusCode = code | |
| lrw.ResponseWriter.WriteHeader(code) | |
| } | |
| func (lrw *loggingResponseWriter) Write(b []byte) (int, error) { | |
| if lrw.statusCode == 0 { | |
| lrw.statusCode = http.StatusOK | |
| } | |
| size, err := lrw.ResponseWriter.Write(b) | |
| lrw.size += size | |
| return size, err | |
| } | |
| func (l *LoggerService) LogMiddleware(next http.Handler) http.Handler { | |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| start := time.Now() | |
| lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: 0, size: 0} | |
| next.ServeHTTP(lrw, r) | |
| // Get client IP from headers or remote address | |
| clientIP := r.Header.Get("X-Forwarded-For") | |
| if clientIP == "" { | |
| clientIP = r.Header.Get("X-Real-IP") | |
| } | |
| if clientIP == "" { | |
| clientIP, _, _ = net.SplitHostPort(r.RemoteAddr) | |
| } | |
| // In case of multiple IPs in X-Forwarded-For, take the first one | |
| if strings.Contains(clientIP, ",") { | |
| clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) | |
| } | |
| event := l.Logger.WithLevel(zerolog.InfoLevel). | |
| Str("route", r.URL.Path). | |
| Str("method", r.Method). | |
| Int("status", lrw.statusCode). | |
| Int("response-size", lrw.size). | |
| Dur("duration", time.Since(start)). | |
| Interface("query-params", r.URL.Query()). | |
| Str("ip", clientIP). | |
| Str("user-agent", r.Header.Get("User-Agent")) | |
| event.Send() | |
| }) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment