@@ -14,6 +14,7 @@ module Network.Wai.Middleware.RequestLogger
1414 , OutputFormat (..)
1515 , OutputFormatter
1616 , OutputFormatterWithDetails
17+ , OutputFormatterWithDetailsAndHeaders
1718 , Destination (..)
1819 , Callback
1920 , IPAddrSource (..)
@@ -49,14 +50,42 @@ import Network.Wai.Middleware.RequestLogger.Internal
4950import Network.Wai.Header (contentLength)
5051import Data.Text.Encoding (decodeUtf8')
5152
52−data OutputFormat = Apache IPAddrSource
53− | Detailed Bool -- ^ use colors?
54− | CustomOutputFormat OutputFormatter
55− | CustomOutputFormatWithDetails OutputFormatterWithDetails
53+data OutputFormat
54+ = Apache IPAddrSource
55+ | Detailed Bool -- ^ use colors?
56+ | CustomOutputFormat OutputFormatter
57+ | CustomOutputFormatWithDetails OutputFormatterWithDetails
58+ | CustomOutputFormatWithDetailsAndHeaders OutputFormatterWithDetailsAndHeaders
5659
5760type OutputFormatter = ZonedDate -> Request -> Status -> Maybe Integer -> LogStr
58−type OutputFormatterWithDetails =
59− ZonedDate -> Request -> Status -> Maybe Integer -> NominalDiffTime -> [S8.ByteString] -> B.Builder -> LogStr
61+
62+type OutputFormatterWithDetails
63+ = ZonedDate
64+ -> Request
65+ -> Status
66+ -> Maybe Integer
67+ -> NominalDiffTime
68+ -> [S8.ByteString]
69+ -> B.Builder
70+ -> LogStr
71+
72+-- | Same as @OutputFormatterWithDetails@ but with response headers included
73+--
74+-- This is useful if you wish to include arbitrary application data in your
75+-- logs, e.g., an authenticated user ID, which you would set in a response
76+-- header in your application and retrieve in the log formatter.
77+--
78+-- @since 3.0.27
79+type OutputFormatterWithDetailsAndHeaders
80+ = ZonedDate -- ^ When the log message was generated
81+ -> Request -- ^ The WAI request
82+ -> Status -- ^ HTTP status code
83+ -> Maybe Integer -- ^ Response size
84+ -> NominalDiffTime -- ^ Duration of the request
85+ -> [S8.ByteString] -- ^ The request body
86+ -> B.Builder -- ^ Raw response
87+ -> [Header] -- ^ The response headers
88+ -> LogStr
6089
6190data Destination = Handle Handle
6291 | Logger LoggerSet
@@ -108,6 +137,9 @@ mkRequestLogger RequestLoggerSettings{..} = do
108137 CustomOutputFormatWithDetails formatter -> do
109138 getdate <- getDateGetter flusher
110139 return $ customMiddlewareWithDetails callbackAndFlush getdate formatter
140+ CustomOutputFormatWithDetailsAndHeaders formatter -> do
141+ getdate <- getDateGetter flusher
142+ return $ customMiddlewareWithDetailsAndHeaders callbackAndFlush getdate formatter
111143
112144apacheMiddleware :: ApacheLoggerActions -> Middleware
113145apacheMiddleware ala app req sendResponse = app req $ \res -> do
@@ -138,6 +170,24 @@ customMiddlewareWithDetails cb getdate formatter app req sendResponse = do
138170 readIORef builderIO
139171 return rspRcv
140172
173+customMiddlewareWithDetailsAndHeaders :: Callback -> IO ZonedDate -> OutputFormatterWithDetailsAndHeaders -> Middleware
174+customMiddlewareWithDetailsAndHeaders cb getdate formatter app req sendResponse = do
175+ (req', reqBody) <- getRequestBody req
176+ t0 <- getCurrentTime
177+ app req' $ \res -> do
178+ t1 <- getCurrentTime
179+ date <- liftIO getdate
180+ let msize = contentLength (responseHeaders res)
181+ builderIO <- newIORef $ B.byteString ""
182+ res' <- recordChunks builderIO res
183+ rspRcv <- sendResponse res'
184+ _ <- do
185+ rawResponse <- readIORef builderIO
186+ let status = responseStatus res'
187+ duration = t1 `diffUTCTime` t0
188+ resHeaders = responseHeaders res'
189+ liftIO . cb $ formatter date req' status msize duration reqBody rawResponse resHeaders
190+ return rspRcv
141191-- | Production request logger middleware.
142192--
143193-- This uses the 'Apache' logging format, and takes IP addresses for clients from