-- |
-- Module      : Amazonka.Data.Log
-- Copyright   : (c) 2013-2021 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
module Amazonka.Data.Log where

import Amazonka.Data.ByteString
import Amazonka.Data.Headers
import Amazonka.Data.Path
import Amazonka.Data.Query
import Amazonka.Data.Text
import Amazonka.Prelude
import qualified Data.ByteString as BS
import qualified Data.ByteString.Builder as Build
import qualified Data.ByteString.Lazy as LBS
import qualified Data.CaseInsensitive as CI
import qualified Data.List as List
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Lazy as LText
import qualified Data.Text.Lazy.Encoding as LText
import qualified Network.HTTP.Client as Client
import qualified Network.HTTP.Types as HTTP
import qualified Numeric as Numeric

class ToLog a where
  -- | Convert a value to a loggable builder.
  build :: a -> ByteStringBuilder

instance ToLog ByteStringBuilder where
  build :: ByteStringBuilder -> ByteStringBuilder
build = ByteStringBuilder -> ByteStringBuilder
forall a. a -> a
id

instance ToLog ByteStringLazy where
  build :: ByteStringLazy -> ByteStringBuilder
build = ByteStringLazy -> ByteStringBuilder
Build.lazyByteString

instance ToLog ByteString where
  build :: ByteString -> ByteStringBuilder
build = ByteString -> ByteStringBuilder
Build.byteString

instance ToLog Int where
  build :: Int -> ByteStringBuilder
build = Int -> ByteStringBuilder
Build.intDec

instance ToLog Int8 where
  build :: Int8 -> ByteStringBuilder
build = Int8 -> ByteStringBuilder
Build.int8Dec

instance ToLog Int16 where
  build :: Int16 -> ByteStringBuilder
build = Int16 -> ByteStringBuilder
Build.int16Dec

instance ToLog Int32 where
  build :: Int32 -> ByteStringBuilder
build = Int32 -> ByteStringBuilder
Build.int32Dec

instance ToLog Int64 where
  build :: Int64 -> ByteStringBuilder
build = Int64 -> ByteStringBuilder
Build.int64Dec

instance ToLog Integer where
  build :: Integer -> ByteStringBuilder
build = Integer -> ByteStringBuilder
Build.integerDec

instance ToLog Word where
  build :: Word -> ByteStringBuilder
build = Word -> ByteStringBuilder
Build.wordDec

instance ToLog Word8 where
  build :: Word8 -> ByteStringBuilder
build = Word8 -> ByteStringBuilder
Build.word8Dec

instance ToLog Word16 where
  build :: Word16 -> ByteStringBuilder
build = Word16 -> ByteStringBuilder
Build.word16Dec

instance ToLog Word32 where
  build :: Word32 -> ByteStringBuilder
build = Word32 -> ByteStringBuilder
Build.word32Dec

instance ToLog Word64 where
  build :: Word64 -> ByteStringBuilder
build = Word64 -> ByteStringBuilder
Build.word64Dec

instance ToLog UTCTime where
  build :: UTCTime -> ByteStringBuilder
build = String -> ByteStringBuilder
Build.stringUtf8 (String -> ByteStringBuilder)
-> (UTCTime -> String) -> UTCTime -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> String
forall a. Show a => a -> String
show

instance ToLog Float where
  build :: Float -> ByteStringBuilder
build = String -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (String -> ByteStringBuilder)
-> (Float -> String) -> Float -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String
"") ((String -> String) -> String)
-> (Float -> String -> String) -> Float -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Int -> Float -> String -> String
forall a. RealFloat a => Maybe Int -> a -> String -> String
Numeric.showFFloat Maybe Int
forall a. Maybe a
Nothing

instance ToLog Double where
  build :: Double -> ByteStringBuilder
build = String -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (String -> ByteStringBuilder)
-> (Double -> String) -> Double -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String
"") ((String -> String) -> String)
-> (Double -> String -> String) -> Double -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Int -> Double -> String -> String
forall a. RealFloat a => Maybe Int -> a -> String -> String
Numeric.showFFloat Maybe Int
forall a. Maybe a
Nothing

instance ToLog Text where
  build :: Text -> ByteStringBuilder
build = ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ByteString -> ByteStringBuilder)
-> (Text -> ByteString) -> Text -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> ByteString
Text.encodeUtf8

instance ToLog TextLazy where
  build :: TextLazy -> ByteStringBuilder
build = ByteStringLazy -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ByteStringLazy -> ByteStringBuilder)
-> (TextLazy -> ByteStringLazy) -> TextLazy -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TextLazy -> ByteStringLazy
LText.encodeUtf8

instance ToLog Char where
  build :: Char -> ByteStringBuilder
build = Text -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Text -> ByteStringBuilder)
-> (Char -> Text) -> Char -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Text
Text.singleton

instance ToLog [Char] where
  build :: String -> ByteStringBuilder
build = TextLazy -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (TextLazy -> ByteStringBuilder)
-> (String -> TextLazy) -> String -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> TextLazy
LText.pack

instance ToLog HTTP.StdMethod where
  build :: StdMethod -> ByteStringBuilder
build = ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ByteString -> ByteStringBuilder)
-> (StdMethod -> ByteString) -> StdMethod -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StdMethod -> ByteString
HTTP.renderStdMethod

instance ToLog QueryString where
  build :: QueryString -> ByteStringBuilder
build = ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ByteString -> ByteStringBuilder)
-> (QueryString -> ByteString) -> QueryString -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. QueryString -> ByteString
forall a. ToByteString a => a -> ByteString
toBS

instance ToLog EscapedPath where
  build :: EscapedPath -> ByteStringBuilder
build = ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ByteString -> ByteStringBuilder)
-> (EscapedPath -> ByteString) -> EscapedPath -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. EscapedPath -> ByteString
forall a. ToByteString a => a -> ByteString
toBS

-- | Intercalate a list of 'ByteStringBuilder's with newlines.
buildLines :: [ByteStringBuilder] -> ByteStringBuilder
buildLines :: [ByteStringBuilder] -> ByteStringBuilder
buildLines = [ByteStringBuilder] -> ByteStringBuilder
forall a. Monoid a => [a] -> a
mconcat ([ByteStringBuilder] -> ByteStringBuilder)
-> ([ByteStringBuilder] -> [ByteStringBuilder])
-> [ByteStringBuilder]
-> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteStringBuilder -> [ByteStringBuilder] -> [ByteStringBuilder]
forall a. a -> [a] -> [a]
List.intersperse ByteStringBuilder
"\n"

instance ToLog a => ToLog (CI a) where
  build :: CI a -> ByteStringBuilder
build = a -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (a -> ByteStringBuilder)
-> (CI a -> a) -> CI a -> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CI a -> a
forall s. CI s -> s
CI.foldedCase

instance ToLog a => ToLog (Maybe a) where
  build :: Maybe a -> ByteStringBuilder
build Maybe a
Nothing = ByteStringBuilder
"Nothing"
  build (Just a
x) = ByteStringBuilder
"Just " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> a -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build a
x

instance ToLog Bool where
  build :: Bool -> ByteStringBuilder
build Bool
True = ByteStringBuilder
"True"
  build Bool
False = ByteStringBuilder
"False"

instance ToLog HTTP.Status where
  build :: Status -> ByteStringBuilder
build Status
x = Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Status -> Int
HTTP.statusCode Status
x) ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
" " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Status -> ByteString
HTTP.statusMessage Status
x)

instance ToLog [HTTP.Header] where
  build :: [Header] -> ByteStringBuilder
build =
    [ByteStringBuilder] -> ByteStringBuilder
forall a. Monoid a => [a] -> a
mconcat
      ([ByteStringBuilder] -> ByteStringBuilder)
-> ([Header] -> [ByteStringBuilder])
-> [Header]
-> ByteStringBuilder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteStringBuilder -> [ByteStringBuilder] -> [ByteStringBuilder]
forall a. a -> [a] -> [a]
List.intersperse ByteStringBuilder
"; "
      ([ByteStringBuilder] -> [ByteStringBuilder])
-> ([Header] -> [ByteStringBuilder])
-> [Header]
-> [ByteStringBuilder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Header -> ByteStringBuilder) -> [Header] -> [ByteStringBuilder]
forall a b. (a -> b) -> [a] -> [b]
map (\(HeaderName
k, ByteString
v) -> HeaderName -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build HeaderName
k ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
": " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build ByteString
v)

instance ToLog HTTP.HttpVersion where
  build :: HttpVersion -> ByteStringBuilder
build HTTP.HttpVersion {Int
httpMajor :: HttpVersion -> Int
httpMajor :: Int
httpMajor, Int
httpMinor :: HttpVersion -> Int
httpMinor :: Int
httpMinor} =
    ByteStringBuilder
"HTTP/"
      ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int
httpMajor
      ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Char -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Char
'.'
      ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int
httpMinor

instance ToLog Client.RequestBody where
  build :: RequestBody -> ByteStringBuilder
build = \case
    Client.RequestBodyBuilder Int64
n ByteStringBuilder
_ -> ByteStringBuilder
" <builder:" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int64 -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int64
n ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
">"
    Client.RequestBodyStream Int64
n GivesPopper ()
_ -> ByteStringBuilder
" <stream:" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int64 -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int64
n ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
">"
    Client.RequestBodyLBS ByteStringLazy
lbs
      | Int64
n Int64 -> Int64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Int64
4096 -> ByteStringLazy -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build ByteStringLazy
lbs
      | Bool
otherwise -> ByteStringBuilder
" <lazy:" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int64 -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int64
n ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
">"
      where
        n :: Int64
n = ByteStringLazy -> Int64
LBS.length ByteStringLazy
lbs
    Client.RequestBodyBS ByteString
bs
      | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
4096 -> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build ByteString
bs
      | Bool
otherwise -> ByteStringBuilder
" <strict:" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Int
n ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
">"
      where
        n :: Int
n = ByteString -> Int
BS.length ByteString
bs
    RequestBody
_ -> ByteStringBuilder
" <chunked>"

instance ToLog Client.HttpException where
  build :: HttpException -> ByteStringBuilder
build HttpException
x = ByteStringBuilder
"[HttpException] {\n" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> String -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (HttpException -> String
forall a. Show a => a -> String
show HttpException
x) ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
"\n}"

instance ToLog Client.HttpExceptionContent where
  build :: HttpExceptionContent -> ByteStringBuilder
build HttpExceptionContent
x = ByteStringBuilder
"[HttpExceptionContent] {\n" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> String -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (HttpExceptionContent -> String
forall a. Show a => a -> String
show HttpExceptionContent
x) ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
"\n}"

instance ToLog Client.Request where
  build :: Request -> ByteStringBuilder
build Request
x =
    [ByteStringBuilder] -> ByteStringBuilder
buildLines
      [ ByteStringBuilder
"[Client Request] {",
        ByteStringBuilder
"  host      = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> ByteString
Client.host Request
x) ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteStringBuilder
":" ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> Int
Client.port Request
x),
        ByteStringBuilder
"  secure    = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Bool -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> Bool
Client.secure Request
x),
        ByteStringBuilder
"  method    = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> ByteString
Client.method Request
x),
        ByteStringBuilder
"  target    = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Maybe ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build Maybe ByteString
target,
        ByteStringBuilder
"  timeout   = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> String -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (ResponseTimeout -> String
forall a. Show a => a -> String
show (Request -> ResponseTimeout
Client.responseTimeout Request
x)),
        ByteStringBuilder
"  redirects = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Int -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> Int
Client.redirectCount Request
x),
        ByteStringBuilder
"  path      = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> ByteString
Client.path Request
x),
        ByteStringBuilder
"  query     = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> ByteString -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> ByteString
Client.queryString Request
x),
        ByteStringBuilder
"  headers   = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> [Header] -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> [Header]
Client.requestHeaders Request
x),
        ByteStringBuilder
"  body      = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> RequestBody -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Request -> RequestBody
Client.requestBody Request
x),
        ByteStringBuilder
"}"
      ]
    where
      target :: Maybe ByteString
target = HeaderName
hAMZTarget HeaderName -> [Header] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
`lookup` Request -> [Header]
Client.requestHeaders Request
x

instance ToLog (Client.Response a) where
  build :: Response a -> ByteStringBuilder
build Response a
x =
    [ByteStringBuilder] -> ByteStringBuilder
buildLines
      [ ByteStringBuilder
"[Client Response] {",
        ByteStringBuilder
"  status  = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> Status -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Response a -> Status
forall body. Response body -> Status
Client.responseStatus Response a
x),
        ByteStringBuilder
"  headers = " ByteStringBuilder -> ByteStringBuilder -> ByteStringBuilder
forall a. Semigroup a => a -> a -> a
<> [Header] -> ByteStringBuilder
forall a. ToLog a => a -> ByteStringBuilder
build (Response a -> [Header]
forall body. Response body -> [Header]
Client.responseHeaders Response a
x),
        ByteStringBuilder
"}"
      ]