packages feed

honeycomb-0.0.0.3: src/Honeycomb/API/Events.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Honeycomb.API.Events
  ( Event(..)
  , sendEvent
  , sendBatchedEvents
  , sendBatchedEvents'
  , BatchResponse(..)
  , BatchOptions(..)
  ) where
import Chronos ( timeToDatetime )
import Control.Exception
import Data.Aeson
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as L
import Control.Monad.IO.Class
import Data.Maybe
import qualified Data.Text.Encoding as T
import Data.Typeable
import Data.Vector (Vector)
import Honeycomb.Client.Internal hiding (Event)
import Honeycomb.Types
import Honeycomb.API.Types
import Network.HTTP.Simple
import Network.HTTP.Types
import Lens.Micro ( (^.), to, )
import Control.Monad.Reader (MonadReader, asks)
import Lens.Micro.Extras (view)
import Honeycomb.Config (defaultDataset, configL)

data MalformedJSONResponse = MalformedJSONResponse
  { malformedJSONResponseMessage :: String
  , malformedJSONResponseBody :: L.ByteString
  }
  deriving stock (Show, Typeable)
  deriving anyclass (Exception)

data FailureResponse
  = UnknownApiKey
  | RequestBodyTooLarge
  | MalformedRequestBody
  | EventDroppedDueToThrottling
  | EventDroppedDueToBlacklist
  | RequestDroppedDueToRateLimiting
  | UnrecognizedError Status L.ByteString 
  deriving stock (Show, Typeable)
  deriving anyclass (Exception)

sendEvent :: (MonadHoneycomb client m) => Event -> m ()
sendEvent e = do
  client <- asks (view honeycombClientL)
  let ds = client ^. configL . to defaultDataset
  r <- post httpLBS ["1", "events", fromDatasetName ds] hs $ eventData e
  case (statusCode $ getResponseStatus r, getResponseBody r) of
    (400, "unknown API key - check your credentials") -> throw UnknownApiKey
    (400, "request body is too large") -> throw RequestBodyTooLarge
    (400, "request body is malformed and cannot be read as JSON") -> throw MalformedRequestBody
    (403, "event dropped due to administrative throttling") -> throw EventDroppedDueToThrottling
    (429, "event dropped due to administrative blacklist") -> throw EventDroppedDueToBlacklist
    (429, "request dropped due to rate limiting") -> throw RequestDroppedDueToRateLimiting
    (200, _) -> pure ()
    (_, str) -> throw $ UnrecognizedError (getResponseStatus r) str
  where
    hs = catMaybes
      [ (\d -> ("X-Honeycomb-Event-Time", T.encodeUtf8 $ encodeRFC3339 $ timeToDatetime d)) <$> eventTimestamp e
      , (\r -> ("X-Honeycomb-Samplerate", C.pack $ show r)) <$> eventSampleRate e
      ]

{-
There are a few limits to note in regards to the events API:

Requests to the individual event endpoint have a maximum request body size of 100KB.
Requests to the batched events endpoint have a maximum request body size of 5MB. Individual event bodies in the batch are limited to 100KB each.
The maximum number of distinct columns (fields) allowed per event is 2000.

Size limitations may be addressed by gzipping request bodies. Be sure to set the Content-Encoding: gzip
-}
newtype BatchOptions = BatchOptions 
  { useGZip :: Bool
  } deriving (Show, Read)

sendBatchedEvents :: (MonadHoneycomb client m) => Vector Event -> m (Vector BatchResponse)
sendBatchedEvents = sendBatchedEvents' (BatchOptions False)

newtype BatchResponse = BatchResponse { batchResponseStatus :: Int }
  deriving (Show)

instance FromJSON BatchResponse where
  parseJSON = withObject "BatchResponse" $ \o -> BatchResponse <$> (o .: "status")

sendBatchedEvents' :: (MonadHoneycomb client m) => BatchOptions -> Vector Event -> m (Vector BatchResponse)
sendBatchedEvents' _ events = do
  config <- asks (view (honeycombClientL . configL))
  let ds = defaultDataset config
  r <- post httpLBS ["1", "batch", fromDatasetName ds] [] events
  case getResponseBody $ decodeJSON r of
    Left err -> throw $ MalformedJSONResponse err (getResponseBody r)
    Right ok -> pure ok