Logging at Functional Programming

Darr Mirr
5 min readMay 19, 2021

--

How to log functionally at Java

Introduction

Logs give us insight how application worked at specific time. Therefore logging is vitally important for any application. And logging framework exists almost at any language.

Today I discuss about logging practices that I use at functional coding style.

Let’s look on following code snippet:

Arithmetics calculation pipeline

I would like to log operation result after each execution step at calcPipeline. Where I should place log statements?

Usually all logs statements are placed in methods at Object-Oriented programming (OOP). You could apply such rule at Functional Programming (FP) too.

But is it functional way to put all log statements to lambda body?

Read further to get answer for this question.

What do we log?

If you get any application logs and try to categorize log messages I think you end up with two log message types:

  1. How program executes
  2. What values are computed

The first type is describe running processes at program. For example, starting scheduled job. Reading such logs we understand that program is running and could track process execution flow.

The second type is describe processes execution results. For example, math operation execution result. Reading such logs we can verify that program algorithms works correctly.

How to log function execution?

Let’s have a look what function consists of and than we dive into logging function execution.

Function consists three part:

  • Input value
  • Output value
  • Function body

Function receives input value than performs some computation at its body and return computation result as output value.

According to function structure there are 3 places to log function execution:

Three places to log

Let’s go through all log places.

Log internal process details

Function body consists of single expression or statement block. It is the place where some computation is performed. You can log some information about process details. For example, debugging information about intermediate computation results.

So, where do you place log statements at such case?

You are free to place anywhere you want according to your coding style. There is only one restriction — logger instance should be accessible and initialized at function execution time.

I have used Operation interface at article’s introduction. Here is interface implementation:

Operation interface before adding log statements

Now I add log statements to lambda body:

Log statements at lambda body

Code looks like an imperative one and there is no differents here between OOP style and FP one.

Log input and output values

Another places to log are similar to each other. Both represent the same function that receive some value as input, perform logging and return input value as output one. Here is code:

PeekLog function implementation

PeekLog interface extends supplier function and return logger instance when method get is invoked. Method of creates PeekLog function. Method log performs logging. Its implementation is inspired by peek method from java.util.stream.Stream interface.

Let’s look on code snippet from introduction part:

Code snippet from article introduction

Improve code by adding log output:

PeekLog function usage at arithmetics calculation pipeline

Using PeekLog function we can embed log statements to method’s chain at functional code style.

Logable function

I think PeekLog function is appropriate for many cases. But from my point of view there are some issues when PeekLog is used at this way:

  1. Required to use local variable
  2. Hard to read code

How to solve such issues?

The first decision is straightforward: move function composition into method in Operation interface:

PeekLog function usage at Operation interface

Let’s look at main method after code moving:

Main interface after refactoring

Code become concise and looks pretty enough at main method. But such decision have some disadvantages:

  • logging input and output value is not Operation interface resposibility due to it is out from function body scope
  • you can not reuse such decision for another function
  • we have to use local variable in order to log pipeline execution result

The second decision gets rid of disadvantages the first one. The main idea of second decision is to put function to logable context.

Let’s have a look to code:

LogableFunction usage example

Static method of defined at LogableFunction. It wrap function and make it as logable one. Logable function have additional methods to log messages before and after function execution. Therefore wrapped function gets new feature — logging input or output values.

Log output is added by function composition at LogableFunction. You can make function composition as many times as you want. Logable function is still function because it extends java.util.function.Function interface. It is very useful because you can make any Function as Logable. Let’s look on example:

Operation interface extends LogableFunction one

Conclusion

Let’s sum up article’s main points.

  • There are two common matters about log messages:
  1. How program executes
  2. What values are computed
  • There are three places to log function execution:
  1. Log function input value
  2. Log function output value
  3. Log function internal process details
  • You can use PeekLog function in order to embed log statements into method’s chain at functional code style
  • You can put your function into logable context. Function at logable context gets new feature — logging input or output values.

All code samples you can find at my repository on Github.com

--

--