Blammo
Batteries-included Structured Logging library
https://github.com/freckle/blammo#readme
| Version on this page: | 1.1.2.1@rev:1 | 
| LTS Haskell 24.18: | 2.1.3.0 | 
| Stackage Nightly 2025-11-03: | 2.1.3.0 | 
| Latest on Hackage: | 2.1.3.0 | 
Blammo-1.1.2.1@sha256:d27e58bedabb513e4beb4ae36b1938c4b43e16878da34db841a3a1c3f6dc6e8a,4886Module documentation for 1.1.2.1
- Blammo
 - Data
- Data.Aeson
 
 - Network
- Network.Wai
- Network.Wai.Middleware
 
 
 - Network.Wai
 - 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. By default, it uses {number-of-processors} 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 such cases, you can set LOG_CONCURRENCY=1 to use a single buffer.
Configuration
| Setting | Setter | Environment variable and format | 
|---|---|---|
| Level(s) | setLogSettingsLevels | 
LOG_LEVEL=<level>[,<source:level>,...] | 
| Destination | setLogSettingsDestination | 
LOG_DESTINATION=stdout|stderr|@<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
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 :: ReaderT App (LoggingT IO) a -> IO a
runApp f = do
  logger <- newLogger defaultLogSettings
  app <- App logger <$> runLoggerLoggingT logger awsDiscover
  runLoggerLoggingT app $ runReaderT f app
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)
    }
Integration with WAI
import Network.Wai.Middleware.Logging
instance HasLogger App where
  -- ...
waiMiddleware :: App -> Middleware
waiMiddleware app =
  addThreadContext ["app" .= ("my-app" :: Text)]
    $ requestLogger app
    $ defaultMiddlewaresNoLogging
Integration with Warp
instance HasLogger App where
  -- ...
warpSettings :: App -> Settings
warpSettings app = setOnException onEx $ defaultSettings
 where
  onEx _req ex =
    when (defaultShouldDisplayException ex)
      $ runLoggerLoggingT app
      $ logError
      $ "Warp exception"
      :# ["exception" .= displayException ex]
Integration with Yesod
instance HasLogger App where
  -- ...
instance Yesod App where
  -- ...
  messageLoggerSource app _logger loc source level msg =
    runLoggerLoggingT app $ monadLoggerLog loc source level msg
Changes
Unreleased
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.