packages feed

log-warper 1.7.1 → 1.7.2

raw patch · 8 files changed

+497/−30 lines, 8 filesdep +markdown-unlitdep ~textnew-component:exe:how-to

Dependencies added: markdown-unlit

Dependency ranges changed: text

Files

CHANGES.md view
@@ -1,3 +1,13 @@+1.7.2+=====++* [#57](https://github.com/serokell/log-warper/issues/57):+  Add `Exception` module with `logException` and `catchLog` functions.+* [#60](https://github.com/serokell/log-warper/issues/60):+  Fix documentation for `termSeveritiesOut` and `termSeveritiesErr`.+* [#63](https://github.com/serokell/log-warper/issues/63):+  Timestamp rounding by powers of 10.+ 1.7.1 ===== 
+ examples/HowTo.lhs view
@@ -0,0 +1,190 @@+# `log-warper` how-to++If you want to see the working example first, please, check out this+[very simple example](https://github.com/serokell/log-warper/blob/master/examples/Playground.hs).++Here we will try to explain step by step how to integrate `log-warper` into our very small project,+specifically how to replace `putStrLn` with simple naive logging.++All code below can be compiled and run with these commands++```bash+stack build log-warper+stack exec how-to+```+and you can see how it's working both with and without logging.++## 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 OverloadedStrings #-}++module Main (main) where+++import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Semigroup ((<>))+import Data.Text (Text)+import System.Wlog (WithLoggerIO, buildAndSetupYamlLogging, logError,+                    logInfo, productionB, usingLoggerName)++import qualified Data.Text as Text+import qualified Data.Text.IO as TextIO++```++## Adding a new logger++First of all, you may ask what are we doing? What the hell is logger?++**Logger** is the [output handler](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-LogHandler.html#t:LogHandler)+labeled by the unique [logger name](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-LoggerName.html#t:LoggerName).++When you log new messages you need to specify logger which is responsible for logging this message.+Fortunately, log-warper has monadic interfaces and logger will be taken explicitly from current context+if possible, thus you need to specify logger only once for some block of actions.+Don't worry, this will be covered later!++So, if you're adding a new logger for your needs you'll have to consider several things.+Firstly, you should choose some reasonable name for the logger you are creating+according to the actions that it would cover with logging. From now on let's assume+that you need to have the logger with the name `new-logger` in your project.++### Configuration file++There are two ways of configuring the logger you creating. One option+is to configure everything directly in the code, which will make the process+of writing code less convenient. Here we will use another configuration possibility+— we will write configurations in special `.yaml` file.+In our example we will use very simple one with the minimal settings needed for the file to work.+You can have a look at [`how-to-log-config.yaml`](https://github.com/serokell/log-warper/blob/master/examples/how-to-log-config.yaml),+it contains the following lines:++```yaml+loggerTree:+    severity: Warning+    # severities for «root» logger+    new-logger:           # logger named «new-logger»+        severity: Debug+  # severities for logger «new-logger»+```+You don't need to worry about root logger for now. This part is explained in section devoted to hierarchical logging.++For full information about all configuration file's features and options please see [this section](#link-to-readme-config).++### Setting up++Before using your logging subsystem you need to set it up. To do that there are functions in `log-warper` library.+In our example we will run all functions with our logger from `main` function of `Main` module.++Let's consider that this is what we have:++```haskell+mainWithoutLogging :: IO ()+mainWithoutLogging = inputLength+```+ where `inputLength` is the function where we will add some logging messages.++So to make it possible we should transform this function to the following:++```haskell+mainWithLogging :: IO ()+mainWithLogging = do+    buildAndSetupYamlLogging productionB "examples/how-to-log-config.yaml"+    usingLoggerName "new-logger" inputLengthWithLog+```+where we set up the config from the file and run `inputLengthWithLog` under the logger with name `new-logger`.+Note that we use `inputLengthWithLog` now instead of `inputLength` because we can not just add logging+to the function that works under `IO` monad automatically. We need to change our faction.+And `inputLengthWithLog` is a version of `inputLength` with logging added.++Let's have a closer look at how we transform the `inputLength` function+to the `inputLengthWithLog` that would work with logging in the next paragraph.++## Add logging messages to function in `IO`++If you want to use logging in a function that already runs in the `IO`,+replace concrete `IO` type with polymorphic constraint+[`WithLoggerIO`](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-CanLog.html#t:WithLoggerIO)` m => m`+and add [`liftIO`](https://www.stackage.org/haddock/lts-9.14/base-4.9.1.0/Control-Monad-IO-Class.html#v:liftIO) all your `IO` actions.++For example, our `inputLength` function works this way:+```haskell++inputLength :: IO ()+inputLength = do+    input <- TextIO.getLine+    case Text.length input of+        0 -> do+            putStrLn "Should not be empty"+            inputLength+          -- v ^  you need to log these+        _ -> do+            putStrLn "Well done"+            funWithInput input++```++_**Note:**_ All logging functions take [`Text`](https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-Text.html#t:Text)+messages instead of `String`. So users might convert their `String`s to `Text`s with+[`Text.pack`](https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-Text.html#v:pack)+or migrate to the `text` package or use some custom prelude which makes the work with `Text` more+convenient, e.g. [`universum`](https://hackage.haskell.org/package/universum).++Okay, it's very easy function. We have some input from console and write some messages depending of the length of user's input.+What we are going to do is to log these messages instead of just `putStrLn` them.++First that we should consider is that logging can not be done in `IO`, so we need make+the function work in some polymorphic monad, which implements `WithLoggerIO` constraint,+so that we are able to do any logging stuff inside.+After this is done you can easily use `logDebug`, `logInfo`, `logWarning` etc.+(see others [here](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-CanLog.html#v:logDebug)) to log messages.+As it's not `IO` after our transformations, we shouldn't forget to `liftIO` functions that work in `IO` and doesn't have logging inside.+As result we will get++```haskell++inputLengthWithLog :: WithLoggerIO m => m ()+inputLengthWithLog = do+    input <- liftIO TextIO.getLine+    case Text.length input of+        0 -> do+            -- `putStrLn` is replaced with `logError` now.+            logError "Should not be empty"+            inputLengthWithLog+        _ -> do+            -- `putStrLn` is replaced with `logInfo` now.+            logInfo "Well done"+            funWithInputInMonad input++```++Another moment, let's have a closer look at the `funWithInput` function from the example above.+We see that its type is `IO ()` and it doesn't have logging inside.++```haskell+funWithInput :: Text -> IO ()+funWithInput input = do+    TextIO.putStrLn $ "Wow! You wrote " <> input++```+So we also can rewrite it in this way+```haskell+funWithInputInMonad :: MonadIO m => Text -> m ()+funWithInputInMonad input =+    liftIO $ TextIO.putStrLn $ "Wow! You wrote " <> input++```+or usage of `liftIO` is also the solution.++And as a conclusion, let's write the `main` function to check how we managed to apply logging in very simple example+```haskell+main :: IO ()+main = do+    mainWithoutLogging+    mainWithLogging+```++You can see an example of the work of the programm we've just wrote:+![Example of run](https://user-images.githubusercontent.com/8126674/33295654-bd6cc94e-d3e7-11e7-8c03-e54aa6556f78.png)
+ examples/HowTo.md view
@@ -0,0 +1,190 @@+# `log-warper` how-to++If you want to see the working example first, please, check out this+[very simple example](https://github.com/serokell/log-warper/blob/master/examples/Playground.hs).++Here we will try to explain step by step how to integrate `log-warper` into our very small project,+specifically how to replace `putStrLn` with simple naive logging.++All code below can be compiled and run with these commands++```bash+stack build log-warper+stack exec how-to+```+and you can see how it's working both with and without logging.++## 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 OverloadedStrings #-}++module Main (main) where+++import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Semigroup ((<>))+import Data.Text (Text)+import System.Wlog (WithLoggerIO, buildAndSetupYamlLogging, logError,+                    logInfo, productionB, usingLoggerName)++import qualified Data.Text as Text+import qualified Data.Text.IO as TextIO++```++## Adding a new logger++First of all, you may ask what are we doing? What the hell is logger?++**Logger** is the [output handler](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-LogHandler.html#t:LogHandler)+labeled by the unique [logger name](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-LoggerName.html#t:LoggerName).++When you log new messages you need to specify logger which is responsible for logging this message.+Fortunately, log-warper has monadic interfaces and logger will be taken explicitly from current context+if possible, thus you need to specify logger only once for some block of actions.+Don't worry, this will be covered later!++So, if you're adding a new logger for your needs you'll have to consider several things.+Firstly, you should choose some reasonable name for the logger you are creating+according to the actions that it would cover with logging. From now on let's assume+that you need to have the logger with the name `new-logger` in your project.++### Configuration file++There are two ways of configuring the logger you creating. One option+is to configure everything directly in the code, which will make the process+of writing code less convenient. Here we will use another configuration possibility+— we will write configurations in special `.yaml` file.+In our example we will use very simple one with the minimal settings needed for the file to work.+You can have a look at [`how-to-log-config.yaml`](https://github.com/serokell/log-warper/blob/master/examples/how-to-log-config.yaml),+it contains the following lines:++```yaml+loggerTree:+    severity: Warning+    # severities for «root» logger+    new-logger:           # logger named «new-logger»+        severity: Debug+  # severities for logger «new-logger»+```+You don't need to worry about root logger for now. This part is explained in section devoted to hierarchical logging.++For full information about all configuration file's features and options please see [this section](#link-to-readme-config).++### Setting up++Before using your logging subsystem you need to set it up. To do that there are functions in `log-warper` library.+In our example we will run all functions with our logger from `main` function of `Main` module.++Let's consider that this is what we have:++```haskell+mainWithoutLogging :: IO ()+mainWithoutLogging = inputLength+```+ where `inputLength` is the function where we will add some logging messages.++So to make it possible we should transform this function to the following:++```haskell+mainWithLogging :: IO ()+mainWithLogging = do+    buildAndSetupYamlLogging productionB "examples/how-to-log-config.yaml"+    usingLoggerName "new-logger" inputLengthWithLog+```+where we set up the config from the file and run `inputLengthWithLog` under the logger with name `new-logger`.+Note that we use `inputLengthWithLog` now instead of `inputLength` because we can not just add logging+to the function that works under `IO` monad automatically. We need to change our faction.+And `inputLengthWithLog` is a version of `inputLength` with logging added.++Let's have a closer look at how we transform the `inputLength` function+to the `inputLengthWithLog` that would work with logging in the next paragraph.++## Add logging messages to function in `IO`++If you want to use logging in a function that already runs in the `IO`,+replace concrete `IO` type with polymorphic constraint+[`WithLoggerIO`](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-CanLog.html#t:WithLoggerIO)` m => m`+and add [`liftIO`](https://www.stackage.org/haddock/lts-9.14/base-4.9.1.0/Control-Monad-IO-Class.html#v:liftIO) all your `IO` actions.++For example, our `inputLength` function works this way:+```haskell++inputLength :: IO ()+inputLength = do+    input <- TextIO.getLine+    case Text.length input of+        0 -> do+            putStrLn "Should not be empty"+            inputLength+          -- v ^  you need to log these+        _ -> do+            putStrLn "Well done"+            funWithInput input++```++_**Note:**_ All logging functions take [`Text`](https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-Text.html#t:Text)+messages instead of `String`. So users might convert their `String`s to `Text`s with+[`Text.pack`](https://hackage.haskell.org/package/text-1.2.2.2/docs/Data-Text.html#v:pack)+or migrate to the `text` package or use some custom prelude which makes the work with `Text` more+convenient, e.g. [`universum`](https://hackage.haskell.org/package/universum).++Okay, it's very easy function. We have some input from console and write some messages depending of the length of user's input.+What we are going to do is to log these messages instead of just `putStrLn` them.++First that we should consider is that logging can not be done in `IO`, so we need make+the function work in some polymorphic monad, which implements `WithLoggerIO` constraint,+so that we are able to do any logging stuff inside.+After this is done you can easily use `logDebug`, `logInfo`, `logWarning` etc.+(see others [here](https://hackage.haskell.org/package/log-warper-1.7.1/docs/System-Wlog-CanLog.html#v:logDebug)) to log messages.+As it's not `IO` after our transformations, we shouldn't forget to `liftIO` functions that work in `IO` and doesn't have logging inside.+As result we will get++```haskell++inputLengthWithLog :: WithLoggerIO m => m ()+inputLengthWithLog = do+    input <- liftIO TextIO.getLine+    case Text.length input of+        0 -> do+            -- `putStrLn` is replaced with `logError` now.+            logError "Should not be empty"+            inputLengthWithLog+        _ -> do+            -- `putStrLn` is replaced with `logInfo` now.+            logInfo "Well done"+            funWithInputInMonad input++```++Another moment, let's have a closer look at the `funWithInput` function from the example above.+We see that its type is `IO ()` and it doesn't have logging inside.++```haskell+funWithInput :: Text -> IO ()+funWithInput input = do+    TextIO.putStrLn $ "Wow! You wrote " <> input++```+So we also can rewrite it in this way+```haskell+funWithInputInMonad :: MonadIO m => Text -> m ()+funWithInputInMonad input =+    liftIO $ TextIO.putStrLn $ "Wow! You wrote " <> input++```+or usage of `liftIO` is also the solution.++And as a conclusion, let's write the `main` function to check how we managed to apply logging in very simple example+```haskell+main :: IO ()+main = do+    mainWithoutLogging+    mainWithLogging+```++You can see an example of the work of the programm we've just wrote:+![Example of run](https://user-images.githubusercontent.com/8126674/33295654-bd6cc94e-d3e7-11e7-8c03-e54aa6556f78.png)
log-warper.cabal view
@@ -1,5 +1,5 @@ name:                log-warper-version:             1.7.1+version:             1.7.2 synopsis:            Flexible, configurable, monadic and pretty logging homepage:            https://github.com/serokell/log-warper license:             MIT@@ -8,7 +8,7 @@ maintainer:          Serokell <hi@serokell.io> copyright:           2016-2017 Serokell category:            Logging-extra-source-files:  CHANGES.md+extra-source-files:  CHANGES.md, examples/HowTo.md, examples/HowTo.lhs stability:           experimental build-type:          Simple cabal-version:       >=1.18@@ -18,6 +18,7 @@ library   exposed-modules:     System.Wlog                        System.Wlog.CanLog+                       System.Wlog.Exception                        System.Wlog.FileUtils                        System.Wlog.Formatter                        System.Wlog.HasLoggerName@@ -94,7 +95,8 @@    hs-source-dirs:      examples   default-language:    Haskell2010-  ghc-options:         -threaded -Wall -fno-warn-orphans+  ghc-options:         -threaded -Wall+                       -fno-warn-orphans    default-extensions:  GeneralizedNewtypeDeriving                        DeriveDataTypeable@@ -103,6 +105,20 @@                        OverloadedStrings                        TypeApplications                        RecordWildCards++executable how-to+  main-is:             HowTo.lhs++  build-depends:       base           >= 4.7 && < 5+                     , log-warper+                     , markdown-unlit >= 0.4.0+                     , text           >= 1.2.2.1++  hs-source-dirs:      examples+  default-language:    Haskell2010+  ghc-options:         -threaded -Wall+                       -fno-warn-orphans+                       -pgmL markdown-unlit  test-suite log-test   type:                exitcode-stdio-1.0
src/System/Wlog.hs view
@@ -13,6 +13,7 @@  module System.Wlog        ( module System.Wlog.CanLog+       , module System.Wlog.Exception        , module System.Wlog.FileUtils        , module System.Wlog.HasLoggerName        , module System.Wlog.IOLogger@@ -28,17 +29,18 @@        , module System.Wlog.Terminal        ) where -import           System.Wlog.CanLog-import           System.Wlog.FileUtils-import           System.Wlog.HasLoggerName-import           System.Wlog.IOLogger-import           System.Wlog.Launcher-import           System.Wlog.LoggerConfig-import           System.Wlog.LoggerName-import           System.Wlog.LoggerNameBox-import           System.Wlog.LogHandler.Roller-import           System.Wlog.LogHandler.Simple-import           System.Wlog.LogHandler.Syslog-import           System.Wlog.PureLogging-import           System.Wlog.Severity-import           System.Wlog.Terminal+import System.Wlog.CanLog+import System.Wlog.Exception+import System.Wlog.FileUtils+import System.Wlog.HasLoggerName+import System.Wlog.IOLogger+import System.Wlog.Launcher+import System.Wlog.LoggerConfig+import System.Wlog.LoggerName+import System.Wlog.LoggerNameBox+import System.Wlog.LogHandler.Roller+import System.Wlog.LogHandler.Simple+import System.Wlog.LogHandler.Syslog+import System.Wlog.PureLogging+import System.Wlog.Severity+import System.Wlog.Terminal
+ src/System/Wlog/Exception.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ExplicitForAll      #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | This module deals with Exception logging.++module System.Wlog.Exception+       ( logException+       , catchLog+       ) where++import Universum++import Control.Exception.Base (Exception)++import System.Wlog.CanLog (WithLogger, WithLoggerIO, logError)++-- | Logs exception's description with ''System.Wlog.Severity.Error' 'System.Wlog.Severity.Severity'+logException :: forall e m . (WithLogger m, Exception e) => e -> m ()+logException = logError . show++{- | Runs the action, if an exception is raised the 'logException' is executed.++==== __Example__+Here is very simple example of usage 'catchLog' on IO functions:++@+main :: IO ()+main = do+    buildAndSetupYamlLogging productionB "log-config.yaml"+    usingLoggerName "new-logger" runWithExceptionLog++runWithExceptionLog :: (WithLoggerIO m, MonadCatch m) => m ()+runWithExceptionLog = catchLog @IOException (liftIO simpleIOfun)++simpleIOfun :: IO ()+simpleIOfun = getLine >>= readFile >>= putStrLn++@++and when run you will get:++>>> run-main-from-this-example+> not-existing-filename.txt+[new-logger:ERROR] [2017-12-01 13:07:33.21 UTC] asd: openFile: does not exist (No such file or directory)++-}+catchLog :: forall e m . (WithLoggerIO m, MonadCatch m, Exception e) => m () -> m ()+catchLog a = a `catch` logE+  where+    logE :: e -> m ()+    logE = logException
src/System/Wlog/Formatter.hs view
@@ -33,7 +33,7 @@ import Data.Monoid (mconcat) import Data.Text.Lazy.Builder as B import Data.Time (formatTime, getCurrentTime, getZonedTime)-import Data.Time.Clock (UTCTime (..))+import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, picosecondsToDiffTime) import Data.Time.Format (FormatTime) import Fmt (fmt, padRightF, (+|), (|+), (|++|)) import Fmt.Time (dateDashF, hmsF, subsecondF, tzNameF)@@ -169,14 +169,19 @@   where     centiSecondF = padRightF 3 '0' . T.take 3 . fmt . subsecondF +-- | Get the current time rounded to 10^n picoseconds.+-- n = 3 rounds to the nearest nanosecond.+-- n = 6 rounds to the nearest microsecond.+-- n = 9 will round to the nearest millisecond.+-- n = 12 will round to the nearest second. getRoundedTime :: Int -> IO UTCTime-getRoundedTime roundN = do-    UTCTime{..} <- liftIO $ getCurrentTime-    let newSec = fromIntegral $ roundBy (round $ toRational utctDayTime :: Int)-    pure $ UTCTime { utctDayTime = newSec, .. }-  where-    roundBy :: (Num a, Integral a) => a -> a-    roundBy x = let y = x `div` fromIntegral roundN in y * fromIntegral roundN+getRoundedTime n = do+    UTCTime{..} <- getCurrentTime+    let m = if n < 0 then 0 else (fromIntegral n :: Integer)+        multiplier = 10 ^ m+        picoseconds = diffTimeToPicoseconds utctDayTime+        roundedPicoseconds = (picoseconds `div` multiplier) * multiplier+    pure $ UTCTime { utctDayTime = picosecondsToDiffTime roundedPicoseconds, .. }  stdoutFormatter :: (UTCTime -> Text) -> Bool -> Bool -> LogFormatter a stdoutFormatter timeF isShowTime isShowTid handle record message = do@@ -184,8 +189,8 @@     createLogFormatter isShowTime isShowTid timeF time handle record message  stdoutFormatterTimeRounded :: (UTCTime -> Text) -> Int -> LogFormatter a-stdoutFormatterTimeRounded timeF roundN handle record message = do-    time <- getRoundedTime roundN+stdoutFormatterTimeRounded timeF n handle record message = do+    time <- getRoundedTime n     createLogFormatter True True timeF time handle record message  createLogFormatter
src/System/Wlog/LoggerConfig.hs view
@@ -114,7 +114,9 @@     { _hwFilePath :: !FilePath       -- ^ Path to the file to be handled.     , _hwRounding :: !(Maybe Int)-      -- ^ Amount of seconds to round time on (if set).+      -- ^ Round timestamps to this power of 10 picoseconds.+      -- Just 3 would round to nanoseconds.+      -- Just 12 would round to seconds.     } deriving (Generic,Show)  makeLenses ''HandlerWrap@@ -306,11 +308,11 @@ -- Builders for 'LoggerConfig'. ---------------------------------------------------------------------------- --- | Setup 'lcTermSeverity' to specified severity inside 'LoggerConfig'.+-- | Setup 'lcTermSeverityOut' to specified severity inside 'LoggerConfig'. termSeveritiesOutB :: Severities -> LoggerConfig termSeveritiesOutB severities = mempty { _lcTermSeverityOut = Just severities } --- | Setup 'lcTermSeverity' to specified severity inside 'LoggerConfig'.+-- | Setup 'lcTermSeverityErr' to specified severity inside 'LoggerConfig'. termSeveritiesErrB :: Severities -> LoggerConfig termSeveritiesErrB severities = mempty { _lcTermSeverityErr = Just severities }