@@ -1,8 +1,10 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE RankNTypes #-}
4{-# LANGUAGE RankNTypes #-}
4{-# LANGUAGE RecordWildCards #-}
5{-# LANGUAGE RecordWildCards #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TypeFamilies #-}
6-- | Includes a scribe that can be used to log structured, JSON log
8-- | Includes a scribe that can be used to log structured, JSON log
7-- messages to ElasticSearch. These logs can be explored easily using
9-- messages to ElasticSearch. These logs can be explored easily using
8-- <https://www.elastic.co/products/kibana kibana> or your tool of
10-- <https://www.elastic.co/products/kibana kibana> or your tool of
@@ -40,6 +42,10 @@ module Katip.Scribes.ElasticSearch
40 , IndexShardingPolicy(..)
42 , IndexShardingPolicy(..)
41 , IndexNameSegment(..)
43 , IndexNameSegment(..)
42 , defaultEsScribeCfg
44 , defaultEsScribeCfg
45+ , defaultEsScribeCfgV1
46+ , defaultEsScribeCfgV5
43 -- * Utilities
49 -- * Utilities
45 , module Katip.Scribes.ElasticSearch.Annotations
51 , module Katip.Scribes.ElasticSearch.Annotations
@@ -63,16 +69,18 @@ import Control.Retry (RetryPolicy,
63 limitRetries,
69 limitRetries,
64 recovering)
70 recovering)
65import Data.Aeson
71import Data.Aeson
72+import Data.ByteString.Lazy (ByteString)
66import Data.Monoid ((<>))
73import Data.Monoid ((<>))
67import Data.Text (Text)
74import Data.Text (Text)
68import qualified Data.Text as T
75import qualified Data.Text as T
69import qualified Data.Text.Encoding as T
76import qualified Data.Text.Encoding as T
70import Data.Time
77import Data.Time
71import Data.Time.Calendar.WeekDate
78import Data.Time.Calendar.WeekDate
72−import Data.Typeable
79+import Data.Typeable as Typeable
73import Data.UUID
80import Data.UUID
74import qualified Data.UUID.V4 as UUID4
81import qualified Data.UUID.V4 as UUID4
82+import qualified Database.V1.Bloodhound as V1
75−import Database.Bloodhound
83+import qualified Database.V5.Bloodhound as V5
76import Network.HTTP.Client
84import Network.HTTP.Client
77import Network.HTTP.Types.Status
85import Network.HTTP.Types.Status
78import Text.Printf (printf)
86import Text.Printf (printf)
@@ -82,7 +90,7 @@ import Katip.Scribes.ElasticSearch.Annotations
82-------------------------------------------------------------------------------
90-------------------------------------------------------------------------------
85−data EsScribeCfg = EsScribeCfg {
93+data EsScribeCfg v = EsScribeCfg {
86 essRetryPolicy :: RetryPolicy
94 essRetryPolicy :: RetryPolicy
87 -- ^ Retry policy when there are errors sending logs to the server
95 -- ^ Retry policy when there are errors sending logs to the server
88 , essQueueSize :: EsQueueSize
96 , essQueueSize :: EsQueueSize
@@ -102,7 +110,7 @@ data EsScribeCfg = EsScribeCfg {
102 -- exposes a querying API, we will try to make deserialization and
110 -- exposes a querying API, we will try to make deserialization and
103 -- querying transparently remove the type annotations if this is
111 -- querying transparently remove the type annotations if this is
104 -- enabled.
112 -- enabled.
105− , essIndexSettings :: IndexSettings
113+ , essIndexSettings :: IndexSettings v
106 , essIndexSharding :: IndexShardingPolicy
114 , essIndexSharding :: IndexShardingPolicy
107 } deriving (Typeable)
115 } deriving (Typeable)
@@ -120,15 +128,30 @@ data EsScribeCfg = EsScribeCfg {
120-- * Annotate types set to False
128-- * Annotate types set to False
122-- * DailyIndexSharding
130-- * DailyIndexSharding
123−defaultEsScribeCfg :: EsScribeCfg
131+defaultEsScribeCfg :: forall v. ESVersion v => EsScribeCfg v
124defaultEsScribeCfg = EsScribeCfg {
132defaultEsScribeCfg = EsScribeCfg {
125 essRetryPolicy = exponentialBackoff 25 <> limitRetries 5
133 essRetryPolicy = exponentialBackoff 25 <> limitRetries 5
126 , essQueueSize = EsQueueSize 1000
134 , essQueueSize = EsQueueSize 1000
127 , essPoolSize = EsPoolSize 2
135 , essPoolSize = EsPoolSize 2
128 , essAnnotateTypes = False
136 , essAnnotateTypes = False
129− , essIndexSettings = defaultIndexSettings
137+ , essIndexSettings = defaultIndexSettings prx
130 , essIndexSharding = DailyIndexSharding
138 , essIndexSharding = DailyIndexSharding
141+ prx :: Typeable.Proxy v
142+ prx = Typeable.Proxy
145+-------------------------------------------------------------------------------
146+-- | EsScribeCfg that will use ElasticSearch V1
147+defaultEsScribeCfgV1 :: EsScribeCfg ESV1
148+defaultEsScribeCfgV1 = defaultEsScribeCfg
151+-------------------------------------------------------------------------------
152+-- | EsScribeCfg that will use ElasticSearch V5
153+defaultEsScribeCfgV5 :: EsScribeCfg ESV5
154+defaultEsScribeCfgV5 = defaultEsScribeCfg
134-------------------------------------------------------------------------------
157-------------------------------------------------------------------------------
@@ -224,9 +247,9 @@ roundToSunday d
226-------------------------------------------------------------------------------
249-------------------------------------------------------------------------------
227−chooseIxn :: IndexName -> IndexShardingPolicy -> Item a -> IndexName
250+chooseIxn :: ESVersion v => proxy v -> IndexName v -> IndexShardingPolicy -> Item a -> IndexName v
228−chooseIxn (IndexName ixn) p i =
251+chooseIxn prx ixn p i =
229− IndexName (T.intercalate "-" (ixn:segs))
252+ toIndexName prx (T.intercalate "-" ((fromIndexName prx ixn):segs))
231 segs = indexNameSegment A.<$> shardPolicySegs p i
254 segs = indexNameSegment A.<$> shardPolicySegs p i
@@ -246,19 +269,23 @@ splitTime t = asMins `divMod` 60
248-------------------------------------------------------------------------------
271-------------------------------------------------------------------------------
249−data EsScribeSetupError = CouldNotCreateIndex !Reply
272+data EsScribeSetupError = CouldNotCreateIndex !(Response ByteString)
250− | CouldNotCreateMapping !Reply deriving (Typeable, Show)
273+ | CouldNotCreateMapping !(Response ByteString) deriving (Typeable, Show)
253instance Exception EsScribeSetupError
276instance Exception EsScribeSetupError
255-------------------------------------------------------------------------------
279-------------------------------------------------------------------------------
256mkEsScribe
280mkEsScribe
281+ :: forall v. ( ESVersion v
257− :: EsScribeCfg
284+ => EsScribeCfg v
258− -> BHEnv
285+ -> BHEnv v
259− -> IndexName
286+ -> IndexName v
260 -- ^ Treated as a prefix if index sharding is enabled
287 -- ^ Treated as a prefix if index sharding is enabled
261− -> MappingName
288+ -> MappingName v
262 -> Severity
289 -> Severity
263 -> Verbosity
290 -> Verbosity
264 -> IO Scribe
291 -> IO Scribe
@@ -266,17 +293,17 @@ mkEsScribe cfg@EsScribeCfg {..} env ix mapping sev verb = do
266 q <- newTBMQueueIO $ unEsQueueSize essQueueSize
293 q <- newTBMQueueIO $ unEsQueueSize essQueueSize
267 endSig <- newEmptyMVar
294 endSig <- newEmptyMVar
269− runBH env $ do
296+ runBH prx env $ do
270− chk <- indexExists ix
297+ chk <- indexExists prx ix
271 -- note that this doesn't update settings. That's not available
298 -- note that this doesn't update settings. That's not available
272 -- through the Bloodhound API yet
299 -- through the Bloodhound API yet
273 unless chk $ void $ do
300 unless chk $ void $ do
274− r1 <- createIndex essIndexSettings ix
301+ r1 <- createIndex prx essIndexSettings ix
275 unless (statusIsSuccessful (responseStatus r1)) $
302 unless (statusIsSuccessful (responseStatus r1)) $
276 liftIO $ throwIO (CouldNotCreateIndex r1)
303 liftIO $ throwIO (CouldNotCreateIndex r1)
277 r2 <- if shardingEnabled
304 r2 <- if shardingEnabled
278− then putTemplate tpl tplName
305+ then putTemplate prx tpl tplName
279− else putMapping ix mapping (baseMapping mapping)
306+ else putMapping prx ix mapping base
280 unless (statusIsSuccessful (responseStatus r2)) $
307 unless (statusIsSuccessful (responseStatus r2)) $
281 liftIO $ throwIO (CouldNotCreateMapping r2)
308 liftIO $ throwIO (CouldNotCreateMapping r2)
@@ -289,27 +316,31 @@ mkEsScribe cfg@EsScribeCfg {..} env ix mapping sev verb = do
289 mapM_ waitCatch workers
316 mapM_ waitCatch workers
290 putMVar endSig ()
317 putMVar endSig ()
293− when (_itemSeverity i >= sev) $
294− void $ atomically $ tryWriteTBMQueue q (chooseIxn ix essIndexSharding i, itemJson' i)
295 let finalizer = putMVar endSig () >> takeMVar endSig
319 let finalizer = putMVar endSig () >> takeMVar endSig
296− return (Scribe logger finalizer)
320+ return (Scribe (logger q) finalizer)
322+ logger :: forall a. LogItem a => TBMQueue (IndexName v, Value) -> Item a -> IO ()
323+ logger q i = when (_itemSeverity i >= sev) $
324+ void $ atomically $ tryWriteTBMQueue q (chooseIxn prx ix essIndexSharding i, itemJson' i)
325+ prx :: Typeable.Proxy v
326+ prx = Typeable.Proxy
298− tplName = TemplateName ixn
327+ tplName = toTemplateName prx ixn
299 shardingEnabled = case essIndexSharding of
328 shardingEnabled = case essIndexSharding of
300 NoIndexSharding -> False
329 NoIndexSharding -> False
301 _ -> True
330 _ -> True
302− tpl = IndexTemplate (TemplatePattern (ixn <> "-*")) (Just essIndexSettings) [toJSON (baseMapping mapping)]
331+ tpl = toIndexTemplate prx (toTemplatePattern prx (ixn <> "-*")) (Just essIndexSettings) [toJSON base]
303− IndexName ixn = ix
332+ base = baseMapping prx mapping
333+ ixn = fromIndexName prx ix
334+ itemJson' :: LogItem a => Item a -> Value
304 itemJson' i
335 itemJson' i
305 | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
336 | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
306 | otherwise = itemJson verb i
337 | otherwise = itemJson verb i
309-------------------------------------------------------------------------------
340-------------------------------------------------------------------------------
310−baseMapping :: MappingName -> Value
341+baseMapping :: ESVersion v => proxy v -> MappingName v -> Value
311−baseMapping (MappingName mn) =
342+baseMapping prx mn =
312− object [ mn .= object ["properties" .= object prs] ]
343+ object [ fromMappingName prx mn .= object ["properties" .= object prs] ]
313 where prs = [ str "thread"
344 where prs = [ str "thread"
314 , str "sev"
345 , str "sev"
315 , str "pid"
346 , str "pid"
@@ -341,8 +372,8 @@ esDateFormat = "yyyy-MM-dd'T'HH:mm:ssZ||yyyy-MM-dd'T'HH:mm:ss.SSSZ||yyyy-MM-dd'T
343-------------------------------------------------------------------------------
374-------------------------------------------------------------------------------
344−mkDocId :: IO DocId
375+mkDocId :: ESVersion v => proxy v -> IO (DocId v)
345−mkDocId = (DocId . T.decodeUtf8 . toASCIIBytes) `fmap` UUID4.nextRandom
376+mkDocId prx = (toDocId prx . T.decodeUtf8 . toASCIIBytes) `fmap` UUID4.nextRandom
348-------------------------------------------------------------------------------
379-------------------------------------------------------------------------------
@@ -384,10 +415,11 @@ mkNonZero ctor n
385-------------------------------------------------------------------------------
416-------------------------------------------------------------------------------
386startWorker
417startWorker
418+ :: forall v. (ESVersion v)
387− :: EsScribeCfg
419+ => EsScribeCfg v
388− -> BHEnv
420+ -> BHEnv v
389− -> MappingName
421+ -> MappingName v
390− -> TBMQueue (IndexName, Value)
422+ -> TBMQueue (IndexName v, Value)
392startWorker EsScribeCfg {..} env mapping q = go
424startWorker EsScribeCfg {..} env mapping q = go
@@ -398,13 +430,112 @@ startWorker EsScribeCfg {..} env mapping q = go
398 sendLog ixn v `catchAny` eat
430 sendLog ixn v `catchAny` eat
400 Nothing -> return ()
432 Nothing -> return ()
433+ prx :: Typeable.Proxy v
434+ prx = Typeable.Proxy
401− sendLog :: IndexName -> Value -> IO ()
435+ sendLog :: IndexName v -> Value -> IO ()
402 sendLog ixn v = void $ recovering essRetryPolicy [handler] $ const $ do
436 sendLog ixn v = void $ recovering essRetryPolicy [handler] $ const $ do
403− did <- mkDocId
437+ did <- mkDocId prx
404− res <- runBH env $ indexDocument ixn mapping defaultIndexDocumentSettings v did
438+ res <- runBH prx env $ indexDocument prx ixn mapping (defaultIndexDocumentSettings prx) v did
405 return res
439 return res
406 eat _ = return ()
440 eat _ = return ()
407 handler _ = Handler $ \e ->
441 handler _ = Handler $ \e ->
408 case fromException e of
442 case fromException e of
409 Just (_ :: AsyncException) -> return False
443 Just (_ :: AsyncException) -> return False
410− _ -> return True
444+ _ -> return True
447+-------------------------------------------------------------------------------
448+-- We are spanning multiple versions of ES which use completely
449+-- separate types and APIs, but the subset we use is the same for both
450+-- versions. This will be kept up to date with bloodhound's supported
451+-- versions and should be minimally visible to the end user.
452+class ESVersion v where
455+ type IndexSettings v
456+ defaultIndexSettings :: proxy v -> IndexSettings v
458+ toIndexName :: proxy v -> Text -> IndexName v
459+ fromIndexName :: proxy v -> IndexName v -> Text
461+ fromMappingName :: proxy v -> MappingName v -> Text
463+ toDocId :: proxy v -> Text -> DocId v
464+ type BH v :: (* -> *) -> * -> *
465+ runBH :: proxy v -> BHEnv v -> BH v m a -> m a
467+ toTemplateName :: proxy v -> Text -> TemplateName v
468+ type TemplatePattern v
469+ toTemplatePattern :: proxy v -> Text -> TemplatePattern v
470+ type IndexTemplate v
471+ toIndexTemplate :: proxy v -> TemplatePattern v -> Maybe (IndexSettings v) -> [Value] -> IndexTemplate v
472+ type IndexDocumentSettings v
473+ defaultIndexDocumentSettings :: proxy v -> IndexDocumentSettings v
476+ -- We're deciding on IO here, but it isn't necessary
477+ indexExists :: proxy v -> IndexName v -> BH v IO Bool
478+ indexDocument :: ToJSON doc => proxy v -> IndexName v -> MappingName v -> IndexDocumentSettings v -> doc -> DocId v -> BH v IO (Response ByteString)
479+ createIndex :: proxy v -> IndexSettings v -> IndexName v -> BH v IO (Response ByteString)
480+ putTemplate :: proxy v -> IndexTemplate v -> TemplateName v -> BH v IO (Response ByteString)
481+ putMapping :: (ToJSON a) => proxy v -> IndexName v -> MappingName v -> a -> BH v IO (Response ByteString)
486+instance ESVersion ESV1 where
487+ type BHEnv ESV1 = V1.BHEnv
488+ type IndexSettings ESV1 = V1.IndexSettings
489+ defaultIndexSettings _ = V1.defaultIndexSettings
490+ type IndexName ESV1 = V1.IndexName
491+ toIndexName _ = V1.IndexName
492+ fromIndexName _ (V1.IndexName x) = x
493+ type MappingName ESV1 = V1.MappingName
494+ fromMappingName _ (V1.MappingName x) = x
495+ type DocId ESV1 = V1.DocId
496+ toDocId _ = V1.DocId
497+ type BH ESV1 = V1.BH
499+ type TemplateName ESV1 = V1.TemplateName
500+ toTemplateName _ = V1.TemplateName
501+ type TemplatePattern ESV1 = V1.TemplatePattern
502+ toTemplatePattern _ = V1.TemplatePattern
503+ type IndexTemplate ESV1 = V1.IndexTemplate
504+ toIndexTemplate _ = V1.IndexTemplate
505+ type IndexDocumentSettings ESV1 = V1.IndexDocumentSettings
506+ defaultIndexDocumentSettings _ = V1.defaultIndexDocumentSettings
507+ indexExists _ = V1.indexExists
508+ indexDocument _ = V1.indexDocument
509+ createIndex _ = V1.createIndex
510+ putTemplate _ = V1.putTemplate
511+ putMapping _ = V1.putMapping
516+instance ESVersion ESV5 where
517+ type BHEnv ESV5 = V5.BHEnv
518+ type IndexSettings ESV5 = V5.IndexSettings
519+ defaultIndexSettings _ = V5.defaultIndexSettings
520+ type IndexName ESV5 = V5.IndexName
521+ toIndexName _ = V5.IndexName
522+ fromIndexName _ (V5.IndexName x) = x
523+ type MappingName ESV5 = V5.MappingName
524+ fromMappingName _ (V5.MappingName x) = x
525+ type DocId ESV5 = V5.DocId
526+ toDocId _ = V5.DocId
527+ type BH ESV5 = V5.BH
529+ type TemplateName ESV5 = V5.TemplateName
530+ toTemplateName _ = V5.TemplateName
531+ type TemplatePattern ESV5 = V5.TemplatePattern
532+ toTemplatePattern _ = V5.TemplatePattern
533+ type IndexTemplate ESV5 = V5.IndexTemplate
534+ toIndexTemplate _ = V5.IndexTemplate
535+ type IndexDocumentSettings ESV5 = V5.IndexDocumentSettings
536+ defaultIndexDocumentSettings _ = V5.defaultIndexDocumentSettings
537+ indexExists _ = V5.indexExists
538+ indexDocument _ = V5.indexDocument
539+ createIndex _ = V5.createIndex
540+ putTemplate _ = V5.putTemplate
541+ putMapping _ = V5.putMapping