go-chi / httplog

Go HTTP request logger with structured logging capabilities built on "log/slog" package
MIT License
207 stars 42 forks source link

Make logger to print stack trace in file #35

Open stepanleas opened 11 months ago

stepanleas commented 11 months ago

When an error with level panic occurs, it does not print the stack trace. It still appears in the terminal output, but is not written in the file.

{"timestamp":"2023-12-09T13:56:15.436815815+02:00","level":"ERROR","message":"Response: 500 Server Error - interface conversion: error is *fmt.wrapError, not *app_error.ApplicationError","service":"app-logger","httpRequest":{"url":"http://localhost:8000/api/v1/hotels","method":"GET","path":"/api/v1/hotels","remoteIP":"[::1]:50340","proto":"HTTP/1.1","requestID":"stepan-ThinkPad-L15-Gen-2/f8pOxWi02A-000002"},"stacktrace":"#","panic":"interface conversion: error is *fmt.wrapError, not *app_error.ApplicationError","httpResponse":{"status":500,"bytes":0,"elapsed":1.042674,"body":""}}
const YYYYMMDD = "2006-01-02"

func newLogger() *httplog.Logger {
    return httplog.NewLogger("app-logger", httplog.Options{
        JSON:             true,
        LogLevel:         slog.LevelError,
        Concise:          true,
        MessageFieldName: "message",
        Tags: map[string]string{
            "version": "v1.0-81aa4244d9fc8076a",
            "env":     "dev",
        },
        QuietDownRoutes: []string{
            "/",
            "/ping",
        },
        QuietDownPeriod: 10 * time.Second,
        Writer:          newWriter(),
    })
}

func newWriter() io.Writer {
    fileNamePath := fmt.Sprintf("logs/%s.log", time.Now().UTC().Format(YYYYMMDD))
    errorLogFile, err := os.OpenFile(fileNamePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("Error opening file: ", err)
    }

    return io.MultiWriter(os.Stdout, errorLogFile)
}

In httplog.go file there is this function:

func (l *RequestLoggerEntry) Panic(v interface{}, stack []byte) {
    stacktrace := "#"
    if l.Options.JSON {
        stacktrace = string(stack)
    }
    l.Logger = *l.Logger.With(
        slog.Attr{
            Key:   "stacktrace",
            Value: slog.StringValue(stacktrace)},
        slog.Attr{
            Key:   "panic",
            Value: slog.StringValue(fmt.Sprintf("%+v", v)),
        })

    l.msg = fmt.Sprintf("%+v", v)

    if !l.Options.JSON {
        middleware.PrintPrettyStack(v)
    }
}

I examined a bit and it seems that the l.Options are not the same that we passed to httplog.NewLogger function. They come from RequestLoggerEntry struct, not from Logger struct.

Is there a way to enable it that I don't know?