Logging in Haskell

Here’s the translation of the Go logging example to Haskell:

Our logging example demonstrates how to use various logging techniques in Haskell. We’ll use the monad-logger library for structured logging.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

import Control.Monad.Logger
import System.Log.FastLogger (fromLogStr)
import Data.Time.Clock (getCurrentTime)
import qualified Data.ByteString.Char8 as BS

main :: IO ()
main = runStdoutLoggingT $ do
    -- Simple logging
    $(logInfo) "standard logger"

    -- Logging with timestamp
    time <- liftIO getCurrentTime
    $(logInfo) $ "with time: " <> showLogTime time

    -- Logging with source location
    $logInfo "with file/line"

    -- Custom logger with prefix
    let mylog msg = $(logInfo) $ "my: " <> msg
    mylog "from mylog"

    -- Changing prefix
    let ohmylog msg = $(logInfo) $ "ohmy: " <> msg
    ohmylog "from mylog"

    -- Logging to a custom target (in this case, a ByteString)
    let buflog msg = do
            let logStr = fromLogStr $ "buf: " <> msg <> "\n"
            liftIO $ BS.putStr logStr
    buflog "hello"

    -- Structured logging
    $(logInfo) $ "hi there" :# []
    $(logInfo) $ "hello again" :# ["key" .= ("val" :: String), "age" .= (25 :: Int)]

showLogTime :: Show a => a -> LogStr
showLogTime = toLogStr . show

To run this program, you’ll need to install the necessary libraries and compile the code:

$ stack build monad-logger fast-logger
$ stack ghc -- -O2 logging.hs
$ ./logging

Sample output (the date and time emitted will depend on when the example ran):

[Info] standard logger
[Info] with time: 2023-08-22 10:45:16.904141 UTC
[Info] logging.hs:22:5: with file/line
[Info] my: from mylog
[Info] ohmy: from mylog
buf: hello
[Info] hi there
[Info] hello again @(key = "val", age = 25)

In this Haskell version:

  1. We use the monad-logger library for structured logging.
  2. The OverloadedStrings and TemplateHaskell language extensions are used to enable string literals and logging macros.
  3. We simulate different logging styles, including simple logging, logging with timestamps, and logging with source location.
  4. Custom loggers with prefixes are created using functions that wrap the logging calls.
  5. For logging to a custom target (like a ByteString in the original example), we create a custom logging function that uses BS.putStr.
  6. Structured logging is achieved using the :# [] syntax for key-value pairs.

Note that Haskell’s logging ecosystem is different from Go’s, so we’ve adapted the concepts to fit Haskell’s paradigms while maintaining the spirit of the original example.