Add a logger¶
Our application currently prints the "Starting HTTP server" message to standard out, and errors to standard error. Both, standard out and error are also a form of global state. We should print to a logger object.
We'll use Zap in this section of the tutorial but you should be able to use any logging system.
-
Provide a Zap logger to the application. In this tutorial, we'll use
zap.NewExample, but for real applications, you should usezap.NewProductionor build a more customized logger.fx.Provide( NewHTTPServer, NewServeMux, NewEchoHandler, zap.NewExample, ), -
Add a field to hold the logger on
EchoHandler, and inNewEchoHandleradd a new logger argument to set this field.type EchoHandler struct { log *zap.Logger } func NewEchoHandler(log *zap.Logger) *EchoHandler { return &EchoHandler{log: log} } -
In the
EchoHandler.ServeHTTPmethod, use the logger instead of printing to standard error.func (h *EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if _, err := io.Copy(w, r.Body); err != nil { h.log.Warn("Failed to handle request", zap.Error(err)) } } -
Similarly, update
NewHTTPServerto expect a logger and log the "Starting HTTP server" message to that.func NewHTTPServer(lc fx.Lifecycle, mux *http.ServeMux, log *zap.Logger) *http.Server { srv := &http.Server{Addr: ":8080", Handler: mux} lc.Append(fx.Hook{ OnStart: func(ctx context.Context) error { ln, err := net.Listen("tcp", srv.Addr) if err != nil { return err } log.Info("Starting HTTP server", zap.String("addr", srv.Addr)) go srv.Serve(ln) -
(Optional) You can use the same Zap logger for Fx's own logs as well.
func main() { fx.New( fx.WithLogger(func(log *zap.Logger) fxevent.Logger { return &fxevent.ZapLogger{Logger: log} }),This will replace the
[Fx]messages with messages printed to the logger. -
Run the application.
{"level":"info","msg":"provided","constructor":"main.NewHTTPServer()","type":"*http.Server"} {"level":"info","msg":"provided","constructor":"main.NewServeMux()","type":"*http.ServeMux"} {"level":"info","msg":"provided","constructor":"main.NewEchoHandler()","type":"*main.EchoHandler"} {"level":"info","msg":"provided","constructor":"go.uber.org/zap.NewExample()","type":"*zap.Logger"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.New.func1()","type":"fx.Lifecycle"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).shutdowner-fm()","type":"fx.Shutdowner"} {"level":"info","msg":"provided","constructor":"go.uber.org/fx.(*App).dotGraph-fm()","type":"fx.DotGraph"} {"level":"info","msg":"initialized custom fxevent.Logger","function":"main.main.func1()"} {"level":"info","msg":"invoking","function":"main.main.func2()"} {"level":"info","msg":"OnStart hook executing","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer"} {"level":"info","msg":"Starting HTTP server","addr":":8080"} {"level":"info","msg":"OnStart hook executed","callee":"main.NewHTTPServer.func1()","caller":"main.NewHTTPServer","runtime":"6.292µs"} {"level":"info","msg":"started"} -
Post a request to it.
$ curl -X POST -d 'hello' http://localhost:8080/echo hello
What did we just do?
We added another component to the application with fx.Provide,
and injected that into other components that need to print messages.
To do that, we only had to add a new parameter to the constructors.
In the optional step,
we told Fx that we'd like to provide a custom logger for Fx's own operations.
We used the existing fxevent.ZapLogger to build this custom logger from our
injected logger, so that all logs follow the same format.