Composable Contravariant Comonadic Logging Library

Version on this page:
LTS Haskell 22.17:
Stackage Nightly 2024-04-17:
Latest on Hackage:

See all snapshots co-log appears in

MPL-2.0 licensed by Dmitrii Kovanikov
Maintained by Kowainik
This version can be pinned in stack with:co-log-,5087


Hackage Stackage LTS Stackage Nightly MPL-2.0 license Build status

Logging library based on co-log-core package. Provides ready-to-go implementation of logging. This README contains How to tutorial on using this library. This tutorial explains step by step how to integrate co-log into small basic project, specifically how to replace putStrLn used for logging with library provided logging.

All code below can be compiled and run with the following commands:

$ cabal new-build co-log
$ cabal new-exec readme

Preamble: imports and language extensions

Since this is a literate haskell file, we need to specify all our language extensions and imports up front.

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}

import Colog (Message, WithLog, cmap, fmtMessage, logDebug, logInfo, logTextStdout, logWarning,
import Control.Monad.IO.Class (MonadIO, liftIO)

import Data.Semigroup ((<>))
import qualified Data.Text as Text
import qualified Data.Text.IO as TextIO

Simple IO function example

Consider the following function that reads lines from stdin and outputs different feedback depending on the line size.

processLinesBasic :: IO ()
processLinesBasic = do
    line <- TextIO.getLine
    case Text.length line of
        0 -> do
            -- here goes logging
            TextIO.putStrLn ">>>> Empty input"
        n -> do
            TextIO.putStrLn ">>>> Correct input"
            TextIO.putStrLn $ "Line length: " <> Text.pack (show n)

This code mixes application logic with logging of the steps. It’s convenient to have logging to observe behavior of the application. But putStrLn is very simple and primitive way to log things.

Using co-log library

In order to use co-log library, we need to refactor processLinesBasic function in the following way:

processLinesLog :: (WithLog env Message m, MonadIO m) => m ()
processLinesLog = do
    line <- liftIO TextIO.getLine
    case Text.length line of
        0 -> do
            -- here goes logging
            logWarning "Empty input"
        n -> do
            logDebug "Correct line"
            logInfo $ "Line length: " <> Text.pack (show n)

Let’s summarize required changes:

  1. Make type more polymorphic: (WithLog env Message m, MonadIO m) => m ()
  2. Add liftIO to all IO functions.
  3. Replace putStrLn with proper log* function.

Running actions

Let’s run both functions:

main :: IO ()
main = do

    let action = cmap fmtMessage logTextStdout
    usingLoggerT action processLinesLog

And here is how output looks like:

screenshot from 2018-09-17 20-52-01



co-log uses PVP Versioning. The changelog is available on GitHub. — Apr 18, 2020

  • #186: Support GHC-8.10.1. — Jan 19, 2020

  • #120: Improve time formatting.

    Old: 29-12-2019 22:00:00.000

    New: 29 Dec 2019 22:00:00.000 +00:00

    (by @vrom911)

  • #144: Add Windows CI check. (by @vrom911)

  • #148: Support GHC-8.8.2. (by @chshersh)

  • #119: Add new message type that allows printing messages without Severity. (by @sphaso)

  • #150: Introduce formatWith — beginner-friendly alias for formatting combinator. (by @chshersh)

  • Use chronos-1.1 as 1.0.9 is not Windows-compatible. (by @vrom911)

  • #156: Improve documentation for the Colog.Concurrent module. (by @chshersh)

  • #146: Allow ansi-terminal-0.10. (by @mpilgrem)

  • #124: Implement executable playground for concurrent logging. (by @chshersh) — May 5, 2019

  • #77: Important: Use chronos time formatter. This is a breaking change because default field map in RichMessage now contains different type representing time. If you use your custom formatter for time, you should change it. Othwerwise no observable differences in the library API usage will be noticed.

  • #103: Breaking change: make Message data type polymorhic over the type of severity.

    Migration guide: this change is done in backwards-compatible way. If you use any fields of the Message data type, you should rename them according to the following scheme:

    messageSeverity -> msgSeverity
    messageStack    -> msgStack
    messageText     -> msgText
  • Export more formatting functions to make implementation of custom formatters easier.

  • #96: Add simpleMessageAction and richMessageAction to work with Messages.

  • Use co-log-core of version

0.2.0 — Nov 15, 2018

  • #45: Introduce approach for concurrent log writing.
  • #46: Moves logStringStdout, logStringStderr, logStringHandle, withLogStringFile from Colog.Actions to Colog.Core.IO
  • #77: Remove relude from dependencies. Add HLint check to Travis CI.
  • #64: Introduce basic benchmarks.
  • #20: Add experimental support for logger rotation (see Colog.Rotation module).
  • #39: Support GHC-8.2.2 and GHC-8.6.2.


  • #37: Add bounds to all dependencies. Move Prelude to the other-modules section.


  • Initially created.