Intro: Using LogAction
March 1, 2024 ยท View on GitHub
This tutorial is an introduction to co-log. It contains basic examples of
using co-log's core data types and functions.
You can run this tutorial by executing the following command:
cabal new-run tutorial-intro --flag=tutorial
Preamble: imports and language extensions
Since this is a literate Haskell file, we need to specify all our imports up front:
import Colog.Core (LogAction (..), (<&), logStringStdout)
Core data type
co-log is based on the following data type:
newtype LogAction m msg = LogAction
{ unLogAction :: msg -> m ()
}
Logging action is a function from some message of a user-defined type msg that
performs all logic inside some monad m. In the co-log library, logger is
represented as a value. With this approach, you can modify the way you do
logging by simply performing some transformations with the value you have.
Let's first look at a very basic example of simply using putStrLn for logging:
example0 :: IO ()
example0 = do
putStrLn "Example 0: First message"
putStrLn "Example 0: Second message"
Using putStrLn for logging is a very simple and basic approach for logging.
When your application grows bigger and more complex, you might want to introduce
some logging library to it. For example, you might want to do something from the
following list:
- Specify messages with a given
Severityso you can control the verbosity of the output. - Automatically print timestamps, thread IDs, and source code lines next to the log messages.
- Submit some statistics to a web server along with each log message so later you can have analytics provided by external parties.
Now, let's look at how you can use LogAction instead of putStrLn to achieve
the same goal. With co-log, you need to have a value of type LogAction that
defines how you are going to perform logging. So you configure your logging
settings separately and then pass and use this LogAction value. See the
following example for more details:
example1 :: LogAction IO String -> IO ()
example1 logger = do
unLogAction logger "Example 1: First message"
unLogAction logger "Example 1: Second message"
NOTE: this function currently does exactly the same thing as
example0. However, givenLogActioncan do many different interesting things which you can configure later in one place and automatically get the proper behavior for your whole application instead of changing the code of every function.
If you want to do logging with co-log, then one of the options (and the simplest
one) is to pass LogAction explicitly as an argument to your function. In the
example above, we are using LogAction that takes Strings as messages and
performs logging inside the IO monad.
For convenience, the library defines the useful operator <& that makes the
code more concise and simpler:
example2 :: LogAction IO String -> IO ()
example2 logger = do
logger <& "Example 2: First message"
logger <& "Example 2: Second message"
In order to do some logging, we need to pass a logger to our functions.
Here we are going to use the following LogAction:
logStringStdout :: LogAction IO String
This action uses putStrLn under the hood and just writes a given string to
stdout. In this particular case, using LogAction from co-log might seem
redundant. However, now it's much easier to replace the simple putStrLn with
something more complex and useful.
Putting it all together, we can now run our examples
main :: IO ()
main = do
let logger = logStringStdout
example0
example1 logger
example2 logger
And the output is exactly what you'd expect:
Example 0: First message
Example 0: Second message
Example 1: First message
Example 1: Second message
Example 2: First message
Example 2: Second message