on b4fd16f help sign in
12 files changed+377−160
since splitsnapshots
unifiedsplit
katip-datadog/src/Katip/Scribes/Datadog/TCP.hs+1−1 katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+1−7 katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Internal.hs+93−88 katip-elasticsearch/test/Main.hs+5−3 katip/katip.cabal+29−26 katip/src/Katip.hs+2 katip/src/Katip/Core.hs+4 katip/src/Katip/Scribes/Handle.hs+64−26 katip/src/Katip/Scribes/Handle/FileOwner.hs+143 katip/test/Katip/Tests/Scribes/Handle.hs+6−8 stack-old.yaml+21 stack.yaml+8−1
katip-datadog/src/Katip/Scribes/Datadog/TCP.hs+1−1
@@ -33,8 +33,8 @@ import qualified Data.Text.Lazy.Builder as B
3333import Data.Time (NominalDiffTime)
3434import Katip
3535import Katip.Core (LocJs (..))
36−import qualified Network as Net
3736import qualified Network.Connection as C
37+import qualified Network.Socket as Net
3838import qualified System.Posix.Types as POSIX
3939-------------------------------------------------------------------------------
4040
katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+1−7
@@ -59,13 +59,7 @@ module Katip.Scribes.ElasticSearch
5959 , IndexShardingPolicy(..)
6060 , IndexNameSegment(..)
6161 -- ** EsScribeCfg and fields
62− , EsScribeCfg
63− , essRetryPolicy
64− , essQueueSize
65− , essPoolSize
66− , essAnnotateTypes
67− , essIndexSettings
68− , essIndexSharding
62+ , EsScribeCfg(..)
6963 , defaultEsScribeCfg
7064 , defaultEsScribeCfgV1
7165 , defaultEsScribeCfgV5
katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Internal.hs+93−88
@@ -53,30 +53,26 @@ import Katip.Scribes.ElasticSearch.Annotations
5353-- 'defaultEsScribeCfgV1' and 'defaultESScribeCfgV5' for a good
5454-- starting point depending on the ES version you have.
5555data EsScribeCfg v = EsScribeCfg {
56− essRetryPolicy :: RetryPolicy
56+ essRetryPolicy :: RetryPolicy
5757 -- ^ Retry policy when there are errors sending logs to the server
58− , essQueueSize :: EsQueueSize
58+ , essQueueSize :: EsQueueSize
5959 -- ^ Maximum size of the bounded log queue
60− , essPoolSize :: EsPoolSize
60+ , essPoolSize :: EsPoolSize
6161 -- ^ Worker pool size limit for sending data to the
62− , essAnnotateTypes :: Bool
63− -- ^ Different payload items coexist in the "data" attribute in
64− -- ES. It is possible for different payloads to have different
65− -- types for the same key, e.g. an "id" key that is sometimes a
66− -- number and sometimes a string. If you're having ES do dynamic
67− -- mapping, the first log item will set the type and any that
68− -- don't conform will be *discarded*. If you set this to True,
69− -- keys will recursively be appended with their ES core
70− -- type. e.g. "id" would become "id::l" and "id::s"
71− -- automatically, so they won't conflict. When this library
72− -- exposes a querying API, we will try to make deserialization and
73− -- querying transparently remove the type annotations if this is
74− -- enabled.
75− , essIndexSettings :: IndexSettings v
62+ , essItemFormatter :: Verbosity -> ItemFunc Value
63+ -- ^ The formatter of an item. Must format any item to json
64+ -- Value. You can use 'annotateValue' to get an effect of
65+ -- @essAnnotateTypes@ (removed) here.
66+ , essIndexName :: IndexName v
67+ , essIndexMapping :: MappingName v
68+ , essIndexMappingValue :: Value
69+ -- ^ The value of index mapping. The default is 'baseMapping' but
70+ -- user can override.
71+ , essIndexSettings :: IndexSettings v
7672 -- ^ This will be the IndexSettings type from the appropriate
7773 -- bloodhound module, either @Database.V1.Bloodhound@ or
7874 -- @Database.V5.Bloodhound@
79− , essIndexSharding :: IndexShardingPolicy
75+ , essIndexSharding :: IndexShardingPolicy
8076 } deriving (Typeable)
8177
8278
@@ -93,34 +89,47 @@ data EsScribeCfg v = EsScribeCfg {
9389-- * Annotate types set to False
9490--
9591-- * DailyIndexSharding
96−defaultEsScribeCfg' :: ESVersion v => proxy v -> EsScribeCfg v
97−defaultEsScribeCfg' prx = EsScribeCfg {
98− essRetryPolicy = exponentialBackoff 25000 <> limitRetries 5
99− , essQueueSize = EsQueueSize 1000
100− , essPoolSize = EsPoolSize 2
101− , essAnnotateTypes = False
102− , essIndexSettings = defaultIndexSettings prx
103− , essIndexSharding = DailyIndexSharding
104− }
105−
92+defaultEsScribeCfg'
93+ :: ESVersion v
94+ => proxy v
95+ -> IndexName v
96+ -> MappingName v
97+ -> EsScribeCfg v
98+defaultEsScribeCfg' prx index mapping = EsScribeCfg
99+ { essRetryPolicy = exponentialBackoff 25000 <> limitRetries 5
100+ , essQueueSize = EsQueueSize 1000
101+ , essPoolSize = EsPoolSize 2
102+ , essItemFormatter = defaultItemFormatter
103+ , essIndexName = index
104+ , essIndexMapping = mapping
105+ , essIndexMappingValue = baseMapping prx mapping
106+ , essIndexSettings = defaultIndexSettings prx
107+ , essIndexSharding = DailyIndexSharding
108+ }
109+
110+defaultItemFormatter :: Verbosity -> ItemFunc Value
111+defaultItemFormatter verb = ItemFunc $ \item -> itemJson verb item
112+
113+annotatedItemFormatter :: Verbosity -> ItemFunc Value
114+annotatedItemFormatter verb = ItemFunc $ \item -> itemJson verb (TypeAnnotated <$> item)
106115
107116-------------------------------------------------------------------------------
108117-- | Alias of 'defaultEsScribeCfgV1' to minimize API
109118-- breakage. Previous versions of katip-elasticsearch only supported
110119-- ES version 1.
111−defaultEsScribeCfg :: EsScribeCfg ESV1
120+defaultEsScribeCfg :: IndexName ESV1 -> MappingName ESV1 -> EsScribeCfg ESV1
112121defaultEsScribeCfg = defaultEsScribeCfgV1
113122
114123
115124-------------------------------------------------------------------------------
116125-- | EsScribeCfg that will use ElasticSearch V1
117−defaultEsScribeCfgV1 :: EsScribeCfg ESV1
126+defaultEsScribeCfgV1 :: IndexName ESV1 -> MappingName ESV1 -> EsScribeCfg ESV1
118127defaultEsScribeCfgV1 = defaultEsScribeCfg' (Typeable.Proxy :: Typeable.Proxy ESV1)
119128
120129
121130-------------------------------------------------------------------------------
122131-- | EsScribeCfg that will use ElasticSearch V5
123−defaultEsScribeCfgV5 :: EsScribeCfg ESV5
132+defaultEsScribeCfgV5 :: IndexName ESV5 -> MappingName ESV5 -> EsScribeCfg ESV5
124133defaultEsScribeCfgV5 = defaultEsScribeCfg' (Typeable.Proxy :: Typeable.Proxy ESV5)
125134
126135
@@ -265,66 +274,62 @@ mkEsScribe
265274 )
266275 => EsScribeCfg v
267276 -> BHEnv v
268− -> IndexName v
269− -- ^ Treated as a prefix if index sharding is enabled
270− -> MappingName v
271277 -> PermitFunc
272278 -> Verbosity
273279 -> IO Scribe
274−mkEsScribe cfg@EsScribeCfg {..} env ix mapping permit verb = do
275− q <- newTBMQueueIO $ unEsQueueSize essQueueSize
276− endSig <- newEmptyMVar
277−
278− runBH prx env $ do
279− if shardingEnabled
280− then do
281− -- create or update
282− res <- putTemplate prx tpl tplName
283− unless (statusIsSuccessful (responseStatus res)) $
284− liftIO $ EX.throwIO (CouldNotPutTemplate res)
285− else do
286− ixExists <- indexExists prx ix
287− if ixExists
288− then do
289− res <- updateIndexSettings prx (toUpdatabaleIndexSettings prx essIndexSettings) ix
290− unless (statusIsSuccessful (responseStatus res)) $
291− liftIO $ EX.throwIO (CouldNotUpdateIndexSettings res)
292− else do
293− r1 <- createIndex prx essIndexSettings ix
294− unless (statusIsSuccessful (responseStatus r1)) $
295− liftIO $ EX.throwIO (CouldNotCreateIndex r1)
296− r2 <- putMapping prx ix mapping base
297− unless (statusIsSuccessful (responseStatus r2)) $
298− liftIO $ EX.throwIO (CouldNotCreateMapping r2)
299−
300− workers <- replicateM (unEsPoolSize essPoolSize) $ async $
301− startWorker cfg env mapping q
302−
303− _ <- async $ do
304− takeMVar endSig
305− atomically $ closeTBMQueue q
306− mapM_ waitCatch workers
307− putMVar endSig ()
308−
309− let finalizer = putMVar endSig () >> takeMVar endSig
310− return (Scribe (logger q) finalizer permit)
311− where
312− logger :: forall a. LogItem a => TBMQueue (IndexName v, Value) -> Item a -> IO ()
313− logger q i =
314− void $ atomically $ tryWriteTBMQueue q (chooseIxn prx ix essIndexSharding i, itemJson' i)
315− prx :: Typeable.Proxy v
316− prx = Typeable.Proxy
317− tplName = toTemplateName prx ixn
318− shardingEnabled = case essIndexSharding of
319− NoIndexSharding -> False
320− _ -> True
321− tpl = toIndexTemplate prx (toTemplatePattern prx (ixn <> "-*")) (Just essIndexSettings) [toJSON base]
322− base = baseMapping prx mapping
323− ixn = fromIndexName prx ix
324− itemJson' :: LogItem a => Item a -> Value
325− itemJson' i
326− | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
327− | otherwise = itemJson verb i
280+mkEsScribe cfg@EsScribeCfg {..} env permit verb = error "fuck"
281+ -- q <- newTBMQueueIO $ unEsQueueSize essQueueSize
282+ -- endSig <- newEmptyMVar
283+
284+ -- runBH prx env $ do
285+ -- if shardingEnabled
286+ -- then do
287+ -- -- create or update
288+ -- res <- putTemplate prx tpl tplName
289+ -- unless (statusIsSuccessful (responseStatus res)) $
290+ -- liftIO $ EX.throwIO (CouldNotPutTemplate res)
291+ -- else do
292+ -- ixExists <- indexExists prx ix
293+ -- if ixExists
294+ -- then do
295+ -- res <- updateIndexSettings prx (toUpdatabaleIndexSettings prx essIndexSettings) ix
296+ -- unless (statusIsSuccessful (responseStatus res)) $
297+ -- liftIO $ EX.throwIO (CouldNotUpdateIndexSettings res)
298+ -- else do
299+ -- r1 <- createIndex prx essIndexSettings ix
300+ -- unless (statusIsSuccessful (responseStatus r1)) $
301+ -- liftIO $ EX.throwIO (CouldNotCreateIndex r1)
302+ -- r2 <- putMapping prx ix mapping mappingValue
303+ -- unless (statusIsSuccessful (responseStatus r2)) $
304+ -- liftIO $ EX.throwIO (CouldNotCreateMapping r2)
305+
306+ -- workers <- replicateM (unEsPoolSize essPoolSize) $ async $
307+ -- startWorker cfg env mapping q
308+
309+ -- _ <- async $ do
310+ -- takeMVar endSig
311+ -- atomically $ closeTBMQueue q
312+ -- mapM_ waitCatch workers
313+ -- putMVar endSig ()
314+
315+ -- let finalizer = putMVar endSig () >> takeMVar endSig
316+ -- return (Scribe (logger q) finalizer permit)
317+ -- where
318+ -- logger :: forall a. LogItem a => TBMQueue (IndexName v, Value) -> Item a -> IO ()
319+ -- logger q i =
320+ -- void $ atomically $ tryWriteTBMQueue q (chooseIxn prx ix essIndexSharding i, itemJson' i)
321+ -- prx :: Typeable.Proxy v
322+ -- prx = Typeable.Proxy
323+ -- tplName = toTemplateName prx ixn
324+ -- shardingEnabled = case essIndexSharding of
325+ -- NoIndexSharding -> False
326+ -- _ -> True
327+ -- tpl = toIndexTemplate prx (toTemplatePattern prx (ixn <> "-*")) (Just essIndexSettings) [mappingValue]
328+ -- ixn = fromIndexName prx ix
329+ -- itemJson' :: LogItem a => Item a -> Value
330+ -- itemJson' i
331+ -- | essAnnotateTypes = itemJson verb (TypeAnnotated <$> i)
332+ -- | otherwise = itemJson verb i
328333
329334
330335-------------------------------------------------------------------------------
katip-elasticsearch/test/Main.hs+5−3
@@ -161,12 +161,14 @@ setupSearch
161161setupSearch prx modScribeCfg = do
162162 bh prx (dropESSchema prx)
163163 mgr <- newManager defaultManagerSettings
164− mkEsScribe cfg (mkBHEnv prx (svr prx) mgr) (ixn prx) (mn prx) (permitItem DebugS) V3
164+ mkEsScribe cfg (mkBHEnv prx (svr prx) mgr)
165+ (permitItem DebugS) V3
165166 where
167+ mname = mn prx
166168 cfg :: EsScribeCfg v
167169 cfg = modScribeCfg $
168− (defaultEsScribeCfg' prx)
169− { essAnnotateTypes = True
170+ (defaultEsScribeCfg' prx (ixn prx) mname)
171+ { essItemFormatter = annotatedItemFormatter
170172 , essIndexSettings = ixs prx
171173 }
172174
katip/katip.cabal+29−26
@@ -36,22 +36,24 @@ flag lib-Werror
3636 manual: True
3737
3838library
39− exposed-modules:
40− Katip
41− Katip.Core
42− Katip.Format.Time
43− Katip.Monadic
44− Katip.Scribes.Handle
39+ exposed-modules: Katip
40+ , Katip.Core
41+ , Katip.Format.Time
42+ , Katip.Monadic
43+ , Katip.Scribes.Handle
44+ , Katip.Scribes.Handle.FileOwner
4545
46− default-extensions:
47− DeriveGeneric
48− FlexibleContexts
49− GeneralizedNewtypeDeriving
50− MultiParamTypeClasses
51− RankNTypes
52− RecordWildCards
53− TemplateHaskell
54− OverloadedStrings
46+ default-extensions: CPP
47+ , DeriveGeneric
48+ , FlexibleContexts
49+ , GeneralizedNewtypeDeriving
50+ , LambdaCase
51+ , MultiParamTypeClasses
52+ , OverloadedStrings
53+ , RankNTypes
54+ , RecordWildCards
55+ , ScopedTypeVariables
56+ , TemplateHaskell
5557
5658 build-depends: base >=4.9.0.0 && <5
5759 , aeson >=0.6
@@ -59,27 +61,28 @@ library
5961 , auto-update >= 0.1
6062 , bytestring >= 0.9
6163 , containers >=0.4
64+ , deepseq
6265 , either >= 4
63− , safe-exceptions >= 0.1.0.0
6466 , hostname >=1.0
67+ , microlens >= 0.2.0.0
68+ , microlens-th >= 0.1.0.0
69+ , monad-control >= 1.0
70+ , mtl >= 2.0
6571 , old-locale >= 1.0
72+ , resourcet >= 1.1
73+ , safe-exceptions >= 0.1.0.0
74+ , scientific >= 0.3.3.0
75+ , semigroups
76+ , stm >= 2.4
6677 , string-conv >= 0.1
6778 , template-haskell >= 2.8
6879 , text >= 0.11
6980 , time >= 1
7081 , transformers >= 0.3
71− , transformers-compat
72− , unordered-containers >= 0.2
73− , monad-control >= 1.0
74− , mtl >= 2.0
7582 , transformers-base >= 0.3
76− , resourcet >= 1.1
77− , scientific >= 0.3.3.0
78− , microlens >= 0.2.0.0
79− , microlens-th >= 0.1.0.0
80− , semigroups
83+ , transformers-compat
8184 , unliftio-core >= 0.1
82− , stm >= 2.4
85+ , unordered-containers >= 0.2
8386
8487 hs-source-dirs: src
8588 default-language: Haskell2010
katip/src/Katip.hs+2
@@ -181,6 +181,8 @@ module Katip
181181 , mkHandleScribe
182182 , mkHandleScribeWithFormatter
183183 , mkFileScribe
184+ , mkFileScribeWithFormatter
185+ , mkFileOwnerScribeWithFormatter
184186 , ColorStrategy (..)
185187 , ItemFormatter
186188 , bracketFormat
katip/src/Katip/Core.hs+4
@@ -574,6 +574,10 @@ itemJson verb a = toJSON $ a & itemPayload %~ payloadObject verb
574574-- combined with other, custom logic with 'permitAND' and 'permitOR'
575575type PermitFunc = forall a. Item a -> IO Bool
576576
577+-- | Generalized version of some function which takes any item and
578+-- produces something. Must replace the 'PermitFunc' and maybe
579+-- 'ItemFormatter'
580+newtype ItemFunc r = ItemFunc (forall a. LogItem a => Item a -> r)
577581
578582-- | AND together 2 permit functions
579583permitAND :: PermitFunc -> PermitFunc -> PermitFunc
katip/src/Katip/Scribes/Handle.hs+64−26
@@ -3,33 +3,42 @@ module Katip.Scribes.Handle where
33-------------------------------------------------------------------------------
44import Control.Applicative as A
55import Control.Concurrent
6−import Control.Exception (bracket_, finally)
6+import Control.Exception (finally)
77import Data.Aeson
8−import Data.Text.Lazy (toStrict)
9−import Data.Text.Lazy.Encoding (decodeUtf8)
10−import qualified Data.HashMap.Strict as HM
8+import Data.Aeson.Text
9+import Data.ByteString.Builder as B
10+import Data.ByteString.Lazy as BL (hPutStr)
11+import Data.ByteString.Lazy.Char8 as BL (singleton)
1112import Data.Monoid as M
1213import Data.Scientific as S
1314import Data.Text (Text)
14−import Data.Text.Lazy.Builder
15−import Data.Text.Lazy.IO as T
15+import Data.Text.Lazy.Builder as TL
16+import Data.Text.Lazy.Encoding (encodeUtf8)
1617import System.IO
18+import qualified Data.HashMap.Strict as HM
19+
1720-------------------------------------------------------------------------------
1821import Katip.Core
1922import Katip.Format.Time (formatAsLogTime)
23+import Katip.Scribes.Handle.FileOwner
2024-------------------------------------------------------------------------------
2125
2226
2327-------------------------------------------------------------------------------
24−brackets :: Builder -> Builder
28+brackets :: TL.Builder -> TL.Builder
2529brackets m = fromText "[" M.<> m <> fromText "]"
2630
31+textBuilderToBS :: TL.Builder -> B.Builder
32+textBuilderToBS = B.lazyByteString . encodeUtf8 . toLazyText
33+
34+appendNL :: B.Builder -> B.Builder
35+appendNL b = b <> B.lazyByteString (BL.singleton '\n')
2736
2837-------------------------------------------------------------------------------
29−getKeys :: LogItem s => Verbosity -> s -> [Builder]
38+getKeys :: LogItem s => Verbosity -> s -> [TL.Builder]
3039getKeys verb a = concat (renderPair A.<$> HM.toList (payloadObject verb a))
3140 where
32− renderPair :: (Text, Value) -> [Builder]
41+ renderPair :: (Text, Value) -> [TL.Builder]
3342 renderPair (k,v) =
3443 case v of
3544 Object o -> concat [renderPair (k <> "." <> k', v') | (k', v') <- HM.toList o]
@@ -65,6 +74,19 @@ data ColorStrategy
6574mkHandleScribe :: ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
6675mkHandleScribe = mkHandleScribeWithFormatter bracketFormat
6776
77+
78+mkFileScribeWithFormatter :: (forall a . LogItem a => ItemFormatter a)
79+ -> ColorStrategy
80+ -> FilePath
81+ -> PermitFunc
82+ -> Verbosity
83+ -> IO Scribe
84+mkFileScribeWithFormatter formatter color f permitF verb = do
85+ h <- openBinaryFile f AppendMode
86+ Scribe logger finalizer permit <- mkHandleScribeWithFormatter formatter color h permitF verb
87+ return (Scribe logger (finalizer `finally` hClose h) permit)
88+
89+
6890-- | Logs to a file handle such as stdout, stderr, or a file. Takes a custom
6991-- `ItemFormatter` that can be used to format `Item` as needed.
7092--
@@ -77,16 +99,34 @@ mkHandleScribeWithFormatter :: (forall a . LogItem a => ItemFormatter a)
7799 -> Verbosity
78100 -> IO Scribe
79101mkHandleScribeWithFormatter itemFormatter cs h permitF verb = do
80− hSetBuffering h LineBuffering
81102 colorize <- case cs of
82103 ColorIfTerminal -> hIsTerminalDevice h
83104 ColorLog b -> return b
84105 lock <- newMVar ()
85− let logger i@Item{..} = do
86− bracket_ (takeMVar lock) (putMVar lock ()) $
87− T.hPutStrLn h $ toLazyText $ itemFormatter colorize verb i
106+ let logger i@Item{..} = withMVar lock $ \() -> do
107+ BL.hPutStr h $ toLazyByteString $ itemFormatter colorize verb i
88108 return $ Scribe logger (hFlush h) permitF
89109
110+-- | Just like mkFileScribeWithFormatter but does it with FileOwner. You can
111+-- rotate your logs with commands to the FileOwner directly. This scribe will
112+-- send messages to the given FileOwner
113+mkFileOwnerScribeWithFormatter :: (forall a . LogItem a => ItemFormatter a)
114+ -> ColorStrategy
115+ -> FileOwner
116+ -> PermitFunc
117+ -> Verbosity
118+ -> IO Scribe
119+mkFileOwnerScribeWithFormatter itemFormatter cs owner permitF verb = do
120+ let
121+ colorize = case cs of
122+ ColorIfTerminal -> False
123+ -- File is never a terminal
124+ ColorLog b -> b
125+ logger item = do
126+ writeFileOwner owner $ toLazyByteString
127+ $ itemFormatter colorize verb item
128+ scribe = Scribe logger (fileOwnerControl owner CloseMsg) permitF
129+ return scribe
90130
91131-------------------------------------------------------------------------------
92132-- | A specialization of 'mkHandleScribe' that takes a 'FilePath'
@@ -95,19 +135,17 @@ mkHandleScribeWithFormatter itemFormatter cs h permitF verb = do
95135-- 'closeScribe'/'closeScribes'. Does not do log coloring. Sets handle
96136-- to 'LineBuffering' mode.
97137mkFileScribe :: FilePath -> PermitFunc -> Verbosity -> IO Scribe
98−mkFileScribe f permitF verb = do
99− h <- openFile f AppendMode
100− Scribe logger finalizer permit <- mkHandleScribe (ColorLog False) h permitF verb
101− return (Scribe logger (finalizer `finally` hClose h) permit)
138+mkFileScribe = mkFileScribeWithFormatter bracketFormat (ColorLog False)
102139
103140
104141-------------------------------------------------------------------------------
105142-- | A custom ItemFormatter for logging `Item`s. Takes a `Bool` indicating
106143-- whether to colorize the output, `Verbosity` of output, and an `Item` to
107−-- format.
144+-- format. Note that formatter is responsible for appending the newline at the
145+-- end of the string if required
108146--
109147-- See `bracketFormat` and `jsonFormat` for examples.
110−type ItemFormatter a = Bool -> Verbosity -> Item a -> Builder
148+type ItemFormatter a = Bool -> Verbosity -> Item a -> B.Builder
111149
112150
113151formatItem :: LogItem a => ItemFormatter a
@@ -121,10 +159,10 @@ formatItem = bracketFormat
121159-- > [2016-05-11 21:01:15][MyApp.confrabulation][Debug][myhost.example.com][PID 1724][ThreadId 1154][confrab_factor:42.0][main:Helpers.Logging Helpers/Logging.hs:41:9] Confrabulating widgets, with extra namespace and context
122160-- > [2016-05-11 21:01:15][MyApp][Info][myhost.example.com][PID 1724][ThreadId 1154][main:Helpers.Logging Helpers/Logging.hs:43:7] Namespace and context are back to normal
123161bracketFormat :: LogItem a => ItemFormatter a
124−bracketFormat withColor verb Item{..} =
162+bracketFormat withColor verb Item{..} = appendNL $ textBuilderToBS $
125163 brackets nowStr <>
126164 brackets (mconcat $ map fromText $ intercalateNs _itemNamespace) <>
127− brackets (fromText (renderSeverity' _itemSeverity)) <>
165+ brackets (renderSeverity' _itemSeverity) <>
128166 brackets (fromString _itemHost) <>
129167 brackets ("PID " <> fromString (show _itemProcess)) <>
130168 brackets ("ThreadId " <> fromText (getThreadIdText _itemThread)) <>
@@ -135,7 +173,7 @@ bracketFormat withColor verb Item{..} =
135173 nowStr = fromText (formatAsLogTime _itemTime)
136174 ks = map brackets $ getKeys verb _itemPayload
137175 renderSeverity' severity =
138− colorBySeverity withColor severity (renderSeverity severity)
176+ colorBySeverity withColor severity (fromText $ renderSeverity severity)
139177
140178-- | Logs items as JSON. This can be useful in circumstances where you already
141179-- have infrastructure that is expecting JSON to be logged to a standard stream
@@ -145,15 +183,15 @@ bracketFormat withColor verb Item{..} =
145183-- > {"at":"2018-10-02T21:50:30.4523848Z","env":"production","ns":["MyApp","confrabulation"],"data":{"confrab_factor":42},"app":["MyApp"],"msg":"Confrabulating widgets, with extra namespace and context","pid":"10456","loc":{"loc_col":11,"loc_pkg":"main","loc_mod":"Helpers.Logging","loc_fn":"Helpers\\Logging.hs","loc_ln":53},"host":"myhost.example.com","sev":"Debug","thread":"ThreadId 139"}
146184-- > {"at":"2018-10-02T21:50:30.4523848Z","env":"production","ns":["MyApp"],"data":{},"app":["MyApp"],"msg":"Namespace and context are back to normal","pid":"10456","loc":{"loc_col":9,"loc_pkg":"main","loc_mod":"Helpers.Logging","loc_fn":"Helpers\\Logging.hs","loc_ln":55},"host":"myhost.example.com","sev":"Info","thread":"ThreadId 139"}
147185jsonFormat :: LogItem a => ItemFormatter a
148−jsonFormat withColor verb i =
149− fromText $
186+jsonFormat withColor verb i = appendNL $
187+ textBuilderToBS $
150188 colorBySeverity withColor (_itemSeverity i) $
151− toStrict $ decodeUtf8 $ encode $ itemJson verb i
189+ encodeToTextBuilder $ itemJson verb i
152190
153191-- | Color a text message based on `Severity`. `ErrorS` and more severe errors
154192-- are colored red, `WarningS` is colored yellow, and all other messages are
155193-- rendered in the default color.
156−colorBySeverity :: Bool -> Severity -> Text -> Text
194+colorBySeverity :: Bool -> Severity -> TL.Builder -> TL.Builder
157195colorBySeverity withColor severity msg = case severity of
158196 EmergencyS -> red msg
159197 AlertS -> red msg
katip/src/Katip/Scribes/Handle/FileOwner.hsnew file+143
@@ -0,0 +1,143 @@
1+module Katip.Scribes.Handle.FileOwner where
2+
3+import Control.Applicative
4+import Control.Concurrent
5+import Control.Concurrent.Async
6+import Control.Concurrent.STM
7+import Control.Debounce
8+import Control.DeepSeq
9+import Control.Exception
10+import Control.Monad
11+import Data.ByteString.Lazy as BL
12+import Data.IORef
13+import GHC.IO.Handle
14+import GHC.IO.Handle.FD
15+import GHC.IO.IOMode
16+
17+#if MIN_VERSION_stm(2,5,0)
18+import Numeric.Natural
19+#endif
20+
21+
22+-- | File owner struct writing data to the file
23+-- sequentially. Automatic buffer flushing and log rotating.
24+data FileOwner = FileOwner
25+ { foDataQueune :: TBQueue BL.ByteString
26+ , foControlQueue :: TBQueue ControlMsg
27+ , foAsync :: Async ()
28+ -- ^ Wait for to be sure that worker thread is closed.
29+ }
30+
31+#if MIN_VERSION_stm(2,5,0)
32+type QueueLen = Natural
33+#else
34+type QueueLen = Int
35+#endif
36+
37+data FileOwnerSettings = FileOwnerSettings
38+ { fosDebounceFreq :: Maybe Int
39+ , fosDataQueueLen :: QueueLen
40+ , fosControlQueueLen :: QueueLen
41+ }
42+
43+defaultFileOwnerSettings :: FileOwnerSettings
44+defaultFileOwnerSettings = FileOwnerSettings
45+ { fosDebounceFreq = Just 200000 -- every 200ms
46+ , fosDataQueueLen = 1000
47+ , fosControlQueueLen = 100
48+ }
49+
50+data ControlMsg
51+ = CloseMsg
52+ | FlushMsg
53+ | ReopenMsg
54+ deriving (Eq, Ord, Show)
55+
56+newFileOwner :: FilePath -> FileOwnerSettings -> IO FileOwner
57+newFileOwner fp s = do
58+ dqueue <- newTBQueueIO $ fosDataQueueLen s
59+ cqueue <- newTBQueueIO $ fosControlQueueLen s
60+ let
61+ newResource = do
62+ openBinaryFile fp AppendMode
63+ ack = newResource >>= newIORef
64+ release ref = do
65+ h <- readIORef ref
66+ hFlush h
67+ hClose h
68+ go ref = do
69+ let
70+ readAllData = do
71+ a <- readTBQueue dqueue
72+ -- flushTBQueue never retries
73+ as <- flushTBQueue dqueue
74+ return $ a:as
75+ readMsg
76+ = (Left <$> readTBQueue cqueue)
77+ <|> (Right <$> readAllData)
78+ debounce action = case fosDebounceFreq s of
79+ Nothing -> do
80+ return $ return ()
81+ Just freq -> mkDebounce $ defaultDebounceSettings
82+ { debounceAction = action
83+ , debounceFreq = freq }
84+ flush <- debounce $ readIORef ref >>= hFlush
85+ let
86+ recur = atomically readMsg >>= \case
87+ Right bs -> do
88+ h <- readIORef ref
89+ BL.hPutStr h $ mconcat bs
90+ flush -- auto debounced flush
91+ recur
92+ Left c -> case c of
93+ CloseMsg -> do
94+ lastMsgs <- atomically $ flushTBQueue dqueue
95+ -- flush never blocks
96+ case lastMsgs of
97+ [] -> return ()
98+ _ -> do
99+ h <- readIORef ref
100+ BL.hPutStr h $ mconcat lastMsgs
101+ return () -- release will flush and close the handler
102+ FlushMsg -> do
103+ readIORef ref >>= hFlush
104+ recur
105+ ReopenMsg -> do
106+ newH <- newResource
107+ oldH <- atomicModifyIORef' ref (\oldH -> (newH, oldH))
108+ hFlush oldH
109+ hClose oldH
110+ recur
111+ recur
112+ worker :: IO ()
113+ worker = try (bracket ack release go) >>= \case
114+ Left (_ :: SomeException) -> do
115+ threadDelay 100000
116+ -- to not restart the worker function too often
117+ worker
118+ -- We dont want the worker thread become unavailable because
119+ -- of exceptions. E.g. could not create or write the file.
120+ Right () -> return ()
121+ asyncRet <- async worker
122+ return $ FileOwner
123+ { foDataQueune = dqueue
124+ , foControlQueue = cqueue
125+ , foAsync = asyncRet
126+ }
127+
128+fileOwnerControl :: FileOwner -> ControlMsg -> IO ()
129+fileOwnerControl fo msg = do
130+ atomically $ writeTBQueue (foControlQueue fo) msg
131+ when (msg == CloseMsg) $ do
132+ -- Wait for worker thread to finish
133+ void $ waitCatch $ foAsync fo
134+
135+writeFileOwner :: FileOwner -> BL.ByteString -> IO ()
136+writeFileOwner fo bs = do
137+ a <- return $!! bs
138+ -- The deepseq here is to be sure that writing thread will not
139+ -- calculate thunks and will not get into the blackhole or
140+ -- something. Precalculating is the responsibility of the sending
141+ -- thread, because there may be several sending threads and only one
142+ -- writing.
143+ atomically $ writeTBQueue (foDataQueune fo) $!! a
katip/test/Katip/Tests/Scribes/Handle.hs+6−8
@@ -1,5 +1,5 @@
11{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE RankNTypes #-}
2+{-# LANGUAGE RankNTypes #-}
33module Katip.Tests.Scribes.Handle
44 ( tests
55 ) where
@@ -7,12 +7,11 @@ module Katip.Tests.Scribes.Handle
77-------------------------------------------------------------------------------
88import Control.Monad
99import Data.Aeson
10−import qualified Data.ByteString.Char8 as B
10+import Data.ByteString.Builder as B
1111import qualified Data.ByteString.Lazy as BL
1212import Data.Monoid as M
1313import Data.Text (Text)
1414import qualified Data.Text as T
15−import qualified Data.Text.Encoding as T
1615import qualified Data.Text.Lazy as LT
1716import qualified Data.Text.Lazy.Builder as LT
1817import Data.Time
@@ -245,14 +244,13 @@ writeJsonLog = writeFormattedLog jsonFormat
245244
246245writeFormattedLog :: (forall a . LogItem a => ItemFormatter a) -> (FilePath, Handle) -> IO (BL.ByteString)
247246writeFormattedLog format (path, h) = do
248− mapM_ (put . formatOne) genItems
249− mapM_ (put . formatOne) genTypedItems
247+ mapM_ put genItems
248+ mapM_ put genTypedItems
250249 hClose h
251250 BL.readFile path
252251 where
253− formatOne :: LogItem a => Item a -> Text
254− formatOne = LT.toStrict . LT.toLazyText . format False V3
255− put = B.hPutStrLn h . T.encodeUtf8
252+ put :: (LogItem a) => Item a -> IO ()
253+ put = BL.hPutStr h . toLazyByteString . format False V3
256254
257255setupTempFile :: IO (FilePath, Handle)
258256setupTempFile = do
stack-old.yamlnew file+21
@@ -0,0 +1,21 @@
1+nix:
2+ packages:
3+ - zlib
4+resolver: lts-12.26
5+packages:
6+ - katip
7+ - katip-elasticsearch
8+ - katip-datadog
9+ - katip-logzio
10+extra-deps:
11+ - bloodhound-0.16.0.0
12+ - unliftio-core-0.1.2.0
13+flags:
14+ katip:
15+ lib-Werror: true
16+ katip-elasticsearch:
17+ lib-Werror: true
18+ katip-datadog:
19+ lib-Werror: true
20+ katip-logzio:
21+ lib-Werror: true
stack.yaml+8−1
@@ -1,11 +1,18 @@
1−resolver: lts-12.26
1+nix:
2+ packages:
3+ - zlib
4+resolver: lts-13.21
25packages:
36 - katip
47 - katip-elasticsearch
58 - katip-datadog
69 - katip-logzio
710extra-deps:
11+ - binary-0.8.7.0
812 - bloodhound-0.16.0.0
13+ - containers-0.5.11.0
14+ - hedgehog-1.0
15+ - tasty-hedgehog-1.0.0.1
916 - unliftio-core-0.1.2.0
1017flags:
1118 katip: