Micro-services Using go-kit: Logging Features
Every application needs logging. Logging is very important, so you can consider it as a first class citizen. Therefore, application cannot be deployed to production without logging implementation.
Why Logging is Important
During my professional experiences, I got reports regarding why the application behaved unexpectedly. Facing this kind of situation, my first step was always looking the log files. And trace line by line to determine the root cause. Believed it or not, I was grateful because the application has a logging feature. Otherwise, I will be lost in the ocean of codes, debugging thousands line of codes to find the problem.
Go-kit Logging Feature
The very cool feature about go-kit is about having a middleware functionality. Basically, middleware is a decorator function that takes an endpoint as an input and return an endpoint. By using this functionality, we can add logging feature into our application.
Use Case
I am still in love with my lorem ipsum application. In fact, I am a little bit lazy to make another example :). Therefore, I will use it as an example (again). Also, it is a good example to show go-kit composability. I will show how easy to add logging feature into existing application. Such a plug and play feature.
Whenever you want to know about lorem application, please visit my previous article here. For this demo, I duplicate the lorem example and rename the package into lorem-logging.
Step 1: Modify service.go
Create new type inside service.go
and give it a name ServiceMiddleware
. This type is a function that takes a Service
interface as an input and return a Service
. This will be useful to make a function chain Service
call in the main function.
1 2 3 |
// create type that return function. // this will be needed in main.go type ServiceMiddleware func (Service) Service |
Step 2: Implement Logging Features
Create a file and give it a name logging.go
. Inside this file, we will create a type and named it loggingMiddleware. This type has two properties, which are Service
and Logger
.
1 2 3 4 5 6 |
// Make a new type and wrap into Service interface // Add logger property to this type type loggingMiddleware struct { Service logger log.Logger } |
Next one is to create a function to return ServiceMiddleware
function (see Step 1). This function take one input parameter, which is Logger
variable.
1 2 3 4 5 6 |
// implement function to return ServiceMiddleware func LoggingMiddleware(logger log.Logger) ServiceMiddleware { return func(next Service) Service { return loggingMiddleware{next, logger} } } |
And the last step for logging functionality is implementing Service functions. Inside each function implementation, we will log function name, input values, output value and how long the function invocation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Implement Service Interface for LoggingMiddleware func (mw loggingMiddleware) Word(min, max int) (output string) { defer func(begin time.Time){ mw.logger.Log( "function","Word", "min", min, "max", max, "result", output, "took", time.Since(begin), ) }(time.Now()) output = mw.Service.Word(min,max) return } // and the rest for sentence and paragraph |
Step 3: Modify main.go
After finishing log functionality, next step is wiring it into main.go
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
func main() { ctx := context.Background() errChan := make(chan error) // Logging domain. var logger log.Logger { logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("caller", log.DefaultCaller) } var svc lorem_logging.Service svc = lorem_logging.LoremService{} svc = lorem_logging.LoggingMiddleware(logger)(svc) endpoint := lorem_logging.Endpoints{ LoremEndpoint: lorem_logging.MakeLoremLoggingEndpoint(svc), } r := lorem_logging.MakeHttpHandler(ctx, endpoint, logger) // HTTP transport go func() { fmt.Println("Starting server at port 8080") handler := r errChan <- http.ListenAndServe(":8080", handler) }() go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errChan <- fmt.Errorf("%s", <-c) }() fmt.Println(<- errChan) } |
See line 15 to wire log functionality. As simple as that.
Step 4: Run
Now it is time to run our main.go file. Go to shell command and run it just like the previous one. If everything goes well, you will see similar output:
1 2 3 4 5 |
# Sample output ts=2017-03-04T16:46:42Z caller=logging.go:31 function=Word min=10 max=10 result=exhibentur took=3.54µs ts=2017-03-04T16:46:49Z caller=logging.go:45 function=Sentence min=10 max=10 result="Cura ob pro qui tibi inveni dum qua fit donec." took=16.795µs ts=2017-03-04T16:46:59Z caller=logging.go:45 function=Sentence min=4 max=4 result="Mortalitatis hi ea ore." took=10.51µs # End of Sample ouput |
Conclusion
What I bring into this article is about basic logging functionality, by using go-kit framework. However, to make it production ready, it is still not enough. Because it will huge number of logs data that will cause problem to analyze manually. Therefore, in my opinion, the log analyzer is still needed. The ELK stack would be a great tool to do that.
I want to write another article about integrating go-kit logging features with ELK stack. But it will take some time to make a simulation between those two. I am still looking for the Vagrant option to do that. Hopefully, I can bring this topic in the near future.
Meanwhile, this is my post for today. As usual, you can see the sample on my github, under folder lorem-logging
. Have a blessed day!
PS: I would like to see another options about this ELK thing. Whenever you guys have any idea, just comment or email me. Would be very much appreciated.
One thought on “Micro-services Using go-kit: Logging Features”