Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WithLogger monad for deriving via #44

Merged
merged 25 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,36 @@ on:
branches: main

jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: generate
uses: freckle/stack-action/generate-matrix@v5
outputs:
stack-yamls: ${{ steps.generate.outputs.stack-yamls }}

test:
runs-on: ubuntu-latest
needs: generate

strategy:
matrix:
stack-yaml:
- stack-nightly.yaml # ghc-9.4
- stack.yaml # ghc-9.2
- stack-lts-19.33.yaml # ghc-9.0
- stack-lts-18.28.yaml # ghc-8.10
- stack-lts-16.31.yaml # ghc-8.8
- stack-lts-14.27.yaml # ghc-8.6
- stack-lts-12.26.yaml # ghc-8.4
stack-yaml: ${{ fromJSON(needs.generate.outputs.stack-yamls) }}
fail-fast: false

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: freckle/stack-action@v4
with:
stack-yaml: ${{ matrix.stack-yaml }}

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: haskell/actions/hlint-setup@v2
- uses: haskell/actions/hlint-run@v2
- uses: actions/checkout@v4
- uses: haskell-actions/hlint-setup@v2
- uses: haskell-actions/hlint-run@v2
with:
fail-on: warning
path: '["src/", "tests/"]'
8 changes: 6 additions & 2 deletions Blammo.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.18
-- see: https://github.com/sol/hpack

name: Blammo
version: 1.1.3.0
version: 1.2.0.0
synopsis: Batteries-included Structured Logging library
description: Please see README.md
category: Utils
Expand Down Expand Up @@ -36,6 +36,7 @@ library
Blammo.Logging.Terminal
Blammo.Logging.Terminal.LogPiece
Blammo.Logging.Test
Blammo.Logging.WithLogger
Data.Aeson.Compat
Network.Wai.Middleware.Logging
System.Log.FastLogger.Compat
Expand All @@ -45,6 +46,7 @@ library
src
default-extensions:
DerivingStrategies
GeneralizedNewtypeDeriving
LambdaCase
NoImplicitPrelude
OverloadedStrings
Expand Down Expand Up @@ -88,6 +90,7 @@ test-suite readme
Paths_Blammo
default-extensions:
DerivingStrategies
GeneralizedNewtypeDeriving
LambdaCase
NoImplicitPrelude
OverloadedStrings
Expand All @@ -98,8 +101,8 @@ test-suite readme
Blammo
, aeson
, base <5
, lens
, markdown-unlit
, monad-logger
, mtl
, text
default-language: Haskell2010
Expand All @@ -123,6 +126,7 @@ test-suite spec
tests
default-extensions:
DerivingStrategies
GeneralizedNewtypeDeriving
LambdaCase
NoImplicitPrelude
OverloadedStrings
Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
## [_Unreleased_](https://github.com/freckle/blammo/compare/v1.1.3.0...main)
## [_Unreleased_](https://github.com/freckle/blammo/compare/v1.2.0.0...main)

## [v1.2.0.0](https://github.com/freckle/blammo/compare/v1.1.3.0...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 `withLoggerLoggingT` or `withLogger` to ensure a log flush at
application shutdown.

## [v1.1.3.0](https://github.com/freckle/blammo/compare/v1.1.2.3...v1.1.3.0)

Expand Down
66 changes: 32 additions & 34 deletions README.lhs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ All built on the well-known `MonadLogger` interface and using an efficient
```haskell
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
chris-martin marked this conversation as resolved.
Show resolved Hide resolved
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingVia #-}

module Main (module Main) where

Expand All @@ -37,9 +36,9 @@ import Data.Aeson
import Data.Text (Text)
import GHC.Generics (Generic)
import Text.Markdown.Unlit ()
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Logger (Loc, LogStr, ToLogStr (toLogStr))
import Control.Monad.Reader (asks, MonadReader, ReaderT (runReaderT))
import Control.Lens (lens)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT))
```
-->

Expand Down Expand Up @@ -142,7 +141,7 @@ setting the format to `json` will automatically enable it (with
## 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\|@<path>` |
Expand Down Expand Up @@ -199,14 +198,17 @@ runAppT app f = runLoggerLoggingT app $ runReaderT f app

If your app monad is not a transformer stack containing `LoggingT` (ex: the
[ReaderT pattern](https://www.fpcomplete.com/blog/readert-design-pattern/)), you
can implement a custom instance of `MonadLogger`:
can derive `MonadLogger` via `WithLogger`:

```haskell
data AppEnv = AppEnv
{ appLogFunc :: Loc -> LogSource -> LogLevel -> LogStr -> IO ()
{ 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
Expand All @@ -216,11 +218,8 @@ newtype App a = App
, MonadIO
, MonadReader AppEnv
)

instance MonadLogger App where
monadLoggerLog loc logSource logLevel msg = do
logFunc <- asks appLogFunc
liftIO $ logFunc loc logSource logLevel (toLogStr msg)
deriving (MonadLogger, MonadLoggerIO)
via (WithLogger AppEnv IO)

runApp :: AppEnv -> App a -> IO a
runApp env action =
Expand All @@ -237,20 +236,18 @@ app = do
action2
```

To retrieve the log function from Blammo, use `askLoggerIO` (from
`MonadLoggerIO`) with `runSimpleLoggingT` (or `runLoggerLoggingT` if you need
more customization options), when you initialize the app:
Initialize the app with `withLogger`.

```haskell
main2 :: IO ()
main2 = do
logFunc <- runSimpleLoggingT askLoggerIO
let appEnv =
AppEnv
{ appLogFunc = logFunc
-- ...
}
runApp appEnv app
main2 =
withLogger defaultLogSettings $ \logger -> do
let appEnv =
AppEnv
{ appLogger = logger
-- ...
}
runApp appEnv app
```

## Integration with RIO
Expand Down Expand Up @@ -299,11 +296,11 @@ data App = App
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
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
Expand Down Expand Up @@ -342,18 +339,19 @@ waiMiddleware app =
## Integration with Warp

```hs
import qualified Network.Wai.Handler.Warp as Warp

instance HasLogger App where
-- ...

warpSettings :: App -> Settings
warpSettings app = setOnException onEx $ defaultSettings
where
onEx _req ex =
when (defaultShouldDisplayException ex)
$ runLoggerLoggingT app
when (Warp.defaultShouldDisplayException ex)
$ runWithLogger app
$ logError
$ "Warp exception"
:# ["exception" .= displayException ex]
$ "Warp exception" :# ["exception" .= displayException ex]
```

## Integration with Yesod
Expand All @@ -366,7 +364,7 @@ instance Yesod App where
-- ...

messageLoggerSource app _logger loc source level msg =
runLoggerLoggingT app $ monadLoggerLog loc source level msg
chris-martin marked this conversation as resolved.
Show resolved Hide resolved
runWithLogger app $ monadLoggerLog loc source level msg
```

---
Expand Down
5 changes: 3 additions & 2 deletions package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Blammo
version: 1.1.3.0
version: 1.2.0.0
maintainer: Freckle Education
category: Utils
github: freckle/blammo
Expand Down Expand Up @@ -37,6 +37,7 @@ dependencies:

default-extensions:
- DerivingStrategies
- GeneralizedNewtypeDeriving
- LambdaCase
- NoImplicitPrelude
- OverloadedStrings
Expand Down Expand Up @@ -91,7 +92,7 @@ tests:
dependencies:
- Blammo
- aeson
- lens
- markdown-unlit
- monad-logger
- mtl
- text
34 changes: 12 additions & 22 deletions src/Blammo/Logging.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Blammo.Logging
, setLogSettingsConcurrency
, Logger
, HasLogger (..)
, withLogger
, newLogger
, runLoggerLoggingT

Expand All @@ -29,10 +30,12 @@ module Blammo.Logging
, myThreadContext
, Pair

-- ** Transformer
-- ** Transformers
, MonadLogger (..)
, MonadLoggerIO (..)
, LoggingT
, WithLogger (..)
, runWithLogger

-- ** Common logging functions

Expand All @@ -54,36 +57,23 @@ module Blammo.Logging
, logOtherNS
) where

import Prelude

import Blammo.Logging.LogSettings
import Blammo.Logging.Logger
import Control.Lens ((^.))
import Blammo.Logging.WithLogger
import Control.Lens (view)
import Control.Monad.Catch (MonadMask)
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Control.Monad.Logger.Aeson
import Data.Aeson (Series)
import Data.Aeson.Types (Pair)
import Data.ByteString (ByteString)
import UnliftIO.Exception (finally)

-- | Initialize logging, pass a 'Logger' to the callback, and clean up at the end.
--
-- Applications should avoid calling this more than once in their lifecycle.
runLoggerLoggingT
:: (MonadUnliftIO m, HasLogger env) => env -> LoggingT m a -> m a
runLoggerLoggingT env f = (`finally` flushLogStr logger) $ do
runLoggingT
(filterLogger (getLoggerShouldLog logger) f)
(loggerOutput logger $ getLoggerReformat logger)
runLoggerLoggingT env f =
runLoggingT f (runLogAction logger) `finally` flushLogStr logger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooo, I like how this un-sectioned the finally 🤩

where
logger = env ^. loggerL

loggerOutput
:: Logger
-> (LogLevel -> ByteString -> ByteString)
-> Loc
-> LogSource
-> LogLevel
-> LogStr
-> IO ()
loggerOutput logger reformat =
defaultOutputWith $ defaultOutputOptions $ \logLevel bytes -> do
pushLogStrLn logger $ toLogStr $ reformat logLevel bytes
logger = view loggerL env
Loading
Loading