diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # Changelog for wai-extra
 
+## 3.1.4
+
+* Export `Network.Wai.Middleware.RequestLogger.JSON.requestToJSON` [#827](https://github.com/yesodweb/wai/pull/827)
+
 ## 3.1.3
 
 * Add a `DetailedWithSettings` output format for `RequestLogger` that allows to hide requests and modify query parameters [#826](https://github.com/yesodweb/wai/pull/826)
diff --git a/Network/Wai/Middleware/RequestLogger/JSON.hs b/Network/Wai/Middleware/RequestLogger/JSON.hs
--- a/Network/Wai/Middleware/RequestLogger/JSON.hs
+++ b/Network/Wai/Middleware/RequestLogger/JSON.hs
@@ -4,12 +4,15 @@
 module Network.Wai.Middleware.RequestLogger.JSON
   ( formatAsJSON
   , formatAsJSONWithHeaders
+
+  , requestToJSON
   ) where
 
 import qualified Data.ByteString.Builder as BB (toLazyByteString)
 import Data.ByteString.Lazy (toStrict)
 import Data.Aeson
 import Data.CaseInsensitive (original)
+import Data.Maybe (maybeToList)
 import Data.Monoid ((<>))
 import qualified Data.ByteString.Char8 as S8
 import Data.IP
@@ -30,7 +33,7 @@
 formatAsJSON date req status responseSize duration reqBody response =
   toLogStr (encode $
     object
-      [ "request"  .= requestToJSON duration req reqBody
+      [ "request"  .= requestToJSON req reqBody (Just duration)
       , "response" .=
       object
         [ "status" .= statusCode status
@@ -54,7 +57,7 @@
 formatAsJSONWithHeaders date req status resSize duration reqBody res resHeaders =
   toLogStr (encode $
     object
-      [ "request"  .= requestToJSON duration req reqBody
+      [ "request"  .= requestToJSON req reqBody (Just duration)
       , "response" .= object
         [ "status" .= statusCode status
         , "size"   .= resSize
@@ -73,19 +76,47 @@
 readAsDouble :: String -> Double
 readAsDouble = read
 
-requestToJSON :: NominalDiffTime -> Request -> [S8.ByteString] -> Value
-requestToJSON duration req reqBody =
-  object
+-- | Get the JSON representation for a request
+--
+-- This representation is identical to that used in 'formatAsJSON' for the
+-- request. It includes:
+--
+--   [@method@]:
+--   [@path@]:
+--   [@queryString@]:
+--   [@size@]: The size of the body, as defined in the request. This may differ
+--   from the size of the data passed in the second argument.
+--   [@body@]: The body, concatenated directly from the chunks passed in
+--   [@remoteHost@]:
+--   [@httpVersion@]:
+--   [@headers@]:
+--
+-- If a @'Just' duration@ is passed in, then additionally the JSON includes:
+--
+--   [@durationMs@] The duration, formatted in milliseconds, to 2 decimal
+--   places
+--
+-- This representation is not an API, and may change at any time (within reason)
+-- without a major version bump.
+--
+-- @since 3.1.4
+requestToJSON :: Request -- ^ The WAI request
+              -> [S8.ByteString] -- ^ Chunked request body
+              -> Maybe NominalDiffTime -- ^ Optional request duration
+              -> Value
+requestToJSON req reqBody duration =
+  object $
     [ "method" .= decodeUtf8With lenientDecode (requestMethod req)
     , "path" .= decodeUtf8With lenientDecode (rawPathInfo req)
     , "queryString" .= map queryItemToJSON (queryString req)
-    , "durationMs" .= (readAsDouble . printf "%.2f" . rationalToDouble $ toRational duration * 1000)
     , "size" .= requestBodyLengthToJSON (requestBodyLength req)
     , "body" .= decodeUtf8With lenientDecode (S8.concat reqBody)
     , "remoteHost" .= sockToJSON (remoteHost req)
     , "httpVersion" .= httpVersionToJSON (httpVersion req)
     , "headers" .= requestHeadersToJSON (requestHeaders req)
     ]
+    <>
+    maybeToList (("durationMs" .=) . readAsDouble . printf "%.2f" . rationalToDouble . (* 1000) . toRational <$> duration)
   where
     rationalToDouble :: Rational -> Double
     rationalToDouble = fromRational
diff --git a/wai-extra.cabal b/wai-extra.cabal
--- a/wai-extra.cabal
+++ b/wai-extra.cabal
@@ -1,5 +1,5 @@
 Name:                wai-extra
-Version:             3.1.3
+Version:             3.1.4
 Synopsis:            Provides some basic WAI handlers and middleware.
 description:
   Provides basic WAI handler and middleware functionality:
