Blammo
Batteries-included Structured Logging library
https://github.com/freckle/blammo#readme
| LTS Haskell 24.17: | 2.1.3.0 | 
| Stackage Nightly 2025-10-31: | 2.1.3.0 | 
| Latest on Hackage: | 2.1.3.0 | 
Blammo-2.1.3.0@sha256:1ebab378041b1786fffbc06ebdbd31483175d4531a27859f4ef6a63dd14b5cd2,4794Module documentation for 2.1.3.0
- Blammo
- Data- Data.Aeson
 
- System- System.Log- System.Log.FastLogger
 
 
- System.Log
Blammo

Blammo is a Structured Logging library that’s
- Easy to use: one import and go!
- Easy to configure: environment variable parsing out of the box!
- Easy to integrate: see below for Amazonka, Yesod, and more!
- Produces beautiful, colorful output in development
- Produces fast-fast JSON in production
All built on the well-known MonadLogger interface and using an efficient
fast-logger implementation.
It’s better than bad, it’s good!
Simple Usage
import Blammo.Logging.Simple
Throughout your application, you should write against the ubiquitous
MonadLogger interface:
action1 :: MonadLogger m => m ()
action1 = do
  logInfo "This is a message sans details"
And make use of monad-logger-aeson for structured
details:
data MyError = MyError
  { code :: Int
  , messages :: [Text]
  }
  deriving stock Generic
  deriving anyclass ToJSON
action2 :: MonadLogger m => m ()
action2 = do
  logError $ "Something went wrong" :# ["error" .= MyError 100 ["x", "y"]]
  logDebug "This won't be seen in default settings"
When you run your transformer stack, wrap it in runLoggerLoggingT providing
any value with a HasLogger instance (such as your main App). The Logger
type itself has such an instance, and we provide runSimpleLoggingT for the
simplest case: it creates one configured via environment variables and then
calls runLoggerLoggingT with it.
You can use withThreadContext (from monad-logger-aeson) to add details that
will appear in all the logged messages within that scope. Placing one of these
at the very top-level adds details to all logged messages.
runner :: LoggingT IO a -> IO a
runner = runSimpleLoggingT . withThreadContext ["app" .= ("example" :: Text)]
main :: IO ()
main = runner $ do
  action1
  action2
The defaults are good for CLI applications, producing colorful output (if connected to a terminal device) suitable for a human:

Under the hood, Logging.Settings.Env is using envparse to
configure logging through environment variables. See that module for full
details. One thing we can adjust is LOG_LEVEL:

In production, you will probably want to set LOG_FORMAT=json and ship logs to
some aggregator like Datadog or Mezmo (formerly LogDNA):

Multiline Format
With the terminal formatter, a log message that is more than 120 visible characters will break into multi-line format:

This breakpoint can be controlled with LOG_BREAKPOINT. Set an unreasonably
large number to disable this feature.
Out of Order Messages
Blammo is built on fast-logger, which offers concurrent logging through multiple buffers. This concurrent logging is fast, but may deliver messages out of order. You want this on production: your aggregator should be inspecting the message’s time-stamp to re-order as necessary on the other side. However, this can be problematic in a CLI, where there is both little need for such high performance and a lower tolerance for the confusion of out of order messages.
For this reason, the default behavior is to not use concurrent logging, but
setting the format to json will automatically enable it (with
{number-of-cores} as the value). To handle this explicitly, set
LOG_CONCURRENCY.
Configuration
| Setting | Setter | Environment variable and format | 
|---|---|---|
| Format | setLogSettingsFormat | LOG_FORMAT=tty|json | 
| Level(s) | setLogSettingsLevels | LOG_LEVEL=<level>[,<source:level>,...] | 
| Destination | setLogSettingsDestination | LOG_DESTINATION=stdout|stderr|null|@<path> | 
| Color | setLogSettingsColor  | LOG_COLOR=auto|always|never | 
| Breakpoint | setLogSettingsBreakpoint | LOG_BREAKPOINT=<number> | 
| Concurrency | setLogSettingsConcurrency | LOG_CONCURRENCY=<number> | 
Advanced Usage
Add our environment variable parser to your own,
data AppSettings = AppSettings
  { appDryRun :: Bool
  , appLogSettings :: LogSettings
  , -- ...
  }
loadAppSettings :: IO AppSettings
loadAppSettings = Env.parse id $ AppSettings
  <$> var switch "DRY_RUN" mempty
  <*> LogSettingsEnv.parser
  <*> -- ...
Load a Logger into your App type and define HasLogger,
data App = App
  { appSettings :: AppSettings
  , appLogger :: Logger
  , -- ...
  }
instance HasLogger App where
  loggerL = lens appLogger $ \x y -> x { appLogger = y }
loadApp :: IO App
loadApp = do
  appSettings <- loadAppSettings
  appLogger <- newLogger $ appLogSettings appSettings
  -- ...
  pure App {..}
Use runLoggerLoggingT,
runAppT :: App -> ReaderT App (LoggingT IO) a -> IO a
runAppT app f = runLoggerLoggingT app $ runReaderT f app
Use without LoggingT
If your app monad is not a transformer stack containing LoggingT (ex: the
ReaderT pattern), you
can derive MonadLogger via WithLogger:
data AppEnv = AppEnv
  { appLogger :: Logger
  -- ...
  }
instance HasLogger AppEnv where
  loggerL = lens appLogger $ \x y -> x {appLogger = y}
newtype App a = App
  { unApp :: ReaderT AppEnv IO a }
  deriving newtype
    ( Functor
    , Applicative
    , Monad
    , MonadIO
    , MonadReader AppEnv
    )
  deriving (MonadLogger, MonadLoggerIO)
    via (WithLogger AppEnv IO)
runApp :: AppEnv -> App a -> IO a
runApp env action =
  runReaderT (unApp action) env
In your app you can use code written against the MonadLogger interface, like
the actions defined earlier:
app :: App ()
app = do
  action1
  action2
Initialize the app with withLogger.
main2 :: IO ()
main2 =
  withLogger defaultLogSettings $ \logger -> do
    let appEnv =
          AppEnv
            { appLogger = logger
            -- ...
            }
    runApp appEnv app
Integration with RIO
data App = App
  { appLogFunc :: LogFunc
  , -- ...
  }
instance HasLogFuncApp where
  logFuncL = lens appLogFunc $ \x y -> x { logFunc = y }
runApp :: MonadIO m => RIO App a -> m a
runApp f = runSimpleLoggingT $ do
  loggerIO <- askLoggerIO
  let
    logFunc = mkLogFunc $ \cs source level msg -> loggerIO
      (callStackLoc cs)
      source
      (fromRIOLevel level)
      (getUtf8Builder msg)
  app <- App logFunc
    <$> -- ...
    <*> -- ...
  runRIO app $ f
callStackLoc :: CallStack -> Loc
callStackLoc = undefined
fromRIOLevel :: RIO.LogLevel -> LogLevel
fromRIOLevel = undefined
Integration with Amazonka
data App = App
  { appLogger :: Logger
  , appAWS :: AWS.Env
  }
instance HasLogger App where
  -- ...
runApp :: MonadUnliftIO m => ReaderT App m a -> m a
runApp f =
  withLogger defaultLogSettings $ \logger -> do
    aws <- runWithLogger logger awsDiscover
    runReaderT f $ App logger aws
awsDiscover :: (MonadIO m, MonadLoggerIO m) => m AWS.Env
awsDiscover = do
  loggerIO <- askLoggerIO
  env <- liftIO $ AWS.newEnv AWS.discover
  pure $ env
    { AWS.envLogger = \level msg -> do
      loggerIO
        defaultLoc -- TODO: there may be a way to get a CallStack/Loc
        "Amazonka"
        (case level of
          AWS.Info -> LevelInfo
          AWS.Error -> LevelError
          AWS.Debug -> LevelDebug
          AWS.Trace -> LevelOther "trace"
        )
        (toLogStr msg)
    }
Changes
Unreleased
v2.1.3.0
- Add setLogSettingsColorsto support customizing colors
v2.1.2.0
- Add setLoggerReformat
v2.1.1.0
- Accept special value nullforLOG_DESTINATIONas a synonym for the null device (/dev/nullor\\.\NULon windows).
v2.1.0.0
Removes less frequently used definitions from the main Blammo.Logging module
into other modules.
- Moved from Blammo.Loggingto new moduleBlammo.Logging.ThreadContext:MonadMask,withThreadContext,myThreadContext,Pair.
- Removed from Blammo.Logging(still available inBlammo.Logging.LogSettings):LogSettings,LogDestination (..),LogFormat (..),defaultLogSettings,LogColor (..),setLogSettingsLevels,setLogSettingsDestination,setLogSettingsFormat,setLogSettingsColor,setLogSettingsBreakpoint,setLogSettingsConcurrency.
- Moved from Blammo.Loggingto new moduleBlammo.Logging.Setup:HasLogger (..),withLogger,newLogger,runLoggerLoggingT,LoggingT,WithLogger (..),runWithLogger
Blammo.Logging.Simple has been expanded to include reëxports of:
- Blammo.Logging.LogSettings
- Blammo.Logging.Setup
- Blammo.Logging.ThreadContext
v2.0.0.0
- Remove module Network.Wai.Middleware.Logging. It is moved to a new package,Blammo-wai.
v1.2.1.0
- Add Blammo.Logging.Simple.withLoggerEnv
v1.2.0.0
- New in Blammo.Logging:withLogger,WithLogger(..), runWithLogger
- New in Blammo.Logging.Logger:runLogAction
- WAI middleware no longer performs a log flush. Wrap your entire application
in either withLoggerLoggingTorwithLoggerto ensure a log flush at application shutdown.
v1.1.3.0
- Update fast-logger to fix log flushing bug, and remove 0.1s delay that was introduced as a workaround.
v1.1.2.3
- Add small delay (0.1s) in flushLoggerto work around fast-logger bug
v1.1.2.2
- 
Don’t automatically colorize if TERM=dumbis found in ENV
- 
Respect NO_COLOR
- 
Automatically adjust log concurrency based on LOG_FORMAT:Disable concurrency for tty(making that the new default) and enable it forjson. SettingLOG_CONCURRENCYwill still be respected.
v1.1.2.1
- Add various getColors*helper functions
v1.1.2.0
- Add Blammo.Logging.LogSettings.LogLevels
v1.1.1.2
- Fix bug in LOG_CONCURRENCYparser
v1.1.1.1
- Add getLogSettingsConcurrency
- Add getLoggerShouldColor
- Add pushLoggerStr&pushLoggerStrLn
- Add getLoggerLogSettings
v1.1.1.0
- Terminal formatter: align attributes vertically if the message goes over a certain number of characters (default 120).
- Adds {get,set}LogSettingsBreakpointandLOG_BREAKPOINTparsing
v1.1.0.0
- Add flushLogger
- Ensure log is flushed even on exceptions.
v1.0.3.0
- Add Env.{parse,parser}Withfunctions for parsing ‘LogSettings’ from environment variables with custom defaults.
v1.0.2.3
- Fix for localhost clientIpvalue inrequestLogger(#18)
v1.0.2.2
- Support down to LTS 12.26 / GHC 8.4
v1.0.2.1
- Add configurability to requestLogger, setLogSourceby default
- Add ability to capture and retrieve logged messages, for testing
v1.0.1.1
- Add addThreadContextFromRequest, a waiMiddlewarefor adding context using information from theRequest.
v1.0.0.1
- Relax lower bounds, support GHC 8.8
v1.0.0.0
First tagged release.
