on master help sign in
Allow multiple ES versions in test

When I get a green test suite I'll see about adding ES5 to the matrix

commit565f93d1dda71ef0ffe1db769b9984ce68afae7d authorMichael Xavier <michael@michaelxavier.net> dateMon 10 Jul 2017, 17:12 (9 years ago) parentfa6bdfa
4 files changed+713−557
katip-elasticsearch/katip-elasticsearch.cabal+2 katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+1−492 katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Internal.hs+503 katip-elasticsearch/test/Main.hs+207−65
katip-elasticsearch/katip-elasticsearch.cabal+2
@@ -25,6 +25,7 @@ library
2525 exposed-modules:
2626 Katip.Scribes.ElasticSearch
2727 Katip.Scribes.ElasticSearch.Annotations
28+ Katip.Scribes.ElasticSearch.Internal
2829 build-depends:
2930 base >=4.6 && <5
3031 , katip >= 0.2.0.0 && < 0.5
@@ -97,6 +98,7 @@ test-suite test
9798 , scientific
9899 , time
99100 , stm
101+ , bytestring
100102
101103 if flag(lib-Werror)
102104 ghc-options: -Wall -Werror
katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+1−492
@@ -1,10 +1,3 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE RankNTypes #-}
5−{-# LANGUAGE RecordWildCards #-}
6−{-# LANGUAGE ScopedTypeVariables #-}
7−{-# LANGUAGE TypeFamilies #-}
81-- | Includes a scribe that can be used to log structured, JSON log
92-- messages to ElasticSearch. These logs can be explored easily using
103-- <https://www.elastic.co/products/kibana kibana> or your tool of
@@ -44,8 +37,6 @@ module Katip.Scribes.ElasticSearch
4437 , defaultEsScribeCfg
4538 , defaultEsScribeCfgV1
4639 , defaultEsScribeCfgV5
47− , ESV1
48− , ESV5
4940 -- * Utilities
5041 , mkDocId
5142 , module Katip.Scribes.ElasticSearch.Annotations
@@ -54,488 +45,6 @@ module Katip.Scribes.ElasticSearch
5445
5546
5647-------------------------------------------------------------------------------
57−import Control.Applicative as A
58−import Control.Concurrent
59−import Control.Concurrent.Async
60−import Control.Concurrent.STM.TBMQueue
61−import Control.Exception.Base
62−import Control.Exception.Enclosed
63−import Control.Monad
64−import Control.Monad.Catch
65−import Control.Monad.IO.Class
66−import Control.Monad.STM
67−import Control.Retry (RetryPolicy,
68− exponentialBackoff,
69− limitRetries,
70− recovering)
71−import Data.Aeson
72−import Data.ByteString.Lazy (ByteString)
73−import Data.Monoid ((<>))
74−import Data.Text (Text)
75−import qualified Data.Text as T
76−import qualified Data.Text.Encoding as T
77−import Data.Time
78−import Data.Time.Calendar.WeekDate
79−import Data.Typeable as Typeable
80−import Data.UUID
81−import qualified Data.UUID.V4 as UUID4
82−import qualified Database.V1.Bloodhound as V1
83−import qualified Database.V5.Bloodhound as V5
84−import Network.HTTP.Client
85−import Network.HTTP.Types.Status
86−import Text.Printf (printf)
87−-------------------------------------------------------------------------------
88−import Katip.Core
8948import Katip.Scribes.ElasticSearch.Annotations
49+import Katip.Scribes.ElasticSearch.Internal
9050-------------------------------------------------------------------------------
91−
92−
93−data EsScribeCfg v = EsScribeCfg {
94− essRetryPolicy :: RetryPolicy
95− -- ^ Retry policy when there are errors sending logs to the server
96− , essQueueSize :: EsQueueSize
97− -- ^ Maximum size of the bounded log queue
98− , essPoolSize :: EsPoolSize
99− -- ^ Worker pool size limit for sending data to the
100− , essAnnotateTypes :: Bool
101− -- ^ Different payload items coexist in the "data" attribute in
102− -- ES. It is possible for different payloads to have different
103− -- types for the same key, e.g. an "id" key that is sometimes a
104− -- number and sometimes a string. If you're having ES do dynamic
105− -- mapping, the first log item will set the type and any that
106− -- don't conform will be *discarded*. If you set this to True,
107− -- keys will recursively be appended with their ES core
108− -- type. e.g. "id" would become "id::l" and "id::s"
109− -- automatically, so they won't conflict. When this library
110− -- exposes a querying API, we will try to make deserialization and
111− -- querying transparently remove the type annotations if this is
112− -- enabled.
113− , essIndexSettings :: IndexSettings v
114− , essIndexSharding :: IndexShardingPolicy
115− } deriving (Typeable)
116−
117−
118−-- | Reasonable defaults for a config:
119−--
120−-- * defaultManagerSettings
121−--
122−-- * exponential backoff with 25ms base delay up to 5 retries
123−--
124−-- * Queue size of 1000
125−--
126−-- * Pool size of 2
127−--
128−-- * Annotate types set to False
129−--
130−-- * DailyIndexSharding
131−defaultEsScribeCfg :: forall v. ESVersion v => EsScribeCfg v
132−defaultEsScribeCfg = EsScribeCfg {
133− essRetryPolicy = exponentialBackoff 25 <> limitRetries 5
134− , essQueueSize = EsQueueSize 1000
135− , essPoolSize = EsPoolSize 2
136− , essAnnotateTypes = False
137− , essIndexSettings = defaultIndexSettings prx
138− , essIndexSharding = DailyIndexSharding
139− }
140− where
141− prx :: Typeable.Proxy v
142− prx = Typeable.Proxy
143−
144−
145−-------------------------------------------------------------------------------
146−-- | EsScribeCfg that will use ElasticSearch V1
147−defaultEsScribeCfgV1 :: EsScribeCfg ESV1
148−defaultEsScribeCfgV1 = defaultEsScribeCfg
149−
150−
151−-------------------------------------------------------------------------------
152−-- | EsScribeCfg that will use ElasticSearch V5
153−defaultEsScribeCfgV5 :: EsScribeCfg ESV5
154−defaultEsScribeCfgV5 = defaultEsScribeCfg
155−
156−
157−-------------------------------------------------------------------------------
158−-- | How should katip store your log data?
159−--
160−-- * NoIndexSharding will store all logs in one index name. This is
161−-- the simplest option but is not advised in production. In practice,
162−-- the index will grow very large and will get slower to
163−-- search. Deleting records based on some sort of retention period is
164−-- also extremely slow.
165−--
166−-- * MonthlyIndexSharding, DailyIndexSharding, HourlyIndexSharding,
167−-- EveryMinuteIndexSharding will generate indexes based on the time of
168−-- the log. Index name is treated as a prefix. So if your index name
169−-- is @foo@ and DailySharding is used, logs will be stored in
170−-- @foo-2016-2-25@, @foo-2016-2-26@ and so on. Index templating will
171−-- be used to set up mappings automatically. Deletes based on date are
172−-- very fast and queries can be restricted to date ranges for better
173−-- performance. Queries against all dates should use @foo-*@ as an
174−-- index name. Note that index aliasing's glob feature is not suitable
175−-- for these date ranges as it matches index names as they are
176−-- declared, so new dates will be excluded. DailyIndexSharding is a
177−-- reasonable choice. Changing index sharding strategies is not
178−-- advisable.
179−--
180−-- * CustomSharding: supply your own function that decomposes an item
181−-- into its index name hierarchy which will be appended to the index
182−-- name. So for instance if your function return ["arbitrary",
183−-- "prefix"], the index will be @foo-arbitrary-prefix@ and the index
184−-- template will be set to match @foo-*@. In general, you want to use
185−-- segments of increasing granularity (like year, month, day for
186−-- dates). This makes it easier to address groups of indexes
187−-- (e.g. @foo-2016-*@).
188−data IndexShardingPolicy = NoIndexSharding
189− | MonthlyIndexSharding
190− | WeeklyIndexSharding
191− -- ^ A special case of daily which shards to sunday
192− | DailyIndexSharding
193− | HourlyIndexSharding
194− | EveryMinuteIndexSharding
195− | CustomIndexSharding (forall a. Item a -> [IndexNameSegment])
196−
197−
198−instance Show IndexShardingPolicy where
199− show NoIndexSharding = "NoIndexSharding"
200− show MonthlyIndexSharding = "MonthlyIndexSharding"
201− show WeeklyIndexSharding = "WeeklyIndexSharding"
202− show DailyIndexSharding = "DailyIndexSharding"
203− show HourlyIndexSharding = "HourlyIndexSharding"
204− show EveryMinuteIndexSharding = "EveryMinuteIndexSharding"
205− show (CustomIndexSharding _) = "CustomIndexSharding λ"
206−
207−
208−-------------------------------------------------------------------------------
209−newtype IndexNameSegment = IndexNameSegment {
210− indexNameSegment :: Text
211− } deriving (Show, Eq, Ord)
212−
213−
214−-------------------------------------------------------------------------------
215−shardPolicySegs :: IndexShardingPolicy -> Item a -> [IndexNameSegment]
216−shardPolicySegs NoIndexSharding _ = []
217−shardPolicySegs MonthlyIndexSharding Item {..} = [sis y, sis m]
218− where
219− (y, m, _) = toGregorian (utctDay _itemTime)
220−shardPolicySegs WeeklyIndexSharding Item {..} = [sis y, sis m, sis d]
221− where
222− (y, m, d) = toGregorian (roundToSunday (utctDay _itemTime))
223−shardPolicySegs DailyIndexSharding Item {..} = [sis y, sis m, sis d]
224− where
225− (y, m, d) = toGregorian (utctDay _itemTime)
226−shardPolicySegs HourlyIndexSharding Item {..} = [sis y, sis m, sis d, sis h]
227− where
228− (y, m, d) = toGregorian (utctDay _itemTime)
229− (h, _) = splitTime (utctDayTime _itemTime)
230−shardPolicySegs EveryMinuteIndexSharding Item {..} = [sis y, sis m, sis d, sis h, sis mn]
231− where
232− (y, m, d) = toGregorian (utctDay _itemTime)
233− (h, mn) = splitTime (utctDayTime _itemTime)
234−shardPolicySegs (CustomIndexSharding f) i = f i
235−
236−
237−-------------------------------------------------------------------------------
238−-- | If the given day is sunday, returns the input, otherwise returns
239−-- the previous sunday
240−roundToSunday :: Day -> Day
241−roundToSunday d
242− | dow == 7 = d
243− | w > 1 = fromWeekDate y (w - 1) 7
244− | otherwise = fromWeekDate (y - 1) 53 7
245− where
246− (y, w, dow) = toWeekDate d
247−
248−
249−-------------------------------------------------------------------------------
250−chooseIxn :: ESVersion v => proxy v -> IndexName v -> IndexShardingPolicy -> Item a -> IndexName v
251−chooseIxn prx ixn p i =
252− toIndexName prx (T.intercalate "-" ((fromIndexName prx ixn):segs))
253− where
254− segs = indexNameSegment A.<$> shardPolicySegs p i
255−
256−
257−-------------------------------------------------------------------------------
258−sis :: Integral a => a -> IndexNameSegment
259−sis = IndexNameSegment . T.pack . fmt
260− where
261− fmt = printf "%02d" . toInteger
262−
263−
264−-------------------------------------------------------------------------------
265−splitTime :: DiffTime -> (Int, Int)
266−splitTime t = asMins `divMod` 60
267− where
268− asMins = floor t `div` 60
269−
270−
271−-------------------------------------------------------------------------------
272−data EsScribeSetupError = CouldNotCreateIndex !(Response ByteString)
273− | CouldNotCreateMapping !(Response ByteString) deriving (Typeable, Show)
274−
275−
276−instance Exception EsScribeSetupError
277−
278−
279−-------------------------------------------------------------------------------
280−mkEsScribe
281− :: forall v. ( ESVersion v
282− , MonadIO (BH v IO)
283− )
284− => EsScribeCfg v
285− -> BHEnv v
286− -> IndexName v
287− -- ^ Treated as a prefix if index sharding is enabled
288− -> MappingName v
289− -> Severity
290− -> Verbosity
291− -> IO Scribe
292−mkEsScribe cfg@EsScribeCfg {..} env ix mapping sev verb = do
293− q <- newTBMQueueIO $ unEsQueueSize essQueueSize
294− endSig <- newEmptyMVar
295−
296− runBH prx env $ do
297− chk <- indexExists prx ix
298− -- note that this doesn't update settings. That's not available
299− -- through the Bloodhound API yet
300− unless chk $ void $ do
301− r1 <- createIndex prx essIndexSettings ix
302− unless (statusIsSuccessful (responseStatus r1)) $
303− liftIO $ throwIO (CouldNotCreateIndex r1)
304− r2 <- if shardingEnabled
305− then putTemplate prx tpl tplName
306− else putMapping prx ix mapping base
307− unless (statusIsSuccessful (responseStatus r2)) $
308− liftIO $ throwIO (CouldNotCreateMapping r2)
309−
310− workers <- replicateM (unEsPoolSize essPoolSize) $ async $
311− startWorker cfg env mapping q
312−
313− _ <- async $ do
314− takeMVar endSig
315− atomically $ closeTBMQueue q
316− mapM_ waitCatch workers
317− putMVar endSig ()
318−
319− let finalizer = putMVar endSig () >> takeMVar endSig
320− return (Scribe (logger q) finalizer)
321− where
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
327− tplName = toTemplateName prx ixn
328− shardingEnabled = case essIndexSharding of
329− NoIndexSharding -> False
330− _ -> True
331− tpl = toIndexTemplate prx (toTemplatePattern prx (ixn <> "-*")) (Just essIndexSettings) [toJSON base]
332− base = baseMapping prx mapping
333− ixn = fromIndexName prx ix
334− itemJson' :: LogItem a => Item a -> Value
335− itemJson' i
336− | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
337− | otherwise = itemJson verb i
338−
339−
340−-------------------------------------------------------------------------------
341−baseMapping :: ESVersion v => proxy v -> MappingName v -> Value
342−baseMapping prx mn =
343− object [ fromMappingName prx mn .= object ["properties" .= object prs] ]
344− where prs = [ str "thread"
345− , str "sev"
346− , str "pid"
347− , str "ns"
348− , str "msg"
349− , "loc" .= locType
350− , str "host"
351− , str "env"
352− , "at" .= dateType
353− , str "app"
354− ]
355− str k = k .= object ["type" .= String "string"]
356− locType = object ["properties" .= object locPairs]
357− locPairs = [ str "loc_pkg"
358− , str "loc_mod"
359− , str "loc_ln"
360− , str "loc_fn"
361− , str "loc_col"
362− ]
363− dateType = object [ "format" .= esDateFormat
364− , "type" .= String "date"
365− ]
366−
367−
368−-------------------------------------------------------------------------------
369−-- | Handle both old-style aeson and picosecond-level precision
370−esDateFormat :: Text
371−esDateFormat = "yyyy-MM-dd'T'HH:mm:ssZ||yyyy-MM-dd'T'HH:mm:ss.SSSZ||yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSSSSZ"
372−
373−
374−-------------------------------------------------------------------------------
375−mkDocId :: ESVersion v => proxy v -> IO (DocId v)
376−mkDocId prx = (toDocId prx . T.decodeUtf8 . toASCIIBytes) `fmap` UUID4.nextRandom
377−
378−
379−-------------------------------------------------------------------------------
380−newtype EsQueueSize = EsQueueSize {
381− unEsQueueSize :: Int
382− } deriving (Show, Eq, Ord)
383−
384−
385−instance Bounded EsQueueSize where
386− minBound = EsQueueSize 1
387− maxBound = EsQueueSize maxBound
388−
389−
390−mkEsQueueSize :: Int -> Maybe EsQueueSize
391−mkEsQueueSize = mkNonZero EsQueueSize
392−
393−
394−-------------------------------------------------------------------------------
395−newtype EsPoolSize = EsPoolSize {
396− unEsPoolSize :: Int
397− } deriving (Show, Eq, Ord)
398−
399−
400−instance Bounded EsPoolSize where
401− minBound = EsPoolSize 1
402− maxBound = EsPoolSize maxBound
403−
404−
405−mkEsPoolSize :: Int -> Maybe EsPoolSize
406−mkEsPoolSize = mkNonZero EsPoolSize
407−
408−
409−-------------------------------------------------------------------------------
410−mkNonZero :: (Int -> a) -> Int -> Maybe a
411−mkNonZero ctor n
412− | n > 0 = Just $ ctor n
413− | otherwise = Nothing
414−
415−
416−-------------------------------------------------------------------------------
417−startWorker
418− :: forall v. (ESVersion v)
419− => EsScribeCfg v
420− -> BHEnv v
421− -> MappingName v
422− -> TBMQueue (IndexName v, Value)
423− -> IO ()
424−startWorker EsScribeCfg {..} env mapping q = go
425− where
426− go = do
427− popped <- atomically $ readTBMQueue q
428− case popped of
429− Just (ixn, v) -> do
430− sendLog ixn v `catchAny` eat
431− go
432− Nothing -> return ()
433− prx :: Typeable.Proxy v
434− prx = Typeable.Proxy
435− sendLog :: IndexName v -> Value -> IO ()
436− sendLog ixn v = void $ recovering essRetryPolicy [handler] $ const $ do
437− did <- mkDocId prx
438− res <- runBH prx env $ indexDocument prx ixn mapping (defaultIndexDocumentSettings prx) v did
439− return res
440− eat _ = return ()
441− handler _ = Handler $ \e ->
442− case fromException e of
443− Just (_ :: AsyncException) -> return False
444− _ -> return True
445−
446−
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
453− -- Types
454− type BHEnv v
455− type IndexSettings v
456− defaultIndexSettings :: proxy v -> IndexSettings v
457− type IndexName v
458− toIndexName :: proxy v -> Text -> IndexName v
459− fromIndexName :: proxy v -> IndexName v -> Text
460− type MappingName v
461− fromMappingName :: proxy v -> MappingName v -> Text
462− type DocId v
463− toDocId :: proxy v -> Text -> DocId v
464− type BH v :: (* -> *) -> * -> *
465− runBH :: proxy v -> BHEnv v -> BH v m a -> m a
466− type TemplateName v
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
474−
475− -- Operations
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)
482−
483−
484−data ESV1
485−
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
498− runBH _ = V1.runBH
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
512−
513−
514−data ESV5
515−
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
528− runBH _ = V5.runBH
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
katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Internal.hsnew file+503
@@ -0,0 +1,503 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE RecordWildCards #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TypeFamilies #-}
8+-- | This is an internal module. No guarantees are made in this module
9+-- about API stability.
10+module Katip.Scribes.ElasticSearch.Internal where
11+
12+
13+-------------------------------------------------------------------------------
14+import Control.Applicative as A
15+import Control.Concurrent
16+import Control.Concurrent.Async
17+import Control.Concurrent.STM.TBMQueue
18+import Control.Exception.Base
19+import Control.Exception.Enclosed
20+import Control.Monad
21+import Control.Monad.Catch
22+import Control.Monad.IO.Class
23+import Control.Monad.STM
24+import Control.Retry (RetryPolicy,
25+ exponentialBackoff,
26+ limitRetries,
27+ recovering)
28+import Data.Aeson
29+import Data.ByteString.Lazy (ByteString)
30+import Data.Monoid ((<>))
31+import Data.Text (Text)
32+import qualified Data.Text as T
33+import qualified Data.Text.Encoding as T
34+import Data.Time
35+import Data.Time.Calendar.WeekDate
36+import Data.Typeable as Typeable
37+import Data.UUID
38+import qualified Data.UUID.V4 as UUID4
39+import qualified Database.V1.Bloodhound as V1
40+import qualified Database.V5.Bloodhound as V5
41+import Network.HTTP.Client
42+import Network.HTTP.Types.Status
43+import Text.Printf (printf)
44+-------------------------------------------------------------------------------
45+import Katip.Core
46+import Katip.Scribes.ElasticSearch.Annotations
47+-------------------------------------------------------------------------------
48+
49+
50+data EsScribeCfg v = EsScribeCfg {
51+ essRetryPolicy :: RetryPolicy
52+ -- ^ Retry policy when there are errors sending logs to the server
53+ , essQueueSize :: EsQueueSize
54+ -- ^ Maximum size of the bounded log queue
55+ , essPoolSize :: EsPoolSize
56+ -- ^ Worker pool size limit for sending data to the
57+ , essAnnotateTypes :: Bool
58+ -- ^ Different payload items coexist in the "data" attribute in
59+ -- ES. It is possible for different payloads to have different
60+ -- types for the same key, e.g. an "id" key that is sometimes a
61+ -- number and sometimes a string. If you're having ES do dynamic
62+ -- mapping, the first log item will set the type and any that
63+ -- don't conform will be *discarded*. If you set this to True,
64+ -- keys will recursively be appended with their ES core
65+ -- type. e.g. "id" would become "id::l" and "id::s"
66+ -- automatically, so they won't conflict. When this library
67+ -- exposes a querying API, we will try to make deserialization and
68+ -- querying transparently remove the type annotations if this is
69+ -- enabled.
70+ , essIndexSettings :: IndexSettings v
71+ , essIndexSharding :: IndexShardingPolicy
72+ } deriving (Typeable)
73+
74+
75+-- | Reasonable defaults for a config:
76+--
77+-- * defaultManagerSettings
78+--
79+-- * exponential backoff with 25ms base delay up to 5 retries
80+--
81+-- * Queue size of 1000
82+--
83+-- * Pool size of 2
84+--
85+-- * Annotate types set to False
86+--
87+-- * DailyIndexSharding
88+defaultEsScribeCfg' :: ESVersion v => proxy v -> EsScribeCfg v
89+defaultEsScribeCfg' prx = EsScribeCfg {
90+ essRetryPolicy = exponentialBackoff 25 <> limitRetries 5
91+ , essQueueSize = EsQueueSize 1000
92+ , essPoolSize = EsPoolSize 2
93+ , essAnnotateTypes = False
94+ , essIndexSettings = defaultIndexSettings prx
95+ , essIndexSharding = DailyIndexSharding
96+ }
97+
98+
99+-------------------------------------------------------------------------------
100+-- | Alias of 'defaultEsScribeCfgV1' to minimize API
101+-- breakage. Previous versions of katip-elasticsearch only supported
102+-- ES version 1.
103+defaultEsScribeCfg :: EsScribeCfg ESV1
104+defaultEsScribeCfg = defaultEsScribeCfgV1
105+
106+
107+-------------------------------------------------------------------------------
108+-- | EsScribeCfg that will use ElasticSearch V1
109+defaultEsScribeCfgV1 :: EsScribeCfg ESV1
110+defaultEsScribeCfgV1 = defaultEsScribeCfg' (Typeable.Proxy :: Typeable.Proxy ESV1)
111+
112+
113+-------------------------------------------------------------------------------
114+-- | EsScribeCfg that will use ElasticSearch V5
115+defaultEsScribeCfgV5 :: EsScribeCfg ESV5
116+defaultEsScribeCfgV5 = defaultEsScribeCfg' (Typeable.Proxy :: Typeable.Proxy ESV5)
117+
118+
119+-------------------------------------------------------------------------------
120+-- | How should katip store your log data?
121+--
122+-- * NoIndexSharding will store all logs in one index name. This is
123+-- the simplest option but is not advised in production. In practice,
124+-- the index will grow very large and will get slower to
125+-- search. Deleting records based on some sort of retention period is
126+-- also extremely slow.
127+--
128+-- * MonthlyIndexSharding, DailyIndexSharding, HourlyIndexSharding,
129+-- EveryMinuteIndexSharding will generate indexes based on the time of
130+-- the log. Index name is treated as a prefix. So if your index name
131+-- is @foo@ and DailySharding is used, logs will be stored in
132+-- @foo-2016-2-25@, @foo-2016-2-26@ and so on. Index templating will
133+-- be used to set up mappings automatically. Deletes based on date are
134+-- very fast and queries can be restricted to date ranges for better
135+-- performance. Queries against all dates should use @foo-*@ as an
136+-- index name. Note that index aliasing's glob feature is not suitable
137+-- for these date ranges as it matches index names as they are
138+-- declared, so new dates will be excluded. DailyIndexSharding is a
139+-- reasonable choice. Changing index sharding strategies is not
140+-- advisable.
141+--
142+-- * CustomSharding: supply your own function that decomposes an item
143+-- into its index name hierarchy which will be appended to the index
144+-- name. So for instance if your function return ["arbitrary",
145+-- "prefix"], the index will be @foo-arbitrary-prefix@ and the index
146+-- template will be set to match @foo-*@. In general, you want to use
147+-- segments of increasing granularity (like year, month, day for
148+-- dates). This makes it easier to address groups of indexes
149+-- (e.g. @foo-2016-*@).
150+data IndexShardingPolicy = NoIndexSharding
151+ | MonthlyIndexSharding
152+ | WeeklyIndexSharding
153+ -- ^ A special case of daily which shards to sunday
154+ | DailyIndexSharding
155+ | HourlyIndexSharding
156+ | EveryMinuteIndexSharding
157+ | CustomIndexSharding (forall a. Item a -> [IndexNameSegment])
158+
159+
160+instance Show IndexShardingPolicy where
161+ show NoIndexSharding = "NoIndexSharding"
162+ show MonthlyIndexSharding = "MonthlyIndexSharding"
163+ show WeeklyIndexSharding = "WeeklyIndexSharding"
164+ show DailyIndexSharding = "DailyIndexSharding"
165+ show HourlyIndexSharding = "HourlyIndexSharding"
166+ show EveryMinuteIndexSharding = "EveryMinuteIndexSharding"
167+ show (CustomIndexSharding _) = "CustomIndexSharding λ"
168+
169+
170+-------------------------------------------------------------------------------
171+newtype IndexNameSegment = IndexNameSegment {
172+ indexNameSegment :: Text
173+ } deriving (Show, Eq, Ord)
174+
175+
176+-------------------------------------------------------------------------------
177+shardPolicySegs :: IndexShardingPolicy -> Item a -> [IndexNameSegment]
178+shardPolicySegs NoIndexSharding _ = []
179+shardPolicySegs MonthlyIndexSharding Item {..} = [sis y, sis m]
180+ where
181+ (y, m, _) = toGregorian (utctDay _itemTime)
182+shardPolicySegs WeeklyIndexSharding Item {..} = [sis y, sis m, sis d]
183+ where
184+ (y, m, d) = toGregorian (roundToSunday (utctDay _itemTime))
185+shardPolicySegs DailyIndexSharding Item {..} = [sis y, sis m, sis d]
186+ where
187+ (y, m, d) = toGregorian (utctDay _itemTime)
188+shardPolicySegs HourlyIndexSharding Item {..} = [sis y, sis m, sis d, sis h]
189+ where
190+ (y, m, d) = toGregorian (utctDay _itemTime)
191+ (h, _) = splitTime (utctDayTime _itemTime)
192+shardPolicySegs EveryMinuteIndexSharding Item {..} = [sis y, sis m, sis d, sis h, sis mn]
193+ where
194+ (y, m, d) = toGregorian (utctDay _itemTime)
195+ (h, mn) = splitTime (utctDayTime _itemTime)
196+shardPolicySegs (CustomIndexSharding f) i = f i
197+
198+
199+-------------------------------------------------------------------------------
200+-- | If the given day is sunday, returns the input, otherwise returns
201+-- the previous sunday
202+roundToSunday :: Day -> Day
203+roundToSunday d
204+ | dow == 7 = d
205+ | w > 1 = fromWeekDate y (w - 1) 7
206+ | otherwise = fromWeekDate (y - 1) 53 7
207+ where
208+ (y, w, dow) = toWeekDate d
209+
210+
211+-------------------------------------------------------------------------------
212+chooseIxn :: ESVersion v => proxy v -> IndexName v -> IndexShardingPolicy -> Item a -> IndexName v
213+chooseIxn prx ixn p i =
214+ toIndexName prx (T.intercalate "-" ((fromIndexName prx ixn):segs))
215+ where
216+ segs = indexNameSegment A.<$> shardPolicySegs p i
217+
218+
219+-------------------------------------------------------------------------------
220+sis :: Integral a => a -> IndexNameSegment
221+sis = IndexNameSegment . T.pack . fmt
222+ where
223+ fmt = printf "%02d" . toInteger
224+
225+
226+-------------------------------------------------------------------------------
227+splitTime :: DiffTime -> (Int, Int)
228+splitTime t = asMins `divMod` 60
229+ where
230+ asMins = floor t `div` 60
231+
232+
233+-------------------------------------------------------------------------------
234+data EsScribeSetupError = CouldNotCreateIndex !(Response ByteString)
235+ | CouldNotCreateMapping !(Response ByteString) deriving (Typeable, Show)
236+
237+
238+instance Exception EsScribeSetupError
239+
240+
241+-------------------------------------------------------------------------------
242+mkEsScribe
243+ :: forall v. ( ESVersion v
244+ , MonadIO (BH v IO)
245+ )
246+ => EsScribeCfg v
247+ -> BHEnv v
248+ -> IndexName v
249+ -- ^ Treated as a prefix if index sharding is enabled
250+ -> MappingName v
251+ -> Severity
252+ -> Verbosity
253+ -> IO Scribe
254+mkEsScribe cfg@EsScribeCfg {..} env ix mapping sev verb = do
255+ q <- newTBMQueueIO $ unEsQueueSize essQueueSize
256+ endSig <- newEmptyMVar
257+
258+ runBH prx env $ do
259+ chk <- indexExists prx ix
260+ -- note that this doesn't update settings. That's not available
261+ -- through the Bloodhound API yet
262+ unless chk $ void $ do
263+ r1 <- createIndex prx essIndexSettings ix
264+ unless (statusIsSuccessful (responseStatus r1)) $
265+ liftIO $ throwIO (CouldNotCreateIndex r1)
266+ r2 <- if shardingEnabled
267+ then putTemplate prx tpl tplName
268+ else putMapping prx ix mapping base
269+ unless (statusIsSuccessful (responseStatus r2)) $
270+ liftIO $ throwIO (CouldNotCreateMapping r2)
271+
272+ workers <- replicateM (unEsPoolSize essPoolSize) $ async $
273+ startWorker cfg env mapping q
274+
275+ _ <- async $ do
276+ takeMVar endSig
277+ atomically $ closeTBMQueue q
278+ mapM_ waitCatch workers
279+ putMVar endSig ()
280+
281+ let finalizer = putMVar endSig () >> takeMVar endSig
282+ return (Scribe (logger q) finalizer)
283+ where
284+ logger :: forall a. LogItem a => TBMQueue (IndexName v, Value) -> Item a -> IO ()
285+ logger q i = when (_itemSeverity i >= sev) $
286+ void $ atomically $ tryWriteTBMQueue q (chooseIxn prx ix essIndexSharding i, itemJson' i)
287+ prx :: Typeable.Proxy v
288+ prx = Typeable.Proxy
289+ tplName = toTemplateName prx ixn
290+ shardingEnabled = case essIndexSharding of
291+ NoIndexSharding -> False
292+ _ -> True
293+ tpl = toIndexTemplate prx (toTemplatePattern prx (ixn <> "-*")) (Just essIndexSettings) [toJSON base]
294+ base = baseMapping prx mapping
295+ ixn = fromIndexName prx ix
296+ itemJson' :: LogItem a => Item a -> Value
297+ itemJson' i
298+ | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
299+ | otherwise = itemJson verb i
300+
301+
302+-------------------------------------------------------------------------------
303+baseMapping :: ESVersion v => proxy v -> MappingName v -> Value
304+baseMapping prx mn =
305+ object [ fromMappingName prx mn .= object ["properties" .= object prs] ]
306+ where prs = [ str "thread"
307+ , str "sev"
308+ , str "pid"
309+ , str "ns"
310+ , str "msg"
311+ , "loc" .= locType
312+ , str "host"
313+ , str "env"
314+ , "at" .= dateType
315+ , str "app"
316+ ]
317+ str k = k .= object ["type" .= String "string"]
318+ locType = object ["properties" .= object locPairs]
319+ locPairs = [ str "loc_pkg"
320+ , str "loc_mod"
321+ , str "loc_ln"
322+ , str "loc_fn"
323+ , str "loc_col"
324+ ]
325+ dateType = object [ "format" .= esDateFormat
326+ , "type" .= String "date"
327+ ]
328+
329+
330+-------------------------------------------------------------------------------
331+-- | Handle both old-style aeson and picosecond-level precision
332+esDateFormat :: Text
333+esDateFormat = "yyyy-MM-dd'T'HH:mm:ssZ||yyyy-MM-dd'T'HH:mm:ss.SSSZ||yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSSSSSZ"
334+
335+
336+-------------------------------------------------------------------------------
337+mkDocId :: ESVersion v => proxy v -> IO (DocId v)
338+mkDocId prx = (toDocId prx . T.decodeUtf8 . toASCIIBytes) `fmap` UUID4.nextRandom
339+
340+
341+-------------------------------------------------------------------------------
342+newtype EsQueueSize = EsQueueSize {
343+ unEsQueueSize :: Int
344+ } deriving (Show, Eq, Ord)
345+
346+
347+instance Bounded EsQueueSize where
348+ minBound = EsQueueSize 1
349+ maxBound = EsQueueSize maxBound
350+
351+
352+mkEsQueueSize :: Int -> Maybe EsQueueSize
353+mkEsQueueSize = mkNonZero EsQueueSize
354+
355+
356+-------------------------------------------------------------------------------
357+newtype EsPoolSize = EsPoolSize {
358+ unEsPoolSize :: Int
359+ } deriving (Show, Eq, Ord)
360+
361+
362+instance Bounded EsPoolSize where
363+ minBound = EsPoolSize 1
364+ maxBound = EsPoolSize maxBound
365+
366+
367+mkEsPoolSize :: Int -> Maybe EsPoolSize
368+mkEsPoolSize = mkNonZero EsPoolSize
369+
370+
371+-------------------------------------------------------------------------------
372+mkNonZero :: (Int -> a) -> Int -> Maybe a
373+mkNonZero ctor n
374+ | n > 0 = Just $ ctor n
375+ | otherwise = Nothing
376+
377+
378+-------------------------------------------------------------------------------
379+startWorker
380+ :: forall v. (ESVersion v)
381+ => EsScribeCfg v
382+ -> BHEnv v
383+ -> MappingName v
384+ -> TBMQueue (IndexName v, Value)
385+ -> IO ()
386+startWorker EsScribeCfg {..} env mapping q = go
387+ where
388+ go = do
389+ popped <- atomically $ readTBMQueue q
390+ case popped of
391+ Just (ixn, v) -> do
392+ sendLog ixn v `catchAny` eat
393+ go
394+ Nothing -> return ()
395+ prx :: Typeable.Proxy v
396+ prx = Typeable.Proxy
397+ sendLog :: IndexName v -> Value -> IO ()
398+ sendLog ixn v = void $ recovering essRetryPolicy [handler] $ const $ do
399+ did <- mkDocId prx
400+ res <- runBH prx env $ indexDocument prx ixn mapping (defaultIndexDocumentSettings prx) v did
401+ return res
402+ eat _ = return ()
403+ handler _ = Handler $ \e ->
404+ case fromException e of
405+ Just (_ :: AsyncException) -> return False
406+ _ -> return True
407+
408+
409+-------------------------------------------------------------------------------
410+-- We are spanning multiple versions of ES which use completely
411+-- separate types and APIs, but the subset we use is the same for both
412+-- versions. This will be kept up to date with bloodhound's supported
413+-- versions and should be minimally visible to the end user.
414+class ESVersion v where
415+ -- Types
416+ type BHEnv v
417+ type IndexSettings v
418+ defaultIndexSettings :: proxy v -> IndexSettings v
419+ type IndexName v
420+ toIndexName :: proxy v -> Text -> IndexName v
421+ fromIndexName :: proxy v -> IndexName v -> Text
422+ type MappingName v
423+ fromMappingName :: proxy v -> MappingName v -> Text
424+ type DocId v
425+ toDocId :: proxy v -> Text -> DocId v
426+ type BH v :: (* -> *) -> * -> *
427+ runBH :: proxy v -> BHEnv v -> BH v m a -> m a
428+ type TemplateName v
429+ toTemplateName :: proxy v -> Text -> TemplateName v
430+ type TemplatePattern v
431+ toTemplatePattern :: proxy v -> Text -> TemplatePattern v
432+ type IndexTemplate v
433+ toIndexTemplate :: proxy v -> TemplatePattern v -> Maybe (IndexSettings v) -> [Value] -> IndexTemplate v
434+ type IndexDocumentSettings v
435+ defaultIndexDocumentSettings :: proxy v -> IndexDocumentSettings v
436+
437+ -- Operations
438+ -- We're deciding on IO here, but it isn't necessary
439+ indexExists :: proxy v -> IndexName v -> BH v IO Bool
440+ indexDocument :: ToJSON doc => proxy v -> IndexName v -> MappingName v -> IndexDocumentSettings v -> doc -> DocId v -> BH v IO (Response ByteString)
441+ createIndex :: proxy v -> IndexSettings v -> IndexName v -> BH v IO (Response ByteString)
442+ putTemplate :: proxy v -> IndexTemplate v -> TemplateName v -> BH v IO (Response ByteString)
443+ putMapping :: (ToJSON a) => proxy v -> IndexName v -> MappingName v -> a -> BH v IO (Response ByteString)
444+
445+
446+data ESV1 = ESV1
447+
448+instance ESVersion ESV1 where
449+ type BHEnv ESV1 = V1.BHEnv
450+ type IndexSettings ESV1 = V1.IndexSettings
451+ defaultIndexSettings _ = V1.defaultIndexSettings
452+ type IndexName ESV1 = V1.IndexName
453+ toIndexName _ = V1.IndexName
454+ fromIndexName _ (V1.IndexName x) = x
455+ type MappingName ESV1 = V1.MappingName
456+ fromMappingName _ (V1.MappingName x) = x
457+ type DocId ESV1 = V1.DocId
458+ toDocId _ = V1.DocId
459+ type BH ESV1 = V1.BH
460+ runBH _ = V1.runBH
461+ type TemplateName ESV1 = V1.TemplateName
462+ toTemplateName _ = V1.TemplateName
463+ type TemplatePattern ESV1 = V1.TemplatePattern
464+ toTemplatePattern _ = V1.TemplatePattern
465+ type IndexTemplate ESV1 = V1.IndexTemplate
466+ toIndexTemplate _ = V1.IndexTemplate
467+ type IndexDocumentSettings ESV1 = V1.IndexDocumentSettings
468+ defaultIndexDocumentSettings _ = V1.defaultIndexDocumentSettings
469+ indexExists _ = V1.indexExists
470+ indexDocument _ = V1.indexDocument
471+ createIndex _ = V1.createIndex
472+ putTemplate _ = V1.putTemplate
473+ putMapping _ = V1.putMapping
474+
475+
476+data ESV5 = ESV5
477+
478+instance ESVersion ESV5 where
479+ type BHEnv ESV5 = V5.BHEnv
480+ type IndexSettings ESV5 = V5.IndexSettings
481+ defaultIndexSettings _ = V5.defaultIndexSettings
482+ type IndexName ESV5 = V5.IndexName
483+ toIndexName _ = V5.IndexName
484+ fromIndexName _ (V5.IndexName x) = x
485+ type MappingName ESV5 = V5.MappingName
486+ fromMappingName _ (V5.MappingName x) = x
487+ type DocId ESV5 = V5.DocId
488+ toDocId _ = V5.DocId
489+ type BH ESV5 = V5.BH
490+ runBH _ = V5.runBH
491+ type TemplateName ESV5 = V5.TemplateName
492+ toTemplateName _ = V5.TemplateName
493+ type TemplatePattern ESV5 = V5.TemplatePattern
494+ toTemplatePattern _ = V5.TemplatePattern
495+ type IndexTemplate ESV5 = V5.IndexTemplate
496+ toIndexTemplate _ = V5.IndexTemplate
497+ type IndexDocumentSettings ESV5 = V5.IndexDocumentSettings
498+ defaultIndexDocumentSettings _ = V5.defaultIndexDocumentSettings
499+ indexExists _ = V5.indexExists
500+ indexDocument _ = V5.indexDocument
501+ createIndex _ = V5.createIndex
502+ putTemplate _ = V5.putTemplate
503+ putMapping _ = V5.putMapping
katip-elasticsearch/test/Main.hs+207−65
@@ -1,7 +1,11 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE KindSignatures #-}
13{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RankNTypes #-}
25{-# LANGUAGE RecordWildCards #-}
36{-# LANGUAGE ScopedTypeVariables #-}
47{-# LANGUAGE TemplateHaskell #-}
8+{-# LANGUAGE TypeFamilies #-}
59{-# OPTIONS_GHC -fno-warn-orphans #-}
610module Main
711 ( main
@@ -9,42 +13,50 @@ module Main
913
1014
1115-------------------------------------------------------------------------------
12−import Control.Applicative as A
16+import Control.Applicative as A
1317import Control.Concurrent.STM
14−import Control.Lens hiding (mapping, (.=))
18+import Control.Lens hiding (mapping, (.=))
1519import Control.Monad
1620import Control.Monad.IO.Class
1721import Data.Aeson
1822import Data.Aeson.Lens
1923import Data.Aeson.Types
20−import qualified Data.HashMap.Strict as HM
24+import Data.ByteString.Lazy (ByteString)
25+import qualified Data.HashMap.Strict as HM
2126import Data.Monoid
2227import Data.Scientific
28+import Data.Text (Text)
2329import Data.Time
2430import Data.Time.Calendar.WeekDate
25−import Data.Typeable
26−import qualified Data.Vector as V
27−import qualified Database.V1.Bloodhound as V1
31+import Data.Typeable as Typeable
32+import qualified Data.Vector as V
33+import qualified Database.V1.Bloodhound as V1
34+import qualified Database.V5.Bloodhound as V5
2835import Network.HTTP.Client
2936import Network.HTTP.Types.Status
30−import Test.QuickCheck.Instances ()
37+import Test.QuickCheck.Instances ()
3138import Test.Tasty
3239import Test.Tasty.HUnit
3340import Test.Tasty.Options
3441import Test.Tasty.QuickCheck
3542-------------------------------------------------------------------------------
3643import Katip
37−import Katip.Scribes.ElasticSearch
44+import Katip.Scribes.ElasticSearch.Annotations
45+import Katip.Scribes.ElasticSearch.Internal
3846-------------------------------------------------------------------------------
3947
4048
4149main :: IO ()
42−main = defaultMain $ askOption $ \vers -> testGroup "katip-elasticsearch"
50+main = defaultMainWithIngredients ings $ askOption $ \vers -> testGroup "katip-elasticsearch"
4351 [
44− esTests vers
52+ case vers of
53+ TestV1 -> esTests (Typeable.Proxy :: Typeable.Proxy ESV1)
54+ TestV5 -> esTests (Typeable.Proxy :: Typeable.Proxy ESV5)
4555 , typeAnnotatedTests
4656 , roundToSundayTests
4757 ]
58+ where
59+ ings = (includingOptions [Option (Typeable.Proxy :: Typeable.Proxy TestWithESVersion)]):defaultIngredients
4860
4961
5062-------------------------------------------------------------------------------
@@ -62,80 +74,174 @@ instance IsOption TestWithESVersion where
6274 optionHelp = "Version of ES to test against, either 1 or 5, defaulting to 1."
6375
6476
77+class ESVersion v => TestESVersion v where
78+ type Server v
79+ toServer :: proxy v -> Text -> Server v
80+ toMappingName :: proxy v -> Text -> MappingName v
81+ type Search v
82+ type Query v
83+ type Filter v
84+ mkSearch :: proxy v -> Maybe (Query v) -> Maybe (Filter v) -> Search v
85+ mkBHEnv :: proxy v -> Server v -> Manager -> BHEnv v
86+ type ShardCount v
87+ toShardCount :: proxy v -> Int -> ShardCount v
88+ type ReplicaCount v
89+ toReplicaCount :: proxy v -> Int -> ReplicaCount v
90+ indexShards :: proxy v -> Lens' (IndexSettings v) (ShardCount v)
91+ indexReplicas :: proxy v -> Lens' (IndexSettings v) (ReplicaCount v)
92+
93+ deleteIndex :: proxy v -> IndexName v -> BH v IO (Response ByteString)
94+ deleteTemplate :: proxy v -> TemplateName v -> BH v IO (Response ByteString)
95+ refreshIndex :: proxy v -> IndexName v -> BH v IO (Response ByteString)
96+ withBH :: proxy v -> ManagerSettings -> Server v -> BH v IO a -> IO a
97+ searchByIndex :: proxy v -> IndexName v -> Search v -> BH v IO (Response ByteString)
98+
99+
100+instance TestESVersion ESV1 where
101+ type Server ESV1 = V1.Server
102+ toServer _ = V1.Server
103+ toMappingName _ = V1.MappingName
104+ type Search ESV1 = V1.Search
105+ type Query ESV1 = V1.Query
106+ type Filter ESV1 = V1.Filter
107+ type ShardCount ESV1 = V1.ShardCount
108+ toShardCount _ = V1.ShardCount
109+ type ReplicaCount ESV1 = V1.ReplicaCount
110+ toReplicaCount _ = V1.ReplicaCount
111+ mkSearch _ = V1.mkSearch
112+ mkBHEnv _ = V1.mkBHEnv
113+ indexShards _ = lens V1.indexShards (\s v -> s { V1.indexShards = v})
114+ indexReplicas _ = lens V1.indexReplicas (\r v -> r { V1.indexReplicas = v})
115+
116+ deleteIndex _ = V1.deleteIndex
117+ deleteTemplate _ = V1.deleteTemplate
118+ refreshIndex _ = V1.refreshIndex
119+ withBH _ = V1.withBH
120+ searchByIndex _ = V1.searchByIndex
121+
122+
123+instance TestESVersion ESV5 where
124+ type Server ESV5 = V5.Server
125+ toServer _ = V5.Server
126+ toMappingName _ = V5.MappingName
127+ type Search ESV5 = V5.Search
128+ type Query ESV5 = V5.Query
129+ type Filter ESV5 = V5.Filter
130+ type ShardCount ESV5 = V5.ShardCount
131+ toShardCount _ = V5.ShardCount
132+ type ReplicaCount ESV5 = V5.ReplicaCount
133+ toReplicaCount _ = V5.ReplicaCount
134+ mkSearch _ = V5.mkSearch
135+ mkBHEnv _ = V5.mkBHEnv
136+ indexShards _ = lens V5.indexShards (\s v -> s { V5.indexShards = v})
137+ indexReplicas _ = lens V5.indexReplicas (\r v -> r { V5.indexReplicas = v})
138+
139+ deleteIndex _ = V5.deleteIndex
140+ deleteTemplate _ = V5.deleteTemplate
141+ refreshIndex _ = V5.refreshIndex
142+ withBH _ = V5.withBH
143+ searchByIndex _ = V5.searchByIndex
144+
145+
65146-------------------------------------------------------------------------------
66−setupSearch :: (EsScribeCfg ESV1 -> EsScribeCfg ESV1) -> IO Scribe
67−setupSearch modScribeCfg = do
68− bh dropESSchema
147+setupSearch
148+ :: forall proxy v. (TestESVersion v, MonadIO (BH v IO))
149+ => proxy v
150+ -> (EsScribeCfg v -> EsScribeCfg v)
151+ -> IO Scribe
152+setupSearch prx modScribeCfg = do
153+ bh prx (dropESSchema prx)
69154 mgr <- newManager defaultManagerSettings
70− mkEsScribe cfg (V1.mkBHEnv svr mgr) ixn mn DebugS V3
155+ mkEsScribe cfg (mkBHEnv prx (svr prx) mgr) (ixn prx) (mn prx) DebugS V3
71156 where
72− cfg = modScribeCfg (defaultEsScribeCfgV1 { essAnnotateTypes = True
73− , essIndexSettings = ixs
74− })
157+ cfg :: EsScribeCfg v
158+ cfg = modScribeCfg $
159+ (defaultEsScribeCfg' prx)
160+ { essAnnotateTypes = True
161+ , essIndexSettings = ixs prx
162+ }
75163
76164
77165-------------------------------------------------------------------------------
78−teardownSearch :: IO ()
79−teardownSearch = do
80− bh $ do
81− when False $ dropESSchema
82− when False $ dropESSTemplate
166+teardownSearch :: (TestESVersion v, Monad (BH v IO)) => proxy v -> IO ()
167+teardownSearch prx = do
168+ bh prx $ do
169+ dropESSchema prx
170+ dropESSTemplate prx
83171
84172
85173-------------------------------------------------------------------------------
86−withSearch :: (IO Scribe -> TestTree) -> TestTree
174+withSearch
175+ :: ( TestESVersion v
176+ , MonadIO (BH v IO)
177+ )
178+ => proxy v
179+ -> (IO Scribe -> TestTree)
180+ -> TestTree
87181withSearch = withSearch' id
88182
89183
90184-------------------------------------------------------------------------------
91−withSearch' :: (EsScribeCfg ESV1 -> EsScribeCfg ESV1) -> (IO Scribe -> TestTree) -> TestTree
92−withSearch' modScribeCfg = withResource (setupSearch modScribeCfg) (const teardownSearch)
185+withSearch'
186+ :: ( TestESVersion v
187+ , MonadIO (BH v IO)
188+ )
189+ => (EsScribeCfg v -> EsScribeCfg v)
190+ -> proxy v
191+ -> (IO Scribe -> TestTree)
192+ -> TestTree
193+withSearch' modScribeCfg prx = withResource (setupSearch prx modScribeCfg) (const (teardownSearch prx))
93194
94195
95196-------------------------------------------------------------------------------
96−--TODO: switch off version
97−esTests :: TestWithESVersion -> TestTree
98−esTests _ = testGroup "elasticsearch scribe"
197+esTests
198+ :: ( TestESVersion v
199+ , MonadIO (BH v IO)
200+ , Show (IndexName v)
201+ )
202+ => proxy v
203+ -> TestTree
204+esTests prx = testGroup "elasticsearch scribe"
99205 [
100− withSearch' (\c -> c { essIndexSharding = NoIndexSharding}) $ \setup -> testCase "it flushes to elasticsearch" $ withTestLogging setup $ \done -> do
206+ withSearch' (\c -> c { essIndexSharding = NoIndexSharding}) prx $ \setup -> testCase "it flushes to elasticsearch" $ withTestLogging prx setup $ \done -> do
101207 $(logT) (ExampleCtx True) mempty InfoS "A test message"
102208 liftIO $ do
103209 void done
104− logs <- getLogs
210+ logs <- getLogs prx
105211 length logs @?= 1
106212 let l = head logs
107213 l ^? key "_source" . key "msg" . _String @?= Just "A test message"
108214 l ^? key "_source" . key "data" . key "whatever::b" . _Bool @?= Just True
109− , withSearch $ \setup -> testCase "date-based index sharding" $ do
215+ , withSearch prx $ \setup -> testCase "date-based index sharding" $ do
110216 let t1 = mkTime 2016 1 2 3 4 5
111217 fakeClock <- newTVarIO t1
112− withTestLogging' (set logEnvTimer (readTVarIO fakeClock)) setup $ \done -> do
218+ withTestLogging' (set logEnvTimer (readTVarIO fakeClock)) prx setup $ \done -> do
113219 $(logT) (ExampleCtx True) mempty InfoS "today"
114220 let t2 = mkTime 2016 1 3 3 4 5
115221 liftIO (atomically (writeTVar fakeClock t2))
116222 $(logT) (ExampleCtx True) mempty InfoS "tomorrow"
117223 liftIO $ do
118224 void done
119− todayLogs <- getLogsByIndex (V1.IndexName "katip-elasticsearch-tests-2016-01-02")
120− tomorrowLogs <- getLogsByIndex (V1.IndexName "katip-elasticsearch-tests-2016-01-03")
225+ todayLogs <- getLogsByIndex prx (toIndexName prx "katip-elasticsearch-tests-2016-01-02")
226+ tomorrowLogs <- getLogsByIndex prx (toIndexName prx "katip-elasticsearch-tests-2016-01-03")
121227 assertBool ("todayLogs has " <> show (length todayLogs) <> " items") (length todayLogs == 1)
122228 assertBool ("tomorrowLogs has " <> show (length tomorrowLogs) <> " items") (length tomorrowLogs == 1)
123229 let logToday = head todayLogs
124230 let logTomorrow = head tomorrowLogs
125231 logToday ^? key "_source" . key "msg" . _String @?= Just "today"
126232 logTomorrow ^? key "_source" . key "msg" . _String @?= Just "tomorrow"
127− , withSearch' (\c -> c { essIndexSharding = WeeklyIndexSharding}) $ \setup -> testCase "weekly index sharding rounds to previous sunday" $ do
233+ , withSearch' (\c -> c { essIndexSharding = WeeklyIndexSharding}) prx $ \setup -> testCase "weekly index sharding rounds to previous sunday" $ do
128234 let t1 = mkTime 2016 3 5 0 0 0 -- saturday, march 5th
129235 fakeClock <- newTVarIO t1
130− withTestLogging' (set logEnvTimer (readTVarIO fakeClock)) setup $ \done -> do
236+ withTestLogging' (set logEnvTimer (readTVarIO fakeClock)) prx setup $ \done -> do
131237 $(logT) (ExampleCtx True) mempty InfoS "today"
132238 let t2 = mkTime 2016 3 6 0 0 0 -- sunday march 6th
133239 liftIO (atomically (writeTVar fakeClock t2))
134240 $(logT) (ExampleCtx True) mempty InfoS "tomorrow"
135241 liftIO $ do
136242 void done
137− todayLogs <- getLogsByIndex (V1.IndexName "katip-elasticsearch-tests-2016-02-28") -- rounds back to previous sunday
138− tomorrowLogs <- getLogsByIndex (V1.IndexName "katip-elasticsearch-tests-2016-03-06") -- is on sunday, so uses current date
243+ todayLogs <- getLogsByIndex prx (toIndexName prx "katip-elasticsearch-tests-2016-02-28") -- rounds back to previous sunday
244+ tomorrowLogs <- getLogsByIndex prx (toIndexName prx "katip-elasticsearch-tests-2016-03-06") -- is on sunday, so uses current date
139245 assertBool ("todayLogs has " <> show (length todayLogs) <> " items") (length todayLogs == 1)
140246 assertBool ("tomorrowLogs has " <> show (length tomorrowLogs) <> " items") (length tomorrowLogs == 1)
141247 let logToday = head todayLogs
@@ -248,45 +354,64 @@ annotatedExampleValue = Array $ V.fromList
248354
249355
250356-------------------------------------------------------------------------------
251−getLogs :: IO [Value]
252−getLogs = getLogsByIndex ixn
357+getLogs
358+ :: ( TestESVersion v
359+ , Monad (BH v IO)
360+ , Show (IndexName v)
361+ )
362+ => proxy v
363+ -> IO [Value]
364+getLogs prx = getLogsByIndex prx (ixn prx)
253365
254366
255367-------------------------------------------------------------------------------
256−getLogsByIndex :: V1.IndexName -> IO [Value]
257−getLogsByIndex i = do
258− r <- bh $ do
259− void (V1.refreshIndex i)
260− V1.searchByIndex i (V1.mkSearch Nothing Nothing)
368+getLogsByIndex
369+ :: ( TestESVersion v
370+ , Monad (BH v IO)
371+ , Show (IndexName v)
372+ )
373+ => proxy v
374+ -> IndexName v
375+ -> IO [Value]
376+getLogsByIndex prx i = do
377+ r <- bh prx $ do
378+ void (refreshIndex prx i)
379+ searchByIndex prx i (mkSearch prx Nothing Nothing)
261380 let actualCode = statusCode (responseStatus r)
262381 assertBool ("search by " <> show i <> " " <> show actualCode <> " /= 200") (actualCode == 200)
263382 return $ responseBody r ^.. key "hits" . key "hits" . values
264383
265384
266385-------------------------------------------------------------------------------
267−bh :: V1.BH IO a -> IO a
268−bh = V1.withBH defaultManagerSettings svr
386+bh :: TestESVersion v => proxy v -> BH v IO a -> IO a
387+bh prx = withBH prx defaultManagerSettings (svr prx)
269388
270389
271390-------------------------------------------------------------------------------
272391withTestLogging
273− :: IO Scribe -> (IO V1.Reply -> KatipT IO b) -> IO b
392+ :: TestESVersion v
393+ => proxy v
394+ -> IO Scribe
395+ -> (IO (Response ByteString) -> KatipT IO b)
396+ -> IO b
274397withTestLogging = withTestLogging' id
275398
276399
277400-------------------------------------------------------------------------------
278401withTestLogging'
279− :: (LogEnv -> LogEnv)
402+ :: (TestESVersion v)
403+ => (LogEnv -> LogEnv)
404+ -> proxy v
280405 -> IO Scribe
281− -> (IO V1.Reply -> KatipT IO b)
406+ -> (IO (Response ByteString) -> KatipT IO b)
282407 -> IO b
283−withTestLogging' modEnv setup f = do
408+withTestLogging' modEnv prx setup f = do
284409 scr <- setup
285410 le <- modEnv <$> initLogEnv ns env
286411 le' <- registerScribe "es" scr defaultScribeSettings le
287412 let done' = do
288413 _ <- closeScribes le'
289− bh (V1.refreshIndex ixn)
414+ bh prx (refreshIndex prx (ixn prx))
290415 runKatipT le' (f done')
291416 where
292417 ns = Namespace ["katip-test"]
@@ -294,33 +419,50 @@ withTestLogging' modEnv setup f = do
294419
295420
296421-------------------------------------------------------------------------------
297−svr :: V1.Server
298−svr = V1.Server "http://localhost:9200"
422+svr :: TestESVersion v => proxy v -> Server v
423+svr prx = toServer prx "http://localhost:9200"
299424
300425
301426-------------------------------------------------------------------------------
302−ixn :: V1.IndexName
303−ixn = V1.IndexName "katip-elasticsearch-tests"
427+ixn :: TestESVersion v => proxy v -> IndexName v
428+ixn prx = toIndexName prx "katip-elasticsearch-tests"
304429
305430
306431-------------------------------------------------------------------------------
307−ixs :: V1.IndexSettings
308−ixs = V1.defaultIndexSettings { V1.indexShards = V1.ShardCount 1
309− , V1.indexReplicas = V1.ReplicaCount 1}
432+ixs :: TestESVersion v => proxy v -> IndexSettings v
433+ixs prx = defaultIndexSettings prx
434+ & indexShards prx .~ toShardCount prx 1
435+ & indexReplicas prx .~ toReplicaCount prx 1
436+
437+
438+-------------------------------------------------------------------------------
439+tn :: TestESVersion v => proxy v -> TemplateName v
440+tn prx = toTemplateName prx "katip-elasticsearch-tests"
441+
310442
311443-------------------------------------------------------------------------------
312−mn :: V1.MappingName
313−mn = V1.MappingName "logs"
444+mn :: TestESVersion v => proxy v -> MappingName v
445+mn prx = toMappingName prx "logs"
314446
315447
316448-------------------------------------------------------------------------------
317−dropESSchema :: V1.BH IO ()
318−dropESSchema = void $ V1.deleteIndex (V1.IndexName "katip-elasticsearch-tests*")
449+dropESSchema
450+ :: ( TestESVersion v
451+ , Monad (BH v IO)
452+ )
453+ => proxy v
454+ -> BH v IO ()
455+dropESSchema prx = void $ deleteIndex prx (toIndexName prx "katip-elasticsearch-tests*")
319456
320457
321458-------------------------------------------------------------------------------
322−dropESSTemplate :: V1.BH IO ()
323−dropESSTemplate = void $ V1.deleteTemplate (V1.TemplateName "katip-elasticsearch-tests")
459+dropESSTemplate
460+ :: ( TestESVersion v
461+ , Monad (BH v IO)
462+ )
463+ => proxy v
464+ -> BH v IO ()
465+dropESSTemplate prx = void $ deleteTemplate prx (tn prx)
324466
325467
326468-------------------------------------------------------------------------------