packages feed

co-log 0.4.0.1 → 0.7.0.0

raw patch · 22 files changed

Files

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 -[![Hackage](https://img.shields.io/hackage/v/co-log.svg)](https://hackage.haskell.org/package/co-log)-[![Stackage LTS](http://stackage.org/package/co-log/badge/lts)](http://stackage.org/lts/package/co-log)-[![Stackage Nightly](http://stackage.org/package/co-log/badge/nightly)](http://stackage.org/nightly/package/co-log)-[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://github.com/kowainik/co-log/blob/master/LICENSE)-[![Build status](https://secure.travis-ci.org/kowainik/co-log.svg)](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"> +[![GitHub CI](https://github.com/co-log/co-log/workflows/CI/badge.svg)](https://github.com/co-log/co-log/actions)+[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](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:  ![screenshot from 2018-09-17 20-52-01](https://user-images.githubusercontent.com/4276606/45623973-8bafb900-babb-11e8-9e20-4369a5a8e5ff.png)++## 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 -[![Hackage](https://img.shields.io/hackage/v/co-log.svg)](https://hackage.haskell.org/package/co-log)-[![Stackage LTS](http://stackage.org/package/co-log/badge/lts)](http://stackage.org/lts/package/co-log)-[![Stackage Nightly](http://stackage.org/package/co-log/badge/nightly)](http://stackage.org/nightly/package/co-log)-[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://github.com/kowainik/co-log/blob/master/LICENSE)-[![Build status](https://secure.travis-ci.org/kowainik/co-log.svg)](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"> +[![GitHub CI](https://github.com/co-log/co-log/workflows/CI/badge.svg)](https://github.com/co-log/co-log/actions)+[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](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:  ![screenshot from 2018-09-17 20-52-01](https://user-images.githubusercontent.com/4276606/45623973-8bafb900-babb-11e8-9e20-4369a5a8e5ff.png)++## 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:--![co-log output example](https://user-images.githubusercontent.com/8126674/71579355-1b804780-2af4-11ea-97eb-3f220011fd8d.png)
+ 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:++![](../img/2-loggert-output.jpg)
+ 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:++![](../img/3-loggert-message.jpg)
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:++![co-log output example](https://user-images.githubusercontent.com/8126674/71579355-1b804780-2af4-11ea-97eb-3f220011fd8d.png)