packages feed

wai-log 0.2.0.0 → 0.3.0.0

raw patch · 6 files changed

+156/−69 lines, 6 filesdep +bytestringdep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, uuid

API changes (from Hackage documentation)

- Network.Wai.Log: [logSendingResponse] :: Options -> Bool
- Network.Wai.Log: mkApplicationLogger :: MonadLog m => m Middleware
- Network.Wai.Log: mkApplicationLoggerWith :: MonadLog m => Options -> m Middleware
- Network.Wai.Log.Internal: logRequestsWith :: LoggerIO -> Options -> Middleware
- Network.Wai.Log.Internal: type LoggerIO = UTCTime -> LogLevel -> Text -> Value -> IO ()
- Network.Wai.Log.Options: Options :: LogLevel -> (Request -> [Pair]) -> Bool -> (Request -> Response -> ResponseTime -> [Pair]) -> Options
- Network.Wai.Log.Options: ResponseTime :: NominalDiffTime -> NominalDiffTime -> ResponseTime
- Network.Wai.Log.Options: [full] :: ResponseTime -> NominalDiffTime
- Network.Wai.Log.Options: [logLevel] :: Options -> LogLevel
- Network.Wai.Log.Options: [logRequest] :: Options -> Request -> [Pair]
- Network.Wai.Log.Options: [logResponse] :: Options -> Request -> Response -> ResponseTime -> [Pair]
- Network.Wai.Log.Options: [logSendingResponse] :: Options -> Bool
- Network.Wai.Log.Options: [processing] :: ResponseTime -> NominalDiffTime
- Network.Wai.Log.Options: data Options
- Network.Wai.Log.Options: data ResponseTime
- Network.Wai.Log.Options: defaultLogRequest :: Request -> [Pair]
- Network.Wai.Log.Options: defaultLogResponse :: Request -> Response -> ResponseTime -> [Pair]
- Network.Wai.Log.Options: defaultOptions :: Options
+ Network.Wai.Log: ResponseTime :: NominalDiffTime -> NominalDiffTime -> ResponseTime
+ Network.Wai.Log: [full] :: ResponseTime -> NominalDiffTime
+ Network.Wai.Log: [logBody] :: Options -> Maybe (Request -> Status -> ResponseHeaders -> Maybe (Builder -> Value))
+ Network.Wai.Log: [processing] :: ResponseTime -> NominalDiffTime
+ Network.Wai.Log: data ResponseTime
+ Network.Wai.Log: defaultLogRequest :: UUID -> Request -> [Pair]
+ Network.Wai.Log: defaultLogResponse :: UUID -> Request -> Response -> Value -> ResponseTime -> [Pair]
+ Network.Wai.Log: logRequestUUID :: MonadLog m => UUID -> m a -> m a
+ Network.Wai.Log: mkLogMiddleware :: MonadLog m => m LogMiddleware
+ Network.Wai.Log: mkLogMiddlewareWith :: MonadLog m => Options -> m LogMiddleware
+ Network.Wai.Log: type LogMiddleware = (UUID -> Application) -> Application
- Network.Wai.Log: Options :: LogLevel -> (Request -> [Pair]) -> Bool -> (Request -> Response -> ResponseTime -> [Pair]) -> Options
+ Network.Wai.Log: Options :: LogLevel -> (UUID -> Request -> [Pair]) -> (UUID -> Request -> Response -> Value -> ResponseTime -> [Pair]) -> Maybe (Request -> Status -> ResponseHeaders -> Maybe (Builder -> Value)) -> Options
- Network.Wai.Log: [logRequest] :: Options -> Request -> [Pair]
+ Network.Wai.Log: [logRequest] :: Options -> UUID -> Request -> [Pair]
- Network.Wai.Log: [logResponse] :: Options -> Request -> Response -> ResponseTime -> [Pair]
+ Network.Wai.Log: [logResponse] :: Options -> UUID -> Request -> Response -> Value -> ResponseTime -> [Pair]

Files

CHANGELOG.md view
@@ -1,4 +1,14 @@-# wai-log-0.2 (not released)+# wai-log-0.3 (2020-11-02)++* Rename `mkApplicationLogger` into `mkLogMiddleware`+* Generate ID for each request to correlate log messages+* Remove optional `logSendingResponse` and always log `Sending response`+* Log `method` and `url` in `defaultLogResponse` too+* Add `logBody` option to log `response_body` depending on the request and+  response parameters+* Use `snake_case` consistently in log output fields for `defaultOptions`++# wai-log-0.2 (2020-04-14)  * Entire interface has changed: now builds a `Middleware` in a `MonadLog`   context
README.md view
@@ -1,4 +1,5 @@-# wai-log [![Hackage version](https://img.shields.io/hackage/v/wai-log.svg?label=Hackage)](https://hackage.haskell.org/package/wai-log) [![Build Status](https://secure.travis-ci.org/scrive/wai-log.svg?branch=master)](http://travis-ci.org/scrive/wai-log)+# wai-log [![Hackage version](https://img.shields.io/hackage/v/wai-log.svg?label=Hackage)](https://hackage.haskell.org/package/wai-log)+![Build & Test](https://github.com/scrive/wai-log/workflows/Build%20&%20Test/badge.svg)  A simple logging middleware for WAI applications that supports the `log-*` family of packages.
src/Network/Wai/Log.hs view
@@ -4,50 +4,74 @@ -- When logging to @stdout@ using 'defaultOptions', the output looks like this: -- -- @--- 2019-02-21 19:51:47 INFO my-server: Request received {---   \"url\": \"\/api\/myapi\",---   \"body-length\": \"KnownLength 0\",---   \"method\": \"GET\",---   \"user-agent\": \"curl\/7.54.0\",---   \"remote-host\": \"127.0.0.1:61249\"+-- 2020-10-27 12:30:23 INFO eid-server: Request received {+--   "url": "\/api\/v1\/transaction\/new",+--   "body_length": "KnownLength 136",+--   "method": \"POST\",+--   "user_agent": "curl/7.68.0",+--   "request_uuid": "f2c89425-9ec4-4cd2-ae56-4bab23681fce",+--   "remote_host": "127.0.0.1:34694" -- }--- 2019-02-21 19:51:47 INFO my-server: Sending response--- 2019-02-21 19:51:47 INFO my-server: Request complete {---   \"status\": {---     \"code\": 200,---     \"message\": \"OK\"+-- 2020-10-27 12:30:23 INFO eid-server: Sending response {+--   "request_uuid": "f2c89425-9ec4-4cd2-ae56-4bab23681fce"+-- }+-- 2020-10-27 12:30:23 INFO eid-server: Request complete {+--   "response_body": null,+--   "url": "\/api\/v1\/transaction\/new",+--   "method": \"POST\",+--   "status": {+--     "code": 400,+--     "message": "Bad Request" --   },---   \"time\": {---     \"process\": 2.224e-3,---     \"full\": 2.348e-3---   }+--   "time": {+--     "process": 2.97493e-3,+--     "full": 3.159565e-3+--   },+--   "request_uuid": "f2c89425-9ec4-4cd2-ae56-4bab23681fce" -- } -- @+ module Network.Wai.Log ( -- * Create a Middleware-  mkApplicationLogger-, mkApplicationLoggerWith+  mkLogMiddleware+, mkLogMiddlewareWith+-- ** Type+, LogMiddleware -- ** Options , Options(..)+, ResponseTime(..) , defaultOptions+, defaultLogRequest+, defaultLogResponse+-- ** Helpers+, logRequestUUID ) where  import Prelude hiding (log) +import Data.UUID (UUID) import Log (MonadLog, getLoggerIO)-import Network.Wai (Middleware)+import Network.Wai  import Network.Wai.Log.Internal import Network.Wai.Log.Options --- | Create a logging 'Middleware' using 'defaultOptions'+-- | The classic @wai@ 'Middleware' type is @Application -> Application@, but+-- that does not work when you want to pass logging context down to the+-- 'Application'. ----- Use 'mkApplicationLoggerWith' for custom 'Options'-mkApplicationLogger :: MonadLog m => m Middleware-mkApplicationLogger = mkApplicationLoggerWith defaultOptions+-- Instead we pass a 'UUID' to the 'Application', containting the+-- @request_uuid@, so it can be logged in the application's context.+type LogMiddleware = (UUID -> Application) -> Application --- | Create a logging 'Middleware' using the supplied 'Options'-mkApplicationLoggerWith :: MonadLog m => Options -> m Middleware-mkApplicationLoggerWith options = do-  logIO <- getLoggerIO-  return $ logRequestsWith logIO options+-- | Create a 'LogMiddleware' using 'defaultOptions'+--+-- Use 'mkLogMiddlewareWith' for custom 'Options'+mkLogMiddleware :: MonadLog m => m LogMiddleware+mkLogMiddleware = mkLogMiddlewareWith defaultOptions++-- | Create a 'LogMiddleware' using the supplied 'Options'+mkLogMiddlewareWith :: MonadLog m => Options -> m LogMiddleware+mkLogMiddlewareWith options = do+  loggerIO <- getLoggerIO+  return $ logRequestsWith loggerIO options
src/Network/Wai/Log/Internal.hs view
@@ -1,39 +1,56 @@ {-# LANGUAGE RecordWildCards #-} module Network.Wai.Log.Internal where -import Control.Monad (when)-import Data.Aeson.Types (Value, object, emptyObject)+import Data.Aeson.Types (Value(..), object)+import Data.ByteString.Builder (Builder) import Data.Text (Text) import Data.Time.Clock (UTCTime, diffUTCTime, getCurrentTime)+import Data.UUID (UUID)+import Data.UUID.V4 (nextRandom) import Log (LogLevel)-import Network.Wai (Middleware)+import Network.Wai (Application, responseToStream) -import Network.Wai.Log.Options (Options(..), ResponseTime(..))+import Network.Wai.Log.Options (Options(..), ResponseTime(..), requestUUID)  -- | This type matches the one returned by 'getLoggerIO' type LoggerIO = UTCTime -> LogLevel -> Text -> Value -> IO () --- | Create a logging 'Middleware' given a 'LoggerIO' logging function and 'Options'-logRequestsWith :: LoggerIO -> Options -> Middleware-logRequestsWith loggerIO Options{..} app req respond = do-  logIO "Request received" . object . logRequest $ req+-- | Create a logging 'Middleware' that takes request UUID+-- given a 'LoggerIO' logging function and 'Options'+logRequestsWith :: LoggerIO -> Options -> (UUID -> Application) -> Application+logRequestsWith loggerIO Options{..} mkApp req respond = do+  uuid <- nextRandom+  logIO "Request received" $ logRequest uuid req   tStart <- getCurrentTime-  app req $ \resp -> do+  mkApp uuid req $ \resp -> do     tEnd <- getCurrentTime-    when logSendingResponse $-         logIO_ "Sending response"+    logIO "Sending response" . requestUUID $ uuid     r <- respond resp     tFull <- getCurrentTime     let processing = diffUTCTime tEnd  tStart         full       = diffUTCTime tFull tStart         times      = ResponseTime{..}-    logIO "Request complete" . object $ logResponse req resp times++    _ <- case logBody of+      Nothing ->+        logIO "Request complete" $ logResponse uuid req resp Null times+      Just bodyLogValueConstructorFunction ->+        let (status, responseHeaders, bodyToIO) = responseToStream resp+            mBodyLogValueConstructor =+              bodyLogValueConstructorFunction req status responseHeaders+        in case mBodyLogValueConstructor of+          Nothing ->+            logIO "Request complete" $ logResponse uuid req resp Null times+          Just bodyLogValueConstructor ->+            bodyToIO $ \streamingBodyToIO ->+              let logWithBuilder :: Builder -> IO ()+                  logWithBuilder b = logIO "Request complete" $+                    logResponse uuid req resp (bodyLogValueConstructor b) times++              in streamingBodyToIO logWithBuilder (return ())     return r    where--    logIO message value = do+    logIO message pairs = do       now <- getCurrentTime-      loggerIO now logLevel message value--    logIO_ m = logIO m emptyObject+      loggerIO now logLevel message (object pairs)
src/Network/Wai/Log/Options.hs view
@@ -6,22 +6,36 @@ , defaultOptions , defaultLogRequest , defaultLogResponse+-- * Helpers+, logRequestUUID+, requestUUID ) where -import Data.Aeson.Types (Pair)+import Data.Aeson.Types (Pair, Value)+import Data.ByteString.Builder (Builder) import Data.String.Conversions (ConvertibleStrings, StrictText, cs) import Data.Text (Text) import Data.Time.Clock (NominalDiffTime)+import Data.UUID (UUID) import Log-import Network.HTTP.Types.Status+import Network.HTTP.Types.Header (ResponseHeaders)+import Network.HTTP.Types.Status (Status, statusCode, statusMessage) import Network.Wai  -- | Logging options+--+-- Logging response body involves extracting it from @Response@ via IO operations,+-- therefore the @logBody@ option takes @Request@, @Status@ and @ResponseHeaders@+-- as arguments to decide whether the IO operations of body extraction have+-- to be permormed.+-- The resulting @Maybe@ function is the constructor of a loggable @Value@+-- from the body bytestring builder. data Options = Options {-    logLevel            :: LogLevel-  , logRequest          :: Request -> [Pair]-  , logSendingResponse  :: Bool-  , logResponse         :: Request -> Response -> ResponseTime -> [Pair]+    logLevel    :: LogLevel+  , logRequest  :: UUID -> Request -> [Pair]+  , logResponse :: UUID -> Request -> Response -> Value -> ResponseTime -> [Pair]+  -- | An optional constructor of the response body log value.+  , logBody :: Maybe (Request -> Status -> ResponseHeaders -> Maybe (Builder -> Value))   }  -- | Timing data@@ -37,7 +51,6 @@ -- @ -- { logLevel = 'LogInfo' -- , logRequest = 'defaultLogRequest'--- , logSendingResponse = True -- , logResponse = 'defaultLogResponse' -- } -- @@@ -45,45 +58,65 @@ defaultOptions = Options   { logLevel = LogInfo   , logRequest = defaultLogRequest-  , logSendingResponse = True   , logResponse = defaultLogResponse+  , logBody = Nothing   }  -- | Logs the following request values: --+-- * request_uuid -- * method -- * url path -- * remote host -- * user agent -- * body-length-defaultLogRequest :: Request -> [Pair]-defaultLogRequest req =-  [ "method"      .= ts (requestMethod req)-  , "url"         .= ts (rawPathInfo req)-  , "remote-host" .= show (remoteHost req)-  , "user-agent"  .= fmap ts (requestHeaderUserAgent req)-  , "body-length" .= show (requestBodyLength req)+defaultLogRequest :: UUID -> Request -> [Pair]+defaultLogRequest uuid req =+  [ "request_uuid" .= uuid+  , "method"       .= ts (requestMethod req)+  , "url"          .= ts (rawPathInfo req)+  , "remote_host"  .= show (remoteHost req)+  , "user_agent"   .= fmap ts (requestHeaderUserAgent req)+  , "body_length"  .= show (requestBodyLength req)   ]  -- | Logs the following values: --+-- * request_uuid+-- * request method+-- * request url path+-- * response_body details provided as 'Value' -- * status code -- * status message -- * time full -- * time processing ----- Nothing from the 'Request' is logged--- -- Time is in seconds as that is how 'NominalDiffTime' is treated by default-defaultLogResponse :: Request -> Response -> ResponseTime -> [Pair]-defaultLogResponse _req resp time =-    [ "status" .= object [ "code"    .= statusCode (responseStatus resp)+defaultLogResponse :: UUID -> Request -> Response -> Value -> ResponseTime -> [Pair]+defaultLogResponse uuid req resp responseBody time =+    [ "request_uuid"  .= uuid+    , "method"        .= ts (requestMethod req)+    , "url"           .= ts (rawPathInfo req)+    , "response_body" .= responseBody+    , "status" .= object [ "code"    .= statusCode (responseStatus resp)                          , "message" .= ts (statusMessage (responseStatus resp))                          ]     , "time"   .= object [ "full"    .= full time                          , "process" .= processing time                          ]     ]++-- | Helper to consistently log the UUID in your application by adding+-- @request_uuid@ field to log's 'localData'+logRequestUUID :: MonadLog m => UUID -> m a -> m a+logRequestUUID = localData . requestUUID++-- | Logs the following values:+--+-- * request_uuid+requestUUID :: UUID -> [Pair]+requestUUID uuid =+  [ "request_uuid" .= uuid ]  ts :: ConvertibleStrings a StrictText => a -> Text ts = cs
wai-log.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.0 name:                wai-log-version:             0.2.0.0+version:             0.3.0.0  synopsis:            A logging middleware for WAI applications @@ -24,7 +24,7 @@ build-type:          Simple extra-source-files:  CHANGELOG.md, README.md tested-with:         GHC ==7.10.3, GHC ==8.0.2, GHC ==8.2.2, GHC ==8.4.4,-                     GHC ==8.6.4+                     GHC ==8.6.5, GHC ==8.8.4  Source-repository head   Type:     git@@ -38,12 +38,14 @@                      , FlexibleContexts   build-depends:       base >=4.8 && <5                      , aeson+                     , bytestring                      , http-types                      , log-base                      , string-conversions                      , text                      , time+                     , uuid                      , wai   exposed-modules:     Network.Wai.Log-                     , Network.Wai.Log.Options+  other-modules:       Network.Wai.Log.Options                      , Network.Wai.Log.Internal