net/http set custom logger

Issue

I would like to log errors from net/http in my own format. In net/http package I have found Server struct:

type Server struct {
        //...
        ErrorLog *log.Logger
}

I would like to substitute logger with my own implementation:

type AppLogger struct {
    log *zap.SugaredLogger
}

func (l *AppLogger) Error(message string, keyAndValues ...interface{}) {
    l.log.Errorw(message, keyAndValues...)
}

What is the correct way of implementing this?


Update:

I have zap logger with following config:

cfg := zap.Config{
    Encoding:         encoding,
    Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
    OutputPaths:      []string{"stdout"},
    ErrorOutputPaths: []string{"stdout"},
    EncoderConfig:    encCfg,
}
logger, err := cfg.Build()

It configured to write in json format. I would like errors from net/http be written in the same way as zap. I create following:

type serverJsonWriter struct {
    io.Writer
}

// ListenAndServeTLS - with custom log Writer
func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler) error {
    server := &http.Server{
        Addr: addr,
        Handler: handler,
        ErrorLog: logger.New(serverJsonWriter{}, "", 0),
    }
}

func (w serverJsonWriter) Write(p []byte) (n int, err error){
    // {"error":{"type":"net/http error","message":"header too long"}}
}

Questions:

  1. What should be the body of serverJsonWriter method?
  2. Should I retrieve zap io.Writer in order to pass it log.Logger? How to do this?

Solution

This is easily doable, because the log.Logger type guarantees that each log message is delivered to the destination io.Writer with a single Writer.Write() call:

Each logging operation makes a single call to the Writer’s Write method. A Logger can be used simultaneously from multiple goroutines; it guarantees to serialize access to the Writer.

So basically you just need to create a type which implements io.Writer, and whose Write() method simply calls your logger.

Here’s a simple implementation which does that:

type fwdToZapWriter struct {
    logger *zap.SugaredLogger
}

func (fw *fwdToZapWriter) Write(p []byte) (n int, err error) {
    fw.logger.Errorw(string(p))
    return len(p), nil
}

And that’s all. You can “install” this writer at your http.Server like this:

server := &http.Server{
    Addr:     addr,
    Handler:  handler,
    ErrorLog: logger.New(&fwdToZapWriter{logger}, "", 0),
}

logger in the above example is from your example: logger, err := cfg.Build()

If you want, you can just as easily forward to your AppLogger instead of logger.

See similar question: Go: Create io.Writer inteface for logging to mongodb database

Answered By – icza

Answer Checked By – Candace Johnson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.