DEV Community

Scott
Scott

Posted on • Updated on

Golang Dynamic Logging for Microservices

Dynamic logging is something I believe should be implemented in any micro-service environment. I suppose I should start with I mean by dynamic logging.. Simply put the following point should be covered.

  • Maintainer should be able to change the log level of the application mid-execution.

Sample Application

Using the go.uber.org/zap library LevelEnablerFunc you should be able to implement the interface using any protocol you want? (Haven't tested this).. That being said, for my sample application I will be using an HTTP interface and so I will use the zap built-in ServeHttp method.

First we need to define our logger and build and build an http router to accept log level changes. This is completed by utilizing the above mentioned ServeHttp function built into the zap.AtomicLevel object.

Setup Logger

config := zap.NewProductionEncoderConfig()
...
encoder := zapcore.NewJSONEncoder(config)
atom := zap.NewAtomicLevel()
    logr := zap.New(zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atom))
Enter fullscreen mode Exit fullscreen mode

Dynamic log level interface

mux := http.NewServeMux()
mux.Handle("/log_level", atom)
go http.ListenAndServe(":1065", mux)
Enter fullscreen mode Exit fullscreen mode

I am running a simple loop that repeats various logging messages to allow us to see the expected output. After building and running the application we can interact with it via curl.

Change logging levels

curl -X PUT -d '{"level":"debug"}' localhost:1065/log_level
curl -X PUT -d '{"level":"info"}' localhost:1065/log_level
curl -X PUT -d '{"level":"info"}' localhost:1065/  # EXPECTED 404
curl -X GET localhost:1065/log_level
Enter fullscreen mode Exit fullscreen mode

NOTE: GIF seems to be broken again... but clicking the link should show you... ill fix it later 😂

DEMO

Sample Application Code

package main

import (
    "net/http"
    "os"
    "time"

    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
    undo := initLogger()
    defer undo()

    for {
        zap.S().Info("test_logs")
        zap.S().Debug("test_logs_debug")
        time.Sleep(1000 * time.Millisecond)
    }
}

func initLogger() func() {
    config := zap.NewProductionEncoderConfig()
    config.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
        nanos := t.UnixNano()
        millis := nanos / int64(time.Millisecond)
        enc.AppendInt64(millis)
    }
    encoder := zapcore.NewJSONEncoder(config)
    atom := zap.NewAtomicLevel()
    logr := zap.New(zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atom))

    mux := http.NewServeMux()
    mux.Handle("/log_level", atom)
    go http.ListenAndServe(":1065", mux)

    return zap.ReplaceGlobals(logr)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
rusty_sys_dev profile image
Scott

If anyone can enlighten me as to how to get my gif to work, I would be eternally grateful! 😢 I have it written as the following.

![APP_GIF](https://i.imgur.com/iHNIhOF.gif)

It works when I try it in hackmd..

Collapse
 
rusty_sys_dev profile image
Scott • Edited

seems the gif was too large??? 🤣 🤣
200MP max size, according to the Firefox dev console I was requesting 380MP..
reduced the size by 1% and it seemed to work... 😕