co-log 0.4.0.1 → 0.7.0.0
raw patch · 22 files changed
Files
- CHANGELOG.md +100/−20
- README.lhs +59/−15
- README.md +59/−15
- co-log.cabal +87/−45
- src/Colog.hs +1/−4
- src/Colog/Actions.hs +33/−13
- src/Colog/Concurrent.hs +0/−343
- src/Colog/Concurrent/Internal.hs +0/−42
- src/Colog/Contra.hs +1/−2
- src/Colog/Message.hs +40/−105
- src/Colog/Monad.hs +6/−4
- src/Colog/Pure.hs +6/−3
- src/Colog/Rotation.hs +2/−3
- test/Doctest.hs +22/−0
- test/Property.hs +1/−2
- tutorials/1-intro/Intro.lhs +1/−1
- tutorials/2-custom/Custom.lhs +0/−136
- tutorials/2-loggert/loggert.lhs +116/−0
- tutorials/3-loggert-with-message/loggert.lhs +117/−0
- tutorials/Concurrent.hs +1/−2
- tutorials/Main.hs +11/−13
- tutorials/custom/Custom.lhs +136/−0
CHANGELOG.md view
@@ -3,14 +3,94 @@ `co-log` uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 0.7.0.0 - April 6, 2025++## What's Changed++* Move away from `chronos`. (#277)++ **Breaking change**: Some signatures have changed from `Chronos.Time` to `Data.Time.UTCTime`.++**Full Changelog**: https://github.com/co-log/co-log/compare/v0.6.1.2...v0.7.0.0++## 0.6.1.2 - March 2, 2025++## What's Changed++* Allow `containers-0.8`.++**Full Changelog**: https://github.com/co-log/co-log/compare/v0.6.1.1...v0.6.1.2++## 0.6.1.1 - January 5, 2025++## What's Changed++* Support ghc-9.10 & ghc-9.12.++**Full Changelog**: https://github.com/co-log/co-log/compare/v0.6.1.0...v0.6.1.1++## 0.6.1.0 - Mar 1, 2024++## What's Changed++* GA(deps): Bump actions/cache from 3 to 4 by @dependabot in https://github.com/co-log/co-log/pull/273+* docs: refine the loggert tutorials by @xieyuschen in https://github.com/co-log/co-log/pull/272+* Support ghc-9.8. by @alaendle in https://github.com/co-log/co-log/pull/270++**Full Changelog**: https://github.com/co-log/co-log/compare/v0.6.0.2...v0.6.1.0++## 0.6.0.0 - Sep 18, 2023++### What's Changed++* Support GHC-9.6 - replace `typerep-map` with `dependent-map`. by @alaendle in [#264](https://github.com/co-log/co-log/pull/264)+* Support GHC 9.4. by @alaendle in [#252](https://github.com/co-log/co-log/pull/252)+* Add MonadUnliftIO instance by @newhoggy in [#240](https://github.com/co-log/co-log/pull/240)+* Update CI tested GHC versions & workaround for GHC < 9.4.5 (run-st, primitive-unlifted) by @alaendle in [#257](https://github.com/co-log/co-log/pull/257)+* Derive `MonadFail` for `LoggerT` by @alaendle in [#260](https://github.com/co-log/co-log/pull/260)+* docs: use relative path to benefit locally reading by @xieyuschen in [#253](https://github.com/co-log/co-log/pull/253)+* docs: refine readme by @xieyuschen in [#254](https://github.com/co-log/co-log/pull/254)+* tutorials: add demo for LoggerT and SimpleMsg, #84 by @xieyuschen in [#256](https://github.com/co-log/co-log/pull/256)+* docs: add a link to tutorial pages and aggregate all tutorial pages by @xieyuschen in [#259](https://github.com/co-log/co-log/pull/259)+* tutorials: add a tutorial for loggert and message by @xieyuschen in [#261](https://github.com/co-log/co-log/pull/261)+* Create tags and upload package candidates on version bumps. by @alaendle in [#265](https://github.com/co-log/co-log/pull/265)+* Added @alaendle as code owner. by @alaendle in [#258](https://github.com/co-log/co-log/pull/258)+* GA(deps): Bump actions/checkout from 3 to 4 by @dependabot in [#263](https://github.com/co-log/co-log/pull/263)++### New Contributors++* @xieyuschen made their first contribution in [#253](https://github.com/co-log/co-log/pull/253)+* @newhoggy made their first contribution in [#240](https://github.com/co-log/co-log/pull/240)++**Full Changelog**: https://github.com/co-log/co-log/compare/v0.5.0.0...v0.6.0.0++## 0.5.0.0 - Nov 2, 2022++* [#230](https://github.com/co-log/co-log/issues/230):+ Support GHC-9.2.+* Allow `mtl-2.3`.+* Allow `vector-0.13`.+* Allow `hedgehog-1.2`.+* [#187](https://github.com/co-log/co-log/issues/187):+ Remove `CoLog.Concurrent` module and executable.+* [#243](https://github.com/co-log/co-log/pull/243):+ Improve printing in multiple threads.+* Drop support for GHC-8.2, GHC-8.4, GHC-8.6, GHC-8.8++## 0.4.0.2 — <M> <d>, 2021++* [#223](https://github.com/co-log/co-log/pulls/223):+ Support GHC-9.0.1.+ Require typerep-map ^>= 0.4+ ## 0.4.0.1 — Apr 18, 2020 -* [#186](https://github.com/kowainik/co-log/issues/186):+* [#186](https://github.com/co-log/co-log/issues/186): Support GHC-8.10.1. ## 0.4.0.0 — Jan 19, 2020 -* [#120](https://github.com/kowainik/co-log/issues/120):+* [#120](https://github.com/co-log/co-log/issues/120): Improve time formatting. Old: `29-12-2019 22:00:00.000`@@ -18,39 +98,39 @@ New: `29 Dec 2019 22:00:00.000 +00:00` (by [@vrom911](https://github.com/vrom911))-* [#144](https://github.com/kowainik/co-log/issues/144):+* [#144](https://github.com/co-log/co-log/issues/144): Add Windows CI check. (by [@vrom911](https://github.com/vrom911))-* [#148](https://github.com/kowainik/co-log/issues/148):+* [#148](https://github.com/co-log/co-log/issues/148): Support GHC-8.8.2. (by [@chshersh](https://github.com/chshersh))-* [#119](https://github.com/kowainik/co-log/issues/119):+* [#119](https://github.com/co-log/co-log/issues/119): Add new message type that allows printing messages without `Severity`. (by [@sphaso](https://github.com/sphaso))-* [#150](https://github.com/kowainik/co-log/issues/150):+* [#150](https://github.com/co-log/co-log/issues/150): Introduce `formatWith` — beginner-friendly alias for formatting combinator. (by [@chshersh](https://github.com/chshersh)) * Use `chronos-1.1` as `1.0.9` is not Windows-compatible. (by [@vrom911](https://github.com/vrom911))-* [#156](https://github.com/kowainik/co-log/issues/156):+* [#156](https://github.com/co-log/co-log/issues/156): Improve documentation for the `Colog.Concurrent` module. (by [@chshersh](https://github.com/chshersh))-* [#146](https://github.com/kowainik/co-log/issues/146):+* [#146](https://github.com/co-log/co-log/issues/146): Allow `ansi-terminal-0.10`. (by [@mpilgrem](https://github.com/mpilgrem))-* [#124](https://github.com/kowainik/co-log/issues/124):+* [#124](https://github.com/co-log/co-log/issues/124): Implement executable playground for concurrent logging. (by [@chshersh](https://github.com/chshersh)) ## 0.3.0.0 — May 5, 2019 -* [#77](https://github.com/kowainik/co-log/issues/77):+* [#77](https://github.com/co-log/co-log/issues/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](https://github.com/kowainik/co-log/issues/103):+* [#103](https://github.com/co-log/co-log/issues/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@@ -62,30 +142,30 @@ messageText -> msgText ``` * Export more formatting functions to make implementation of custom formatters easier.-* [#96](https://github.com/kowainik/co-log/issues/96):+* [#96](https://github.com/co-log/co-log/issues/96): Add `simpleMessageAction` and `richMessageAction` to work with `Message`s. * Use `co-log-core` of version `0.2.0.0`. ## 0.2.0 — Nov 15, 2018 -* [#45](https://github.com/kowainik/co-log/issues/45):+* [#45](https://github.com/co-log/co-log/issues/45): Introduce approach for concurrent log writing.-* [#46](https://github.com/kowainik/co-log/issues/46):+* [#46](https://github.com/co-log/co-log/issues/46): Moves `logStringStdout`, `logStringStderr`, `logStringHandle`, `withLogStringFile` from `Colog.Actions` to `Colog.Core.IO`-* [#77](https://github.com/kowainik/co-log/issues/77):+* [#77](https://github.com/co-log/co-log/issues/77): Remove `relude` from dependencies. Add HLint check to Travis CI.-* [#64](https://github.com/kowainik/co-log/issues/64):+* [#64](https://github.com/co-log/co-log/issues/64): Introduce basic benchmarks.-* [#20](https://github.com/kowainik/co-log/issues/20):+* [#20](https://github.com/co-log/co-log/issues/20): Add experimental support for logger rotation (see `Colog.Rotation` module).-* [#39](https://github.com/kowainik/co-log/issues/39):+* [#39](https://github.com/co-log/co-log/issues/39): Support GHC-8.2.2 and GHC-8.6.2. ## 0.1.0 -* [#37](https://github.com/kowainik/co-log/issues/37):+* [#37](https://github.com/co-log/co-log/issues/37): Add bounds to all dependencies. Move `Prelude` to the `other-modules` section. @@ -94,4 +174,4 @@ * Initially created. [1]: https://pvp.haskell.org-[2]: https://github.com/kowainik/co-log/releases+[2]: https://github.com/co-log/co-log/releases
README.lhs view
@@ -1,23 +1,55 @@ # co-log -[](https://hackage.haskell.org/package/co-log)-[](http://stackage.org/lts/package/co-log)-[](http://stackage.org/nightly/package/co-log)-[](https://github.com/kowainik/co-log/blob/master/LICENSE)-[](https://travis-ci.org/kowainik/co-log)+<img align="left" width="180" height="180" src="https://user-images.githubusercontent.com/8126674/80955687-92f21a80-8df7-11ea-90d3-422dafdc8391.png"> +[](https://github.com/co-log/co-log/actions)+[](https://github.com/co-log/co-log/blob/main/LICENSE) -Logging library based on [`co-log-core`](../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.+`co-log` is a composable and configurable logging framework. It+combines all the benefits of Haskell idioms to provide a reasonable+and convenient interface. Although the library design uses some advanced+concepts in its core, we are striving to provide beginner-friendly API. The+library also provides the complete documentation with a lot of beginner-friendly+examples, explanations and tutorials to guide users. The combination of a+pragmatic approach to logging and fundamental Haskell abstractions allows us to+create a highly composable and configurable logging framework. +---++If you're interested in how different Haskell typeclasses are used to+implement core functions of `co-log`, you can read the following blog+post which goes into detail about the internal implementation specifics:++- [co-log: Composable Contravariant Combinatorial Comonadic Configurable Convenient Logging](https://kowainik.github.io/posts/2018-09-25-co-log)++## Co-Log Family++Co-Log is a family of repositories for a composable and configurable logging+framework `co-log`.++Here is the list of currently available repositories and libraries that you can+check out:++| | | |+| :----------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------- |+| [`co-log-core`](https://github.com/co-log/co-log-core) | lightweight package with basic data types and general idea which depends only on `base` | [![Hackage][hk-img-core]][hk-core] |+| [`co-log`](https://github.com/co-log/co-log) | taggless final implementation of logging library based on `co-log-core` | [![Hackage][hk-img]][hk] |+| [`co-log-polysemy`](https://github.com/co-log/co-log-polysemy) | implementation of logging library based on `co-log-core` and the [`polysemy`](http://hackage.haskell.org/package/polysemy) extensible effects library. | [![Hackage][hk-img-ps]][hk-ps] |+| [`co-log-benchmarks`](https://github.com/co-log/co-log-benchmarks) | benchmarks of the `co-log` library | - |++## `co-log` library++Logging library based on [`co-log-core`](https://github.com/co-log/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: ```shell-$ cabal new-build co-log-$ cabal new-exec readme+$ cabal build+$ cabal exec readme ``` ## Preamble: imports and language extensions@@ -29,14 +61,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} +import Control.Monad.IO.Class (MonadIO, liftIO)+ import Colog (Message, WithLog, cmap, fmtMessage, logDebug, logInfo, logTextStdout, logWarning, usingLoggerT)-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@@ -103,3 +134,16 @@ And here is how output looks like: ++## More Tutorials++To provide a more user-friendly introduction to the library, we've+created the tutorial series which introduces the main concepts behind `co-log`+smoothly, please [check more details here](./tutorials/README.md).++[hk-img]: https://img.shields.io/hackage/v/co-log.svg?logo=haskell+[hk-img-ps]: https://img.shields.io/hackage/v/co-log-polysemy.svg?logo=haskell+[hk-img-core]: https://img.shields.io/hackage/v/co-log-core.svg?logo=haskell+[hk]: https://hackage.haskell.org/package/co-log+[hk-ps]: https://hackage.haskell.org/package/co-log-polysemy+[hk-core]: https://hackage.haskell.org/package/co-log-core
README.md view
@@ -1,23 +1,55 @@ # co-log -[](https://hackage.haskell.org/package/co-log)-[](http://stackage.org/lts/package/co-log)-[](http://stackage.org/nightly/package/co-log)-[](https://github.com/kowainik/co-log/blob/master/LICENSE)-[](https://travis-ci.org/kowainik/co-log)+<img align="left" width="180" height="180" src="https://user-images.githubusercontent.com/8126674/80955687-92f21a80-8df7-11ea-90d3-422dafdc8391.png"> +[](https://github.com/co-log/co-log/actions)+[](https://github.com/co-log/co-log/blob/main/LICENSE) -Logging library based on [`co-log-core`](../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.+`co-log` is a composable and configurable logging framework. It+combines all the benefits of Haskell idioms to provide a reasonable+and convenient interface. Although the library design uses some advanced+concepts in its core, we are striving to provide beginner-friendly API. The+library also provides the complete documentation with a lot of beginner-friendly+examples, explanations and tutorials to guide users. The combination of a+pragmatic approach to logging and fundamental Haskell abstractions allows us to+create a highly composable and configurable logging framework. +---++If you're interested in how different Haskell typeclasses are used to+implement core functions of `co-log`, you can read the following blog+post which goes into detail about the internal implementation specifics:++- [co-log: Composable Contravariant Combinatorial Comonadic Configurable Convenient Logging](https://kowainik.github.io/posts/2018-09-25-co-log)++## Co-Log Family++Co-Log is a family of repositories for a composable and configurable logging+framework `co-log`.++Here is the list of currently available repositories and libraries that you can+check out:++| | | |+| :----------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------- |+| [`co-log-core`](https://github.com/co-log/co-log-core) | lightweight package with basic data types and general idea which depends only on `base` | [![Hackage][hk-img-core]][hk-core] |+| [`co-log`](https://github.com/co-log/co-log) | taggless final implementation of logging library based on `co-log-core` | [![Hackage][hk-img]][hk] |+| [`co-log-polysemy`](https://github.com/co-log/co-log-polysemy) | implementation of logging library based on `co-log-core` and the [`polysemy`](http://hackage.haskell.org/package/polysemy) extensible effects library. | [![Hackage][hk-img-ps]][hk-ps] |+| [`co-log-benchmarks`](https://github.com/co-log/co-log-benchmarks) | benchmarks of the `co-log` library | - |++## `co-log` library++Logging library based on [`co-log-core`](https://github.com/co-log/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: ```shell-$ cabal new-build co-log-$ cabal new-exec readme+$ cabal build+$ cabal exec readme ``` ## Preamble: imports and language extensions@@ -29,14 +61,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} +import Control.Monad.IO.Class (MonadIO, liftIO)+ import Colog (Message, WithLog, cmap, fmtMessage, logDebug, logInfo, logTextStdout, logWarning, usingLoggerT)-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@@ -103,3 +134,16 @@ And here is how output looks like: ++## More Tutorials++To provide a more user-friendly introduction to the library, we've+created the tutorial series which introduces the main concepts behind `co-log`+smoothly, please [check more details here](./tutorials/README.md).++[hk-img]: https://img.shields.io/hackage/v/co-log.svg?logo=haskell+[hk-img-ps]: https://img.shields.io/hackage/v/co-log-polysemy.svg?logo=haskell+[hk-img-core]: https://img.shields.io/hackage/v/co-log-core.svg?logo=haskell+[hk]: https://hackage.haskell.org/package/co-log+[hk-ps]: https://hackage.haskell.org/package/co-log-polysemy+[hk-core]: https://hackage.haskell.org/package/co-log-core
co-log.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: co-log-version: 0.4.0.1+version: 0.7.0.0 synopsis: Composable Contravariant Comonadic Logging Library description: The default implementation of logging based on [co-log-core](http://hackage.haskell.org/package/co-log-core).@@ -9,30 +9,37 @@ . * [co-log: Composable Contravariant Combinatorial Comonadic Configurable Convenient Logging](https://kowainik.github.io/posts/2018-09-25-co-log) -homepage: https://github.com/kowainik/co-log-bug-reports: https://github.com/kowainik/co-log/issues+homepage: https://github.com/co-log/co-log+bug-reports: https://github.com/co-log/co-log/issues license: MPL-2.0 license-file: LICENSE author: Dmitrii Kovanikov maintainer: Kowainik <xrom.xkov@gmail.com>-copyright: 2018-2020 Kowainik+copyright: 2018-2022 Kowainik, 2023-2025 Co-Log category: Logging, Contravariant, Comonad build-type: Simple stability: provisional extra-doc-files: CHANGELOG.md README.md-tested-with: GHC == 8.2.2- GHC == 8.4.4- GHC == 8.6.5- GHC == 8.8.3- GHC == 8.10.1+tested-with: GHC == 8.10.7+ GHC == 9.0.2+ GHC == 9.2.8+ GHC == 9.4.8+ GHC == 9.6.6+ GHC == 9.8.4+ GHC == 9.10.1+ GHC == 9.12.1 +flag tutorial+ description: Controls if tutorials get build (mainly to avoid building them on hackage).+ default: False+ source-repository head type: git- location: https://github.com/kowainik/co-log.git+ location: https://github.com/co-log/co-log.git common common-options- build-depends: base >= 4.10.1.0 && < 4.15+ build-depends: base >= 4.14 && < 4.22 ghc-options: -Wall -Wcompat@@ -49,6 +56,11 @@ ghc-options: -Wmissing-deriving-strategies if impl(ghc >= 8.10) ghc-options: -Wunused-packages+ if impl(ghc >= 9.0)+ ghc-options: -Winvalid-haddock+ if impl(ghc >= 9.2)+ ghc-options: -Wredundant-bang-patterns+ -Woperator-whitespace default-language: Haskell2010 default-extensions: ConstraintKinds@@ -66,40 +78,49 @@ common tutorial-options import: common-options- if os(windows)+ if os(windows) || !flag(tutorial) buildable: False build-depends: co-log-core+ , text - build-tool-depends: markdown-unlit:markdown-unlit+ build-tool-depends: markdown-unlit:markdown-unlit >= 0.5.0 && < 0.7 ghc-options: -pgmL markdown-unlit +common tutorial-depends+ import: tutorial-options+ build-depends: co-log+ , mtl++ library import: common-options hs-source-dirs: src exposed-modules: Colog- Colog.Actions- Colog.Concurrent- Colog.Concurrent.Internal- Colog.Contra- Colog.Message- Colog.Monad- Colog.Pure- Colog.Rotation+ Colog.Actions+ Colog.Contra+ Colog.Message+ Colog.Monad+ Colog.Pure+ Colog.Rotation - build-depends: ansi-terminal ^>= 0.10- , bytestring ^>= 0.10.8- , co-log-core ^>= 0.2.1.0- , containers >= 0.5.7 && < 0.7+ build-depends: ansi-terminal >= 1.0 && < 1.2+ , bytestring >= 0.10.8 && < 0.13+ , co-log-core ^>= 0.3+ , containers >= 0.5.7 && < 0.9 , contravariant ^>= 1.5 , directory ^>= 1.3.0- , filepath ^>= 1.4.1- , mtl ^>= 2.2.2- , stm >= 2.4 && < 2.6- , text ^>= 1.2.3- , chronos ^>= 1.1- , transformers ^>= 0.5- , typerep-map ^>= 0.3.2- , vector ^>= 0.12.0.3+ , exceptions >= 0.8.3 && < 0.11+ , filepath >= 1.4.1 && < 1.6+ , mtl >= 2.2.2 && < 2.4+ , text >= 1.2.3 && < 2.2+ , time >= 1.8 && < 1.15+ , transformers >= 0.5 && < 0.7+ , dependent-sum >= 0.7 && < 0.8+ , dependent-map >= 0.4 && < 0.5+ , unliftio-core ^>= 0.2+ , vector >= 0.12.0.3 && < 0.14+ if impl(ghc < 9.4.5)+ build-depends: run-st <= 0.1.3.0 executable play-colog import: common-options@@ -108,7 +129,7 @@ build-depends: co-log , mtl- , typerep-map+ , dependent-map ghc-options: -threaded -rtsopts@@ -130,21 +151,42 @@ build-depends: co-log , text -executable tutorial-intro- import: tutorial-options- main-is: tutorials/1-intro/Intro.lhs--executable tutorial-custom- import: tutorial-options- main-is: tutorials/2-custom/Custom.lhs- build-depends: co-log- , mtl- test-suite test-co-log import: common-options build-depends: co-log , co-log-core- , hedgehog ^>= 1.0+ , hedgehog >= 1.0 && < 1.6 hs-source-dirs: test main-is: Property.hs type: exitcode-stdio-1.0++test-suite co-log-doctest+ import: common-options+ -- Disable `doctest` on windows since it couldn't handle qualified imports reliable (which leads to errors like "Not in scope: `C.timeToOffsetDatetime'").+ if os(windows)+ buildable: False+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Doctest.hs++ build-depends: doctest >= 0.16.0 && < 0.25+ , Glob ^>= 0.10.0+ ghc-options: -threaded+++executable tutorial-intro+ import: tutorial-options+ main-is: tutorials/1-intro/Intro.lhs++executable tutorial-loggert-simple+ import: tutorial-depends+ main-is: tutorials/2-loggert/loggert.lhs+ +executable tutorial-loggert+ import: tutorial-depends+ main-is: tutorials/3-loggert-with-message/loggert.lhs++executable tutorial-custom+ import: tutorial-depends+ main-is: tutorials/custom/Custom.lhs+
src/Colog.hs view
@@ -1,7 +1,6 @@ {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> This package contains @mtl@ implementation of composable, contravariant and comonadic logging based on @co-log-core@.@@ -9,7 +8,6 @@ module Colog ( module Colog.Actions- , module Colog.Concurrent , module Colog.Core , module Colog.Message , module Colog.Monad@@ -18,7 +16,6 @@ ) where import Colog.Actions-import Colog.Concurrent import Colog.Contra () import Colog.Core import Colog.Message
src/Colog/Actions.hs view
@@ -1,7 +1,6 @@ {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> Logging actions for various text types. -}@@ -27,9 +26,10 @@ import Control.Monad.IO.Class (MonadIO (..)) import Data.Text.Encoding (encodeUtf8)-import System.IO (Handle, IOMode (AppendMode), stderr, withFile)+import System.IO (Handle, IOMode (AppendMode), stderr, stdout, withFile) import Colog.Core.Action (LogAction (..), cmapM, (>$<))+import Colog.Core.IO (logFlush) import Colog.Message (Message, defaultFieldMap, fmtMessage, fmtRichMessageDefault, upgradeMessageAction) @@ -42,19 +42,28 @@ -- ByteString ---------------------------------------------------------------------------- -{- | Action that prints 'BS.ByteString' to stdout. -}+{- | Action that prints 'BS.ByteString' to stdout.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logByteStringStdout :: MonadIO m => LogAction m BS.ByteString logByteStringStdout = LogAction $ liftIO . BS8.putStrLn {-# INLINE logByteStringStdout #-} {-# SPECIALIZE logByteStringStdout :: LogAction IO BS.ByteString #-} -{- | Action that prints 'BS.ByteString' to stderr. -}+{- | Action that prints 'BS.ByteString' to stderr.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logByteStringStderr :: MonadIO m => LogAction m BS.ByteString logByteStringStderr = logByteStringHandle stderr {-# INLINE logByteStringStderr #-} {-# SPECIALIZE logByteStringStderr :: LogAction IO BS.ByteString #-} -{- | Action that prints 'BS.ByteString' to 'Handle'. -}+{- | Action that prints 'BS.ByteString' to 'Handle'.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logByteStringHandle :: MonadIO m => Handle -> LogAction m BS.ByteString logByteStringHandle handle = LogAction $ liftIO . BS8.hPutStrLn handle {-# INLINE logByteStringHandle #-}@@ -64,7 +73,8 @@ 'Colog.Core.Action.withLogStringFile' for details. -} withLogByteStringFile :: MonadIO m => FilePath -> (LogAction m BS.ByteString -> IO r) -> IO r-withLogByteStringFile path action = withFile path AppendMode $ action . logByteStringHandle+withLogByteStringFile path action = withFile path AppendMode $ \handle ->+ action (logByteStringHandle handle <> logFlush handle) {-# INLINE withLogByteStringFile #-} {-# SPECIALIZE withLogByteStringFile :: FilePath -> (LogAction IO BS.ByteString -> IO r) -> IO r #-} @@ -72,21 +82,30 @@ -- Text ---------------------------------------------------------------------------- -{- | Action that prints 'T.Text' to stdout. -}+{- | Action that prints 'T.Text' to stdout.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logTextStdout :: MonadIO m => LogAction m T.Text-logTextStdout = LogAction $ liftIO . TIO.putStrLn+logTextStdout = logTextHandle stdout {-# INLINE logTextStdout #-} {-# SPECIALIZE logTextStdout :: LogAction IO T.Text #-} -{- | Action that prints 'T.Text' to stderr. -}+{- | Action that prints 'T.Text' to stderr.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logTextStderr :: MonadIO m => LogAction m T.Text logTextStderr = logTextHandle stderr {-# INLINE logTextStderr #-} {-# SPECIALIZE logTextStderr :: LogAction IO T.Text #-} -{- | Action that prints 'T.Text' to 'Handle'. -}+{- | Action that prints 'T.Text' to 'Handle'.+This action does not flush the output buffer.+If buffering mode is block buffering, the effect of this action can be delayed.+-} logTextHandle :: MonadIO m => Handle -> LogAction m T.Text-logTextHandle handle = LogAction $ liftIO . TIO.hPutStrLn handle+logTextHandle handle = LogAction $ \m -> liftIO . TIO.hPutStr handle $ m <> "\n" {-# INLINE logTextHandle #-} {-# SPECIALIZE logTextHandle :: Handle -> LogAction IO T.Text #-} @@ -94,7 +113,8 @@ 'Colog.Core.Action.withLogStringFile' for details. -} withLogTextFile :: MonadIO m => FilePath -> (LogAction m T.Text -> IO r) -> IO r-withLogTextFile path action = withFile path AppendMode $ action . logTextHandle+withLogTextFile path action = withFile path AppendMode $ \handle ->+ action (logTextHandle handle <> logFlush handle) {-# INLINE withLogTextFile #-} {-# SPECIALIZE withLogTextFile :: FilePath -> (LogAction IO T.Text -> IO r) -> IO r #-}
− src/Colog/Concurrent.hs
@@ -1,343 +0,0 @@-{- |-Copyright: (c) 2018-2020 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--__NOTE:__ Many thanks to Alexander Vershilov for the implementation.--For the speed reasons you may want to dump logs asynchronously.-This is especially useful when application threads are CPU-bound while logs emitting is I/O bound. This approach-allows to mitigate bottlenecks from the I/O.--When writing an application user should be aware of the tradeoffs-that concurrent log system can provide, in this module we explain-potential tradeoffs and describe if certain building blocks are-affected or not.-- 1. Unbounded memory usage - if there is no backpressure mechanism- the user threads, they may generate more logs that can be- written in the same amount of time. In those cases messages will- be accumulated in memory. That will lead to extended GC times and- application may be killed by the operating systems mechanisms.-- 2. Persistence requirement - sometimes application may want to- ensure that logs were written before it can continue. This is not- a case with concurrent log systems in general, and some logs may- be lost when application exits before dumping all logs.-- 3. Non-precise logging - sometimes it may happen that there can be- logs reordering (in case if thread was moved to another capability).--In case if your application is a subject of those problems you may-consider not using concurrent logging system in other cases concurrent-logger may be a good default for you.--}--module Colog.Concurrent- ( -- $general- -- * Simple API.- -- $simple-api- withBackgroundLogger- , defCapacity- -- * Extended API- -- $extended-api- -- ** Background worker- -- $background-worker- , BackgroundWorker- , backgroundWorkerWrite- , killBackgroundLogger- -- ** Background logger- , forkBackgroundLogger- , convertToLogAction- -- ** Worker thread- -- $worker-thread- , mkBackgroundThread- , runInBackgroundThread- -- *** Usage example- -- $worker-thread-usage- ) where--import Control.Applicative (many)-import Control.Concurrent (forkFinally, killThread)-import Control.Concurrent.STM (atomically, check, newTVarIO, readTVar, writeTVar)-import Control.Concurrent.STM.TBQueue (newTBQueueIO, readTBQueue, writeTBQueue)-import Control.Exception (bracket, finally)-import Control.Monad (forever, join)-import Control.Monad.IO.Class (MonadIO (..))-import Data.Foldable (for_)--import Colog.Concurrent.Internal (BackgroundWorker (..), Capacity (..))-import Colog.Core.Action (LogAction (..))---{- $general--Concurrent logger consists of the basic parts (see schema below).-- 1. Logger in application thread. This logger is evaluated in the- application thread and has an access to all the context available- in that thread and monad, this logger can work in any @m@.-- 2. Communication channel with backpressure support. In addition to- the channel we have a converter that puts the user message to the- communication channel. This converter works in the user thread.- Such a logger usually works in 'IO' but it's possible to make it- work in 'Control.Concurrent.STM.STM' as well. At this point library provides only 'IO'- version, but it can be lifted to any 'MonadIO' by the user.-- 3. Logger thread. This is the thread that performs actual write to- the sinks. Loggers there do not have access to the users thread- state, unless that state was passed in the message.---@-- +-------------------------+ +--------------------------------+- | | | Logger | Sink-1 |- | Application Thread | | Thread +---> |- | ----------------- | +-----------+ | | +----------------+- | | | | +---------+ | +----------------+- | +-------------+ | channel | | Shared +-----> Sink-2 |- | | application|| | +----> logger | | | |- | | logger +-----> | +---------+ | +----------------+- | +-------------+ | | | | +----------------+- | | +-----------+ | +---> Sink3 |- | | | | |- | | | +----------------+- | | | |- +-------------------------+ +--------------------------------+-@--So usually user should write the logging system in the way that all 'LogAction'-that populate and filter information should live in the application logger.-All loggers that do serialization and formatting should live in shared logger.---If more concurrency is needed it's possible to build multilayer systems:---@- +-------------+ +-------+- | application |---+ +---| sink-1|- +-------------+ | +---------+ | +-------+- +---| logger |---+- +---------+ | +-------+- +---| sink-2|- +-------+-@--In this approach application will be concurrently write logs to the logger, then-logger will be concurrently writing to all sinks.--}--{- $simple-api--Simple API provides a handy easy to use API that can be used directly-in application without dealing with internals. Based on users feedback-internal implementation of the simple API may change, especially in early-versions of the library. But the guarantee that we give is that no matter-what implementation is it will be kept with reasonable defaults and will-be applicable to a generic application.--}--{- | An exception safe way to create background logger. This method will fork-a thread that will run 'shared worker', see schema above.--@Capacity@ - provides a backpressure mechanism and tells how many messages-in flight are allowed. In most cases 'defCapacity' will work well.-See 'forkBackgroundLogger' for more details.--@LogAction@ - provides a logger action, this action does not have access to the-application state or thread info, so you should only pass methods that serialize-and dump data there.--@-main :: IO ()-main =- 'withBackgroundLogger'- 'defCapacity'- 'Colog.Actions.logByteStringStdout'- (\log -> 'Colog.Monad.usingLoggerT' log $ __do__- 'Colog.Monad.logMsg' \@ByteString "Starting application..."- 'Colog.Monad.logMsg' \@ByteString "Finishing application..."- )-@--}-withBackgroundLogger- :: MonadIO m- => Capacity -- ^ Capacity of messages to handle; bounded channel size- -> LogAction IO msg -- ^ Action that will be used in a forked thread- -> (LogAction m msg -> IO a) -- ^ Continuation action- -> IO a-withBackgroundLogger cap logger action =- bracket (forkBackgroundLogger cap logger)- killBackgroundLogger- (action . convertToLogAction)---- | Default capacity size, (4096)-defCapacity :: Capacity-defCapacity = Capacity 4096---{- $extended-api--Extended API explains how asynchronous logging is working and provides basic-building blocks for writing your own combinators. This is the part of the public-API and will not change without prior notice.--}--{- $background-worker-The main abstraction for the concurrent worker is 'BackgroundWorker'. This-is a wrapper of the thread, that has communication channel to talk to, and threadId.--Background worker may provide a backpressure mechanism, but does not provide-notification of completeness unless it's included in the message itself.--}--{- | Stop background logger thread.--The thread is blocked until background thread will finish processing-all messages that were written in the channel.--}-killBackgroundLogger :: BackgroundWorker msg -> IO ()-killBackgroundLogger bl = do- killThread (backgroundWorkerThreadId bl)- atomically $ readTVar (backgroundWorkerIsAlive bl) >>= check . not--{- $background-logger--Background logger is specialized version of the 'BackgroundWorker' process.-Instead of running any job it will accept @msg@ type-instead and process it with a single logger defined at creation time.--}--{- | Creates background logger with given @Capacity@,-takes a 'LogAction' that should describe how to write-logs.--@capacity@ - parameter tells how many in flight messages are allowed,-if that value is reached then user's thread that emits logs will be-blocked until any message will be written. Usually if value should be-chosen reasonably high and if this value is reached it means that-the application environment experience severe problems.--__N.B.__ The 'LogAction' will be run in the background-thread so that logger should not add any thread specific-context to the message.--__N.B.__ On exit, even in case of exception thread will dump all values-that are in the queue. But it will stop doing that in case if another-exception will happen.--}-forkBackgroundLogger :: Capacity -> LogAction IO msg -> IO (BackgroundWorker msg)-forkBackgroundLogger (Capacity cap) logAction = do- queue <- newTBQueueIO cap- isAlive <- newTVarIO True- tid <- forkFinally- (forever $ do- msg <- atomically $ readTBQueue queue- unLogAction logAction msg)- (\_ ->- (do msgs <- atomically $ many $ readTBQueue queue- for_ msgs $ unLogAction logAction)- `finally` atomically (writeTVar isAlive False))- pure $ BackgroundWorker tid (writeTBQueue queue) isAlive---{- | Convert a given 'BackgroundWorker msg' into a 'LogAction msg'-that will send log message to the background thread,-without blocking the thread.--If logger dies for any reason then thread that emits-logs will receive 'BlockedIndefinitelyOnSTM' exception.--You can extend result worker with all functionality available-with co-log. This logger will have an access to the thread-state.--}-convertToLogAction :: MonadIO m => BackgroundWorker msg -> LogAction m msg-convertToLogAction logger = LogAction $ \msg ->- liftIO $ atomically $ backgroundWorkerWrite logger msg--{- $worker-thread--While generic background logger is enough for the most-of the usecases, sometimes you may want even more.--There are at least two cases where that may happen:-- 1. You need to modify logger, for example different- threads wants to write to different sources. Or you- want to change lgo mechanism in runtime.-- 2. You may want to implement some notification- machinery that allows you to guarantee that your- logs were written before processing further.--In order to solve those problems worker thread abstraction-was introduced. This is a worker that accepts any action-and performs that.--}--{- | Create a background worker with a given capacity.-If capacity is reached, then the thread that tries to-write logs will be blocked.--This method is more generic than 'forkBackgroundLogger' but-it's less effective, as you have to pass entire closure to-be run and that leads to extra memory usage and indirect calls-happening.--When closed it will dump all pending messages, unless-another asynchronous exception will arrive, or synchronous-exception will happen during the logging.--}-mkBackgroundThread :: Capacity -> IO (BackgroundWorker (IO ()))-mkBackgroundThread (Capacity cap) = do- queue <- newTBQueueIO cap- isAlive <- newTVarIO True- tid <- forkFinally- (forever $ join $ atomically $ readTBQueue queue)- (\_ ->- (sequence_ =<< atomically (many $ readTBQueue queue))- `finally` atomically (writeTVar isAlive False))- pure $ BackgroundWorker tid (writeTBQueue queue) isAlive--{- | Run logger action asynchronously in the worker thread.-Logger is executed in the other thread entirely, so if-logger takes any thread related context it will be-read from the other thread.--}-runInBackgroundThread :: BackgroundWorker (IO ()) -> LogAction IO msg -> LogAction IO msg-runInBackgroundThread bt logAction = LogAction $ \msg ->- atomically $ backgroundWorkerWrite bt $ unLogAction logAction msg--{- $worker-thread-usage--Consider following example. (Leaving resource control aside).--@-data M msg = M (MVar ()) msg--notificationLogger :: MonadIO m => LoggerAction m msg -> LoggerAction m (M msg)-notificationLogger logger = 'LogAction' $ \(M lock msg) ->- (unLogger logger msg) `finally` (putMVar lock ())--example = __do__- worker <- 'mkBackgroundThread' 'defCapacity'- lock <- newEmptyMVar- -- Log message with default logger.- 'unLogger'- ('runInBackgroundThread' worker- (notificationLogger $ 'Colog.Action.withLogByteStringFile' "\/var\/log\/myapp\/log")- (M lock "my message")- -- Log message with a different logger.- 'unLogger'- ('runInBackgroundThread' worker- ('Colog.Action.withLogByteStringFile' "/var/log/myapp/log")- ("another message")- -- Block until first message is logged.- _ <- takeMVar lock-@--}
− src/Colog/Concurrent/Internal.hs
@@ -1,42 +0,0 @@-{-# LANGUAGE CPP #-}--{- |-Copyright: (c) 2018-2020 Kowainik-SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com>--__NOTE:__ Many thanks to Alexander Vershilov for the implementation.--This is internal module, use it on your own risk. The implementation here may be-changed without a version bump.--}--module Colog.Concurrent.Internal- ( BackgroundWorker (..)- , Capacity (..)- ) where--import Control.Concurrent (ThreadId)-import Control.Concurrent.STM (STM, TVar)-import Numeric.Natural (Natural)---{- | A wrapper type that carries capacity. The internal type may be-differrent for the different GHC versions.--}-#if MIN_VERSION_stm(2,5,0)-newtype Capacity = Capacity Natural-#else-newtype Capacity = Capacity Int-#endif--{- | Wrapper for the background thread that may receive messages to-process.--}-data BackgroundWorker msg = BackgroundWorker- { backgroundWorkerThreadId :: !ThreadId- -- ^ Background 'ThreadId'.- , backgroundWorkerWrite :: msg -> STM ()- -- ^ Method for communication with the thread.- , backgroundWorkerIsAlive :: TVar Bool- }
src/Colog/Contra.hs view
@@ -3,9 +3,8 @@ {-# LANGUAGE CPP #-} {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> This module contains 'LogAction' orphan instances of @contravariant@ classes. -}
src/Colog/Message.hs view
@@ -5,13 +5,11 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeFamilies #-} {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> This module contains logging messages data types along with the formatting and logging actions for them.@@ -42,6 +40,9 @@ , fmtMessage , showSeverity , showSourceLoc+ , showTime+ , showTimeOffset+ , showThreadId -- * Externally extensible message type -- ** Field of the dependent map@@ -62,33 +63,26 @@ , upgradeMessageAction ) where -import Prelude hiding (log)+import Prelude hiding (lookup, log) import Control.Concurrent (ThreadId, myThreadId) import Control.Exception (Exception, displayException) import Control.Monad.IO.Class (MonadIO (..))+import Data.Dependent.Map (DMap, fromList, lookup)+import Data.Dependent.Sum (DSum ((:=>))) import Data.Kind (Type)-import Data.Semigroup ((<>)) import Data.Text (Text)-import Data.Text.Lazy (toStrict)-import Data.TypeRepMap (TypeRepMap)-import GHC.Exts (IsList (..))-import GHC.OverloadedLabels (IsLabel (..)) import GHC.Stack (CallStack, SrcLoc (..), callStack, getCallStack, withFrozenCallStack)-import GHC.TypeLits (KnownSymbol, Symbol)+import GHC.TypeLits (Symbol) import System.Console.ANSI (Color (..), ColorIntensity (Vivid), ConsoleLayer (Foreground), SGR (..), setSGRCode)+import Type.Reflection (TypeRep, typeRep) import Colog.Core (LogAction, Severity (..), cmap) import Colog.Monad (WithLog, logMsg) -import qualified Chronos as C-import qualified Chronos.Locale.English as C+import qualified Data.Time as C import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as TB-import qualified Data.Text.Lazy.Builder.Int as TB-import qualified Data.TypeRepMap as TM-import qualified Data.Vector as Vector ---------------------------------------------------------------------------- -- Plain message@@ -105,7 +99,7 @@ data Msg sev = Msg { msgSeverity :: !sev , msgStack :: !CallStack- , msgText :: !Text+ , msgText :: Text } {- | Message data type without 'Severity'. Use 'logText' to log@@ -261,8 +255,8 @@ types. The type family is open so you can add new instances. -} type family FieldType (fieldName :: Symbol) :: Type-type instance FieldType "threadId" = ThreadId-type instance FieldType "posixTime" = C.Time+type instance FieldType "threadId" = ThreadId+type instance FieldType "utcTime" = C.UTCTime {- | @newtype@ wrapper. Stores monadic ability to extract value of 'FieldType'. @@ -295,15 +289,6 @@ unMessageField (MessageField f) = f {-# INLINE unMessageField #-} -instance (KnownSymbol fieldName, a ~ m (FieldType fieldName))- => IsLabel fieldName (a -> TM.WrapTypeable (MessageField m)) where-#if MIN_VERSION_base(4,11,0)- fromLabel field = TM.WrapTypeable $ MessageField @fieldName field-#else- fromLabel field = TM.WrapTypeable $ MessageField @_ @fieldName field-#endif- {-# INLINE fromLabel #-}- -- | Helper function to deal with 'MessageField' when looking it up in the 'FieldMap'. extractField :: Applicative m@@ -320,20 +305,20 @@ {- | Depedent map from type level strings to the corresponding types. See 'FieldType' for mapping between names and types. -}-type FieldMap (m :: Type -> Type) = TypeRepMap (MessageField m)+type FieldMap m = DMap TypeRep (MessageField m) {- | Default message map that contains actions to extract 'ThreadId' and-'C.Time'. Basically, the following mapping:+'C.UTCTime'. Basically, the following mapping: @-"threadId" -> 'myThreadId'-"posixTime" -> 'C.now'+"threadId" -> 'myThreadId'+"utcTime" -> 'C.getCurrentTime' @ -} defaultFieldMap :: MonadIO m => FieldMap m defaultFieldMap = fromList- [ #threadId (liftIO myThreadId)- , #posixTime (liftIO C.now)+ [ typeRep @"threadId" :=> MessageField (liftIO myThreadId)+ , typeRep @"utcTime" :=> MessageField (liftIO C.getCurrentTime) ] {- | Contains additional data to 'Message' to display more verbose information.@@ -342,7 +327,7 @@ -} data RichMsg (m :: Type -> Type) (msg :: Type) = RichMsg { richMsgMsg :: !msg- , richMsgMap :: {-# UNPACK #-} !(FieldMap m)+ , richMsgMap :: !(FieldMap m) } deriving stock (Functor) -- | Specialised version of 'RichMsg' that stores severity, callstack and text message.@@ -366,7 +351,7 @@ fmtRichMessageDefault :: MonadIO m => RichMessage m -> m Text fmtRichMessageDefault msg = fmtRichMessageCustomDefault msg formatRichMessage where- formatRichMessage :: Maybe ThreadId -> Maybe C.Time -> Message -> Text+ formatRichMessage :: Maybe ThreadId -> Maybe C.UTCTime -> Message -> Text formatRichMessage (maybe "" showThreadId -> thread) (maybe "" showTime -> time) Msg{..} = showSeverity msgSeverity <> time@@ -394,7 +379,7 @@ fmtSimpleRichMessageDefault :: MonadIO m => RichMsg m SimpleMsg -> m Text fmtSimpleRichMessageDefault msg = fmtRichMessageCustomDefault msg formatRichMessage where- formatRichMessage :: Maybe ThreadId -> Maybe C.Time -> SimpleMsg -> Text+ formatRichMessage :: Maybe ThreadId -> Maybe C.UTCTime -> SimpleMsg -> Text formatRichMessage (maybe "" showThreadId -> thread) (maybe "" showTime -> time) SimpleMsg{..} = time <> showSourceLoc simpleMsgStack@@ -408,88 +393,38 @@ fmtRichMessageCustomDefault :: MonadIO m => RichMsg m msg- -> (Maybe ThreadId -> Maybe C.Time -> msg -> Text)+ -> (Maybe ThreadId -> Maybe C.UTCTime -> msg -> Text) -> m Text fmtRichMessageCustomDefault RichMsg{..} formatter = do- maybeThreadId <- extractField $ TM.lookup @"threadId" richMsgMap- maybePosixTime <- extractField $ TM.lookup @"posixTime" richMsgMap- pure $ formatter maybeThreadId maybePosixTime richMsgMsg+ maybeThreadId <- extractField $ lookup (typeRep @"threadId") richMsgMap+ maybeUtcTime <- extractField $ lookup (typeRep @"utcTime") richMsgMap+ pure $ formatter maybeThreadId maybeUtcTime richMsgMsg {- | Shows time in the following format: ->>> showTime $ C.Time 1577656800-[29 Dec 2019 22:00:00.000 +00:00]+>>> showTime $ C.UTCTime (C.fromGregorian 2019 12 29) (C.secondsToDiffTime 3600 * 22)+"[29 Dec 2019 22:00:00.000 +00:00] " -}-showTime :: C.Time -> Text-showTime t =- square- $ toStrict- $ TB.toLazyText- $ builderDmyHMSz (C.timeToDatetime t)--------------------------------------------------------------------------------- Chronos extra-----------------------------------------------------------------------------+showTime :: C.UTCTime -> Text+showTime = showTimeOffset . C.utcToZonedTime C.utc -{- | Given a 'Datetime', constructs a 'Text' 'TB.Builder' corresponding to a-Day\/Month\/Year,Hour\/Minute\/Second\/Offset encoding of the given 'Datetime'.+{- | Shows time in the following format: -Example: @29 Dec 2019 22:00:00.000 +00:00@+>>> showTimeOffset $ C.utcToZonedTime (C.hoursToTimeZone (-2)) (C.UTCTime (C.fromGregorian 2019 12 29) (C.secondsToDiffTime 3600 * 22))+"[29 Dec 2019 20:00:00.000 -02:00] " -}-builderDmyHMSz :: C.Datetime -> TB.Builder-builderDmyHMSz (C.Datetime date time) =- builderDmy date- <> spaceSep- <> C.builder_HMS (C.SubsecondPrecisionFixed 3) (Just ':') time- <> spaceSep- <> C.builderOffset C.OffsetFormatColonOn (C.Offset 0)- where- spaceSep :: TB.Builder- spaceSep = TB.singleton ' '-- {- | Given a 'Date' construct a 'Text' 'TB.Builder'- corresponding to a Day\/Month\/Year encoding.-- Example: @01 Jan 2020@- -}- builderDmy :: C.Date -> TB.Builder- builderDmy (C.Date (C.Year y) m d) =- zeroPadDayOfMonth d- <> spaceSep- <> TB.fromText (C.caseMonth C.abbreviated m)- <> spaceSep- <> TB.decimal y--- zeroPadDayOfMonth :: C.DayOfMonth -> TB.Builder- zeroPadDayOfMonth (C.DayOfMonth d) =- if d < 100- then Vector.unsafeIndex twoDigitTextBuilder d- else TB.decimal d-- twoDigitTextBuilder :: Vector.Vector TB.Builder- twoDigitTextBuilder = Vector.fromList $- map (TB.fromText . T.pack) twoDigitStrings- {-# NOINLINE twoDigitTextBuilder #-}-- twoDigitStrings :: [String]- twoDigitStrings =- [ "00","01","02","03","04","05","06","07","08","09"- , "10","11","12","13","14","15","16","17","18","19"- , "20","21","22","23","24","25","26","27","28","29"- , "30","31","32","33","34","35","36","37","38","39"- , "40","41","42","43","44","45","46","47","48","49"- , "50","51","52","53","54","55","56","57","58","59"- , "60","61","62","63","64","65","66","67","68","69"- , "70","71","72","73","74","75","76","77","78","79"- , "80","81","82","83","84","85","86","87","88","89"- , "90","91","92","93","94","95","96","97","98","99"- ]+showTimeOffset :: C.ZonedTime -> Text+showTimeOffset = T.pack . C.formatTime C.defaultTimeLocale "[%d %b %Y %H:%M:%S%3Q %Ez] " ---------------------------------------------------------------------------- -- Utility functions ---------------------------------------------------------------------------- +{- | Shows a thread id in the following format:++__>>__ showThreadId <$> Control.Concurrent.myThreadId+"[ThreadId 4898] "+-} showThreadId :: ThreadId -> Text showThreadId = square . T.pack . show
src/Colog/Monad.hs view
@@ -1,15 +1,15 @@ {-# LANGUAGE InstanceSigs #-} {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> Core of the @mtl@ implementation. -} module Colog.Monad ( LoggerT (..)+ , HasLog (..) , WithLog , logMsg , logMsgs@@ -21,20 +21,22 @@ import Prelude hiding (log) import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.IO.Unlift (MonadUnliftIO (..)) import Control.Monad.Reader (MonadReader (..), ReaderT (..), asks) import Control.Monad.Trans.Class (MonadTrans (..)) import Data.Foldable (traverse_) import GHC.Stack (HasCallStack) -import Colog.Core (HasLog (..), LogAction (..), overLogAction, hoistLogAction)+import Colog.Core (HasLog (..), LogAction (..), hoistLogAction, overLogAction) {- | @newtype@ wrapper 'ReaderT' that keeps 'LogAction' in its context. -} newtype LoggerT msg m a = LoggerT { runLoggerT :: ReaderT (LogAction (LoggerT msg m) msg) m a- } deriving newtype ( Functor, Applicative, Monad, MonadIO+ } deriving newtype ( Functor, Applicative, Monad, MonadIO, MonadFail , MonadReader (LogAction (LoggerT msg m) msg)+ , MonadUnliftIO ) instance MonadTrans (LoggerT msg) where
src/Colog/Pure.hs view
@@ -1,7 +1,6 @@ {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> Pure implementation of logging action. -}@@ -16,6 +15,8 @@ , logMessagePure ) where +import Control.Monad.Catch (MonadThrow)+import Control.Monad.IO.Class (MonadIO) import Control.Monad.State (MonadState, StateT (..), modify') import Control.Monad.Trans.Class (MonadTrans) import Data.Bifunctor (second)@@ -31,7 +32,9 @@ -} newtype PureLoggerT msg m a = PureLoggerT { runPureLoggerT :: StateT (Seq msg) m a- } deriving newtype (Functor, Applicative, Monad, MonadTrans, MonadState (Seq msg))+ } deriving newtype ( Functor, Applicative, Monad, MonadTrans+ , MonadState (Seq msg), MonadFail, MonadIO, MonadThrow+ ) -- | Returns result value of 'PureLoggerT' and list of logged messages. runPureLogT :: Functor m => PureLoggerT msg m a -> m (a, [msg])
src/Colog/Rotation.hs view
@@ -1,7 +1,6 @@ {- |-Copyright: (c) 2018-2020 Kowainik+Copyright: (c) 2018-2022 Kowainik, 2023-2025 Co-Log SPDX-License-Identifier: MPL-2.0-Maintainer: Kowainik <xrom.xkov@gmail.com> Stability: experimental __NOTE:__ This functionality is not to be considered stable@@ -125,7 +124,7 @@ -- if you give it name like `node.log.4` then it returns `Just 4` logFileIndex :: FilePath -> Maybe Natural-logFileIndex path = fmap NE.tail (nonEmpty (POS.takeExtension path)) >>= readMaybe+logFileIndex path = nonEmpty (POS.takeExtension path) >>= readMaybe . NE.tail -- creates list of files with indices who are older on given Limit than the latest one getOldFiles :: Limit -> FilePath -> IO [FilePath]
+ test/Doctest.hs view
@@ -0,0 +1,22 @@+module Main(main) where++import System.FilePath.Glob (glob)+import Test.DocTest (doctest)++main :: IO ()+main = do+ sourceFiles <- glob "src/**/*.hs"+ doctest+ $ "-XConstraintKinds"+ : "-XDerivingStrategies"+ : "-XDeriveGeneric"+ : "-XGeneralizedNewtypeDeriving"+ : "-XLambdaCase"+ : "-XOverloadedStrings"+ : "-XRecordWildCards"+ : "-XScopedTypeVariables"+ : "-XStandaloneDeriving"+ : "-XTupleSections"+ : "-XTypeApplications"+ : "-XViewPatterns"+ : sourceFiles
test/Property.hs view
@@ -1,9 +1,8 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TemplateHaskell #-} -module Main where+module Main (main) where -import Data.Semigroup ((<>)) import Hedgehog (MonadGen, Property, checkSequential, discover, forAll, property, (===)) import System.Exit (exitFailure, exitSuccess) import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
tutorials/1-intro/Intro.lhs view
@@ -6,7 +6,7 @@ You can run this tutorial by executing the following command: ```shell-cabal new-run tutorial-intro+cabal new-run tutorial-intro --flag=tutorial ``` ## Preamble: imports and language extensions
− tutorials/2-custom/Custom.lhs
@@ -1,136 +0,0 @@-# Using a custom monad that stores `LogAction` inside its environment--This tutorial covers the more advanced topic of using the `co-log` library with-a custom application monad.--You can run this tutorial by executing the following command:--```shell-cabal new-run tutorial-custom-```--## Preamble: imports and language extensions--Since this is a literate Haskell file, we need to specify all our language-extensions and imports up front.--```haskell-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PatternSynonyms #-}--import Prelude hiding (log)--import Colog (pattern D, HasLog (..), pattern I, LogAction, Message, WithLog, log,- richMessageAction)-import Control.Monad.IO.Class (MonadIO)-import Control.Monad.Reader (MonadReader, ReaderT (..))-```--## Application environment--If you have a complex Haskell application, then you are likely to also have-non-trivial settings that configure your application environment. The-environment may store various parameters relevant to your application's-behavior. Interestingly, we can store a `LogAction` inside the same-environment to use it for our logging functions.--The environment for your application may look like this:--```haskell-data Env m = Env- { envServerPort :: !Int- , envLogAction :: !(LogAction m Message)- }-```--Several notes about this data type:--1. It stores different parameters, like the server port.-2. It stores a `LogAction` that can log values of the `Message` type-from `co-log` in the `m` monad.-3. `Env` is parameterized by type variable `m` which is going to be the-application monad.--The next step is to define an instance of the `HasLog` typeclass for the `Env`-data type. This instance will specify how to get and update the `LogAction`-stored inside the environment.--```haskell-instance HasLog (Env m) Message m where- getLogAction :: Env m -> LogAction m Message- getLogAction = envLogAction- {-# INLINE getLogAction #-}-- setLogAction :: LogAction m Message -> Env m -> Env m- setLogAction newLogAction env = env { envLogAction = newLogAction }- {-# INLINE setLogAction #-}-```--That's it! `co-log` requires very little boilerplate.--## Application monad--Now let's define our application monad:--```haskell-newtype App a = App- { unApp :: ReaderT (Env App) IO a- } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Env App))-```--This monad stores `Env` parameterized by the monad itself in its context.-Nothing special is required here to tell the monad how to use the logger.--## Example--`co-log` relies on the tagless final technique for writing functions. So you-define your monadic actions with the `WithLog` constraint that allows you to-perform logging:--```haskell-example :: WithLog env Message m => m ()-example = do- log D "First message..."- log I "Second message..."-```--The `WithLog` constraint has three type parameters: the application environment,-the type of the message and the monad. Function `log` takes two parameters:-the logger severity and the log message's text.--## Running example--Now we are ready to execute this action.--First, let's create an example environment:--```haskell-simpleEnv :: Env App-simpleEnv = Env- { envServerPort = 8081- , envLogAction = richMessageAction- }-```--Then we need to define a function that performs actions of type `App`:--```haskell-runApp :: Env App -> App a -> IO a-runApp env app = runReaderT (unApp app) env-```--Putting it all together, we can specialize the `WithLog` constraint to our-`App` monad and run our example.--```haskell-main :: IO ()-main = runApp simpleEnv example-```--And the output will look like this:--
+ tutorials/2-loggert/loggert.lhs view
@@ -0,0 +1,116 @@+# Simple Message and LoggerT++This tutorial will show you how to use LoggerT and Simple message to log with more information in a more flexible way.++You can run this tutorial by executing the following command:++```shell+cabal new-run tutorial-loggert-simple --flag=tutorial+```++## Preamble: imports and language extensions++Since this is a literate Haskell file, we need to specify all our language+extensions and imports up front.++```haskell+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Control.Monad.Reader+import GHC.Stack+import Prelude hiding (log)+import Data.Text (Text,append)+import Colog ( LogAction, SimpleMsg(..), usingLoggerT, LoggerT, (<&),+ WithLog, cmap, logText,fmtSimpleMessage,formatWith,+ logTextStderr,logTextStdout, getLogAction+ )+```++## LoggerT++The `LoggerT` monad transformer wraps a ReaderT that keeps `LogAction` in its context. It denotes a+computation of logging. +The `WithLog` constraint has three type parameters: the application environment,+the type of the message and the monad. The actions constraint by `WithLog` could be used as `LoggerT`.++In this example, we use a `LoggerT` monad transformer and a monadic action with `WithLog` constraint to perform log (the preferred way).++```haskell+-- logTextExample1 asks a logger from the LoggerT monad transformer, and then writes the text into the LogAction+-- it needs `HasCallStack` to print the stack information correctly+logTextExample1 :: (HasCallStack, Monad m) => LoggerT SimpleMsg m ()+logTextExample1 = + asks getLogAction >>= \logger ->+ logger <& SimpleMsg{ + simpleMsgStack = callStack+ , simpleMsgText = "this is a demo log for simple message!" + }++-- logTextExample2 logs the text down with the respective call stack information by the logger carried by env+-- logTextExample2 is an equivalent version of LoggerT as logTextExample1 with more features so we recommend you to use it+logTextExample2 :: WithLog env SimpleMsg m => m ()+logTextExample2 = do+ logText "you see the demo log for simple message again!"+```+++## Simple Message++The simple message is data type without `severity`. It contains a callstack information and a text message.++```idris+data SimpleMsg = SimpleMsg+ { simpleMsgStack :: !CallStack+ , simpleMsgText :: !Text+ }+```++When logging, simple messages require a format for transforming from simple messages to text. We can either use `formatWith` or its alias `cmap`+to combine it with the `LogAction`.++```haskell+logStdoutAction :: LogAction IO SimpleMsg+logStdoutAction = cmap fmtSimpleMessage logTextStdout++logStdErrAction :: LogAction IO SimpleMsg+logStdErrAction = formatWith fmtSimpleMessage logTextStderr+```++What's more, it's possible to define an own formatter.++```haskell+selfDefinedFmtSimpleMessage :: SimpleMsg -> Text+selfDefinedFmtSimpleMessage = append "+ self defined behavior: " . fmtSimpleMessage++logByOwnFormatterAction :: LogAction IO SimpleMsg+logByOwnFormatterAction = formatWith selfDefinedFmtSimpleMessage logTextStderr+```++## Running example++Now we are ready to execute those actions defined above.++```haskell+main :: IO ()+main = do+ usingLoggerT logStdoutAction logTextExample1+ usingLoggerT logStdoutAction logTextExample2+ usingLoggerT logStdErrAction logTextExample1+ usingLoggerT logStdErrAction logTextExample2+ usingLoggerT logByOwnFormatterAction logTextExample1+ usingLoggerT logByOwnFormatterAction logTextExample2+```++Run command `cabal new-run tutorial-loggert-simple --flag=tutorial`.++And the output will look like this:++
+ tutorials/3-loggert-with-message/loggert.lhs view
@@ -0,0 +1,117 @@+# Message and LoggerT++The previous tutorial shows how to use `LoggerT` with simple message.+This tutorial will show you how to use LoggerT together with messages with more details(e.g. severity).++You can run this tutorial by executing the following command:++```shell+cabal new-run tutorial-loggert+```++## Preamble: imports and language extensions++```haskell+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}++module Main (main) where++import Colog (+ LogAction,+ Message,+ Msg (..),+ WithLog,+ cmap,+ fmtMessage,+ formatWith,+ logError,+ logInfo,+ logTextStderr,+ logTextStdout,+ msgSeverity,+ msgText,+ richMessageAction,+ showSeverity,+ usingLoggerT,+ )++import Data.Text (Text)+import Prelude hiding (log)+```++## LoggerT++The `LoggerT` monad transformer wraps a ReaderT that keeps `LogAction` in its context. It denotes a+computation of logging. So you define your monadic actions with the `WithLog` constraint that allows you to perform logging.+The `Message` allows you to log with severity.++```haskell+example1 :: WithLog env Message m => m ()+example1 = do+ logInfo "this is a demo log for message!"++example2 :: WithLog env Message m => m ()+example2 = do+ logError "Mayday! Mayday! Here is an example of error log\n"+```++The `WithLog` constraint has three type parameters:++- `env` – application environment+- `msg` – type of the message+- `m` – monad++The actions constraint by `WithLog` could be used as `LoggerT`.++## Message++The message is parametrized by the `Severity` type through `Msg`, which is a general logging message data type.++```idris+type Message = Msg Severity+data Msg sev = Msg+ { msgSeverity :: !sev+ , msgStack :: !CallStack+ , msgText :: Text+ }+```++Hence, besides printing the callstack, as the `LoggerT` with simple message tutorial shows, this example supports printing severity and thread id as well.++```haskell+logStdoutAction :: LogAction IO Message+logStdoutAction = cmap fmtMessage logTextStdout++fmtMessageWithoutSourceLoc :: Message -> Text+fmtMessageWithoutSourceLoc Msg{..} =+ showSeverity msgSeverity+ <> msgText++logStdErrActionWithoutStackAction :: LogAction IO Message+logStdErrActionWithoutStackAction = formatWith fmtMessageWithoutSourceLoc logTextStderr+```++## Running example++Now we are ready to execute those actions defined above.++```haskell+main :: IO ()+main = do+ usingLoggerT logStdoutAction example1+ usingLoggerT logStdoutAction example2+ usingLoggerT logStdErrActionWithoutStackAction example1+ usingLoggerT logStdErrActionWithoutStackAction example2+ usingLoggerT richMessageAction example1+ usingLoggerT richMessageAction example2+```++Run command `cabal new-run tutorial-loggert --flag=tutorial`, and the output will look like this:++
tutorials/Concurrent.hs view
@@ -1,4 +1,4 @@-{- | The purpose of this module is to check concurrent abilities of @colog@.+{- | The purpose of this module is to check concurrent abilities of @co-log@. -} module Main (main) where @@ -6,7 +6,6 @@ import Control.Monad (forM_, replicateM_, void) import Data.ByteString (ByteString) import Data.ByteString.Char8 (pack)-import Data.Semigroup ((<>)) import Prelude hiding (log) import Colog (LogAction, logByteStringStderr, logByteStringStdout, (<&))
tutorials/Main.hs view
@@ -11,7 +11,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeSynonymInstances #-} module Main (main) where @@ -21,17 +20,16 @@ import Control.Exception (Exception) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Reader (MonadReader, ReaderT (..))-import Data.Semigroup ((<>)) -import Colog (pattern D, HasLog (..), LogAction, Message, Msg (..), PureLogger, RichMsg (..),- SimpleMsg (..), WithLog, cmap, cmapM, defaultFieldMap, fmtMessage,- fmtRichMessageDefault, fmtSimpleRichMessageDefault, liftLogIO, log, logException,- logInfo, logMessagePure, logMsg, logMsgs, logPrint, logStringStdout, logText,- logTextStderr, logTextStdout, logWarning, runPureLog, upgradeMessageAction,- usingLoggerT, withLog, withLogTextFile, (*<), (<&), (>$), (>$<), (>*), (>*<), (>|<))--import qualified Data.TypeRepMap as TM+import Colog (HasLog (..), LogAction, Message, Msg (..), PureLogger, RichMsg (..), SimpleMsg (..),+ WithLog, cmap, cmapM, defaultFieldMap, fmtMessage, fmtRichMessageDefault,+ fmtSimpleRichMessageDefault, liftLogIO, log, logException, logInfo, logMessagePure,+ logMsg, logMsgs, logPrint, logStringStdout, logText, logTextStderr, logTextStdout,+ logWarning, pattern D, runPureLog, upgradeMessageAction, usingLoggerT, withLog,+ withLogTextFile, (*<), (<&), (>$), (>$<), (>*), (>*<), (>|<)) +import Data.Dependent.Map (delete)+import Type.Reflection (typeRep) example :: WithLog env Message m => m () example = do@@ -46,7 +44,7 @@ app :: (WithLog env Message m, MonadIO m) => m () app = do logWarning "Starting application..."- liftIO $ threadDelay $ 10^(6 :: Int)+ liftIO $ threadDelay $ 10 ^ (6 :: Int) withLog (cmap addApp) $ do example exceptionL@@ -170,7 +168,7 @@ ---------------------------------------------------------------------------- main :: IO ()-main = withLogTextFile "co-log/tutorials/example.log" $ \logTextFile -> do+main = withLogTextFile "tutorials/example.log" $ \logTextFile -> do let runApp :: LogAction IO Message -> IO () runApp action = usingLoggerT action app @@ -190,7 +188,7 @@ let fullMessageAction = upgradeMessageAction defaultFieldMap richMessageAction let semiMessageAction = upgradeMessageAction- (TM.delete @"threadId" defaultFieldMap)+ (delete (typeRep @"threadId") defaultFieldMap) richMessageAction runApp simpleMessageAction
+ tutorials/custom/Custom.lhs view
@@ -0,0 +1,136 @@+# Using a custom monad that stores `LogAction` inside its environment++This tutorial covers the more advanced topic of using the `co-log` library with+a custom application monad.++You can run this tutorial by executing the following command:++```shell+cabal new-run tutorial-custom+```++## Preamble: imports and language extensions++Since this is a literate Haskell file, we need to specify all our language+extensions and imports up front.++```haskell+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-}++import Prelude hiding (log)++import Colog (pattern D, HasLog (..), pattern I, LogAction, Message, WithLog, log,+ richMessageAction)+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Reader (MonadReader, ReaderT (..))+```++## Application environment++If you have a complex Haskell application, then you are likely to also have+non-trivial settings that configure your application environment. The+environment may store various parameters relevant to your application's+behavior. Interestingly, we can store a `LogAction` inside the same+environment to use it for our logging functions.++The environment for your application may look like this:++```haskell+data Env m = Env+ { envServerPort :: !Int+ , envLogAction :: !(LogAction m Message)+ }+```++Several notes about this data type:++1. It stores different parameters, like the server port.+2. It stores a `LogAction` that can log values of the `Message` type+from `co-log` in the `m` monad.+3. `Env` is parameterized by type variable `m` which is going to be the+application monad.++The next step is to define an instance of the `HasLog` typeclass for the `Env`+data type. This instance will specify how to get and update the `LogAction`+stored inside the environment.++```haskell+instance HasLog (Env m) Message m where+ getLogAction :: Env m -> LogAction m Message+ getLogAction = envLogAction+ {-# INLINE getLogAction #-}++ setLogAction :: LogAction m Message -> Env m -> Env m+ setLogAction newLogAction env = env { envLogAction = newLogAction }+ {-# INLINE setLogAction #-}+```++That's it! `co-log` requires very little boilerplate.++## Application monad++Now let's define our application monad:++```haskell+newtype App a = App+ { unApp :: ReaderT (Env App) IO a+ } deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Env App))+```++This monad stores `Env` parameterized by the monad itself in its context.+Nothing special is required here to tell the monad how to use the logger.++## Example++`co-log` relies on the tagless final technique for writing functions. So you+define your monadic actions with the `WithLog` constraint that allows you to+perform logging:++```haskell+example :: WithLog env Message m => m ()+example = do+ log D "First message..."+ log I "Second message..."+```++The `WithLog` constraint has three type parameters: the application environment,+the type of the message and the monad. Function `log` takes two parameters:+the logger severity and the log message's text.++## Running example++Now we are ready to execute this action.++First, let's create an example environment:++```haskell+simpleEnv :: Env App+simpleEnv = Env+ { envServerPort = 8081+ , envLogAction = richMessageAction+ }+```++Then we need to define a function that performs actions of type `App`:++```haskell+runApp :: Env App -> App a -> IO a+runApp env app = runReaderT (unApp app) env+```++Putting it all together, we can specialize the `WithLog` constraint to our+`App` monad and run our example.++```haskell+main :: IO ()+main = runApp simpleEnv example+```++And the output will look like this:++