on master help sign in
Merge branch 'master' of github.com:Soostone/katip
commit0d001018efd329253ae218df65aa99897f260c99 authorOzgun Ataman <ozgun.ataman@soostone.com> dateWed 9 Nov 2016, 11:18 (10 years ago) parente0f2924b7a73e9
27 files changed+1379−195
.travis.yml+6−5 README.md+3−3 katip-elasticsearch/changelog.md+47−1 katip-elasticsearch/katip-elasticsearch.cabal+13−13 katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+31−10 katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Annotations.hs+2−1 katip-elasticsearch/test/Main.hs+41−27 katip/README.md+7−3 katip/bench/Main.hs+7−4 katip/changelog.md+42−1 katip/examples/add_context_loop.hs+69 katip/examples/example.hs+38−4 katip/examples/example_lens.hs+38−5 katip/katip.cabal+29−16 katip/src/Katip.hs+109−13 katip/src/Katip/Core.hs+154−52 katip/src/Katip/Format/Time.hs+213 katip/src/Katip/Monadic.hs+98−10 katip/src/Katip/Scribes/Handle.hs+26−18 katip/test/Katip/Tests.hs+58−1 katip/test/Katip/Tests/Format/Time.hs+81 katip/test/Katip/Tests/Scribes/Handle-text.golden+40 katip/test/Katip/Tests/Scribes/Handle.hs+166−6 katip/test/Main.hs+2 stack-lts-2.yaml+32 stack-nightly.yaml+23 stack.yaml+4−2
.travis.yml+6−5
@@ -14,9 +14,10 @@ addons:
1414# change flags or use --stack-yaml to point to a different file.
1515env:
1616- ARGS=""
17−- ARGS="--resolver lts-3"
18−- ARGS="--resolver lts"
19−- ARGS="--resolver nightly"
17+- ARGS="--stack-yaml=stack-lts-2.yaml"
18+- ARGS="--resolver=lts-3"
19+- ARGS="--resolver=lts"
20+- ARGS="--stack-yaml=stack-nightly.yaml"
2021
2122before_install:
2223# Download and unpack the stack executable
@@ -35,9 +36,9 @@ install:
3536# around some quirks in Travis's terminal implementation.
3637script:
3738 - stack $ARGS setup
38− - stack $ARGS --no-terminal test --haddock --no-haddock-deps
39+ - stack $ARGS test $BUILDFLAGS --no-terminal --haddock --no-haddock-deps
3940 - stack $ARGS bench
40− - stack $ARGS build
41+ - stack $ARGS build $BUILDFLAGS
4142 - for f in katip/examples/*.hs; do stack $ARGS ghc -- -Wall -Werror $f -o /dev/null; done
4243 - stack $ARGS sdist
4344
README.md+3−3
@@ -2,7 +2,7 @@
22
33Katip is a structured logging framework for Haskell.
44
5−![Katip](./katip/katip.jpg)
5+![Katip](https://github.com/Soostone/katip/blob/master/katip/katip.jpg)
66
77Kâtip (pronounced kah-tip) is the Turkish word for scribe.
88
@@ -11,11 +11,11 @@ Kâtip (pronounced kah-tip) is the Turkish word for scribe.
1111This is the core katip logging library. It includes a file handle
1212logger.
1313
14−See [katip's README](./katip/README.md) for more details.
14+See [katip's README](https://github.com/Soostone/katip/blob/master/katip/README.md) for more details.
1515
1616
1717## katip-elasticsearch
1818
1919ElasticSearch backend for katip using the `bloodhound` package.
2020
21−See [katip-elasticsearch's README](./katip-elasticsearch/README.md) for more details.
21+See [katip-elasticsearch's README](https://github.com/Soostone/katip/blob/master/katip-elasticsearch/README.md) for more details.
katip-elasticsearch/changelog.md+47−1
@@ -1,4 +1,50 @@
1+0.3.0.0
2+==============
3+* Added zero padding to date-based indices. This shouldn't negatively
4+ impact most users but to be safe, this was put behind a breaking
5+ version number. Previously, you may see indices
6+ `log-index-prefix-2016-1-2`. That index will now be
7+ `log-index-prefix-2016-01-02`, so at worst when you deploy, the day
8+ of the change will have 2 indices: one zero-padded and one not. If
9+ you are using custom index sharding, you will not be affected by
10+ this. The reasoning here is that most existing elasticsearch tools
11+ such as `curator` expects date indices to be zero padded. By
12+ switching to the standard, users can easily get log rotation and
13+ other features from `curator` rather than having to roll their own
14+ or add them to Katip.
15+
16+0.2.1.0
17+==============
18+
19+* Drop direct dependency on random, upgrade uuid to >= 1.3.12 for
20+ safer id generation. Previously, UUID was using randomIO, which uses
21+ the system clock as a seed. So if multiple nodes happened to start
22+ at the same time, they would produce conflicting UUID sequences.
23+
24+0.2.0.0
25+==============
26+
27+* Default index sharding policy to daily. Previously it was no
28+ sharding. The reasoning here is that no sharding creates very large
29+ indices which become very difficult to manage in
30+ production. Rotating data out on a time basis is very slow compared
31+ to deleting date-based indices.
32+
33+ Upgrade note: if you were using the defaults before and switch to
34+ daily, rather than having the index name of `my-index`, you'll start
35+ seeing `my-index-2016-3-14`. The good news is that whatever you're
36+ using to use to search against your logs (such as kibana) will
37+ support index patterns, so just use the pattern of `my-index*` to
38+ get everything. Eventually if you have a retention period, you can
39+ manually delete the `my-index` index without disruption.
40+
41+
42+0.1.1.0
43+==============
44+
45+* Set upper bounds for a few dependencies.
46+
1470.1.0.0
248==============
349
4−Initial release
50+* Initial release
katip-elasticsearch/katip-elasticsearch.cabal+13−13
@@ -1,11 +1,11 @@
11name: katip-elasticsearch
22synopsis: ElasticSearch scribe for the Katip logging framework.
33description: See README.md for more details.
4−version: 0.1.0.0
4+version: 0.3.0.0
55license: BSD3
66license-file: LICENSE
7−author: Ozgun Ataman
8−maintainer: ozgun.ataman@soostone.com
7+author: Ozgun Ataman, Michael Xavier
8+maintainer: michael.xavier@soostone.com
99copyright: Soostone Inc, 2015-2016
1010category: Data
1111build-type: Simple
@@ -15,6 +15,7 @@ extra-source-files:
1515 changelog.md
1616 bench/Main.hs
1717 test/Main.hs
18+tested-with: GHC == 7.8.4, GHC== 7.10.3
1819
1920flag lib-Werror
2021 default: False
@@ -26,24 +27,23 @@ library
2627 Katip.Scribes.ElasticSearch.Annotations
2728 build-depends:
2829 base >=4.6 && <5
29− , katip >= 0.1.0.0 && < 0.2
30+ , katip >= 0.2.0.0 && < 0.4
3031 , bloodhound >= 0.11.0.0 && < 0.12
31− , random >= 1.0 && < 1.2
32− , uuid >= 1.3 && < 1.4
32+ , uuid >= 1.3.12 && < 1.4
3333 , aeson >=0.6 && <0.12
3434 , stm >= 2.4.3 && < 2.5
3535 , stm-chans >= 3.0.0.2 && < 3.1
3636 , async >= 2.0.1.0 && < 2.2
3737 , enclosed-exceptions >= 1.0.0 && < 1.1
38− , http-client
39− , text
38+ , http-client >= 0.3 && < 0.5
39+ , text >= 0.11 && < 1.3
4040 , retry >= 0.7 && < 0.8
4141 , exceptions
42− , scientific
43− , unordered-containers
44− , transformers
45− , http-types
46− , time
42+ , scientific >= 0.3.0.0 && < 0.4
43+ , unordered-containers >= 0.1.0.0 && < 0.3
44+ , transformers >= 0.2 && < 0.6
45+ , http-types >= 0.8 && < 0.10
46+ , time >= 1 && < 1.7
4747 hs-source-dirs: src
4848 default-language: Haskell2010
4949 hs-source-dirs: src
katip-elasticsearch/src/Katip/Scribes/ElasticSearch.hs+31−10
@@ -3,6 +3,24 @@
33{-# LANGUAGE RankNTypes #-}
44{-# LANGUAGE RecordWildCards #-}
55{-# LANGUAGE ScopedTypeVariables #-}
6+-- | Includes a scribe that can be used to log structured, JSON log
7+-- messages to ElasticSearch. These logs can be explored easily using
8+-- <https://www.elastic.co/products/kibana kibana> or your tool of
9+-- choice.
10+--
11+-- == __Important Note on Index Settings__
12+--
13+-- 'defaultEsScribeCfg' inherits a set of default index settings from
14+-- the @bloodhound@ package. These settings at this time of writing
15+-- set the indices up to have 3 shards and 2 replicas. This is an
16+-- arguably reasonable default setting for production but may cause
17+-- problems for development. In development, your cluster may be
18+-- configured to seek a write quorum greater than 1. If you're running
19+-- ElasticSearch on a single node, this could cause your writes to
20+-- wait for a bit and then fail due to a lack of quorum. __For development, we recommend setting your replica count to 0 or modifying your write quorum settings__. For production, we recommend reading the
21+-- <https://www.elastic.co/guide/en/elasticsearch/guide/current/scale.html ElasticSearch Scaling Guide> and choosing the appropriate settings,
22+-- keeping in mind that you can chage replica counts on a live index
23+-- but that changing shard counts requires recreating the index.
624module Katip.Scribes.ElasticSearch
725 (-- * Building a scribe
826 mkEsScribe
@@ -53,10 +71,11 @@ import Data.Time
5371import Data.Time.Calendar.WeekDate
5472import Data.Typeable
5573import Data.UUID
74+import qualified Data.UUID.V4 as UUID4
5675import Database.Bloodhound
5776import Network.HTTP.Client
5877import Network.HTTP.Types.Status
59−import System.Random
78+import Text.Printf (printf)
6079-------------------------------------------------------------------------------
6180import Katip.Core
6281import Katip.Scribes.ElasticSearch.Annotations
@@ -64,13 +83,13 @@ import Katip.Scribes.ElasticSearch.Annotations
6483
6584
6685data EsScribeCfg = EsScribeCfg {
67− essRetryPolicy :: RetryPolicy
86+ essRetryPolicy :: RetryPolicy
6887 -- ^ Retry policy when there are errors sending logs to the server
69− , essQueueSize :: EsQueueSize
88+ , essQueueSize :: EsQueueSize
7089 -- ^ Maximum size of the bounded log queue
71− , essPoolSize :: EsPoolSize
90+ , essPoolSize :: EsPoolSize
7291 -- ^ Worker pool size limit for sending data to the
73− , essAnnotateTypes :: Bool
92+ , essAnnotateTypes :: Bool
7493 -- ^ Different payload items coexist in the "data" attribute in
7594 -- ES. It is possible for different payloads to have different
7695 -- types for the same key, e.g. an "id" key that is sometimes a
@@ -83,8 +102,8 @@ data EsScribeCfg = EsScribeCfg {
83102 -- exposes a querying API, we will try to make deserialization and
84103 -- querying transparently remove the type annotations if this is
85104 -- enabled.
86− , essIndexSettings :: IndexSettings
87− , essIndexSharding :: IndexShardingPolicy
105+ , essIndexSettings :: IndexSettings
106+ , essIndexSharding :: IndexShardingPolicy
88107 } deriving (Typeable)
89108
90109
@@ -213,8 +232,10 @@ chooseIxn (IndexName ixn) p i =
213232
214233
215234-------------------------------------------------------------------------------
216−sis :: Show a => a -> IndexNameSegment
217−sis = IndexNameSegment . T.pack . show
235+sis :: Integral a => a -> IndexNameSegment
236+sis = IndexNameSegment . T.pack . fmt
237+ where
238+ fmt = printf "%02d" . toInteger
218239
219240
220241-------------------------------------------------------------------------------
@@ -322,7 +343,7 @@ esDateFormat = "yyyy-MM-dd'T'HH:mm:ssZ||yyyy-MM-dd'T'HH:mm:ss.SSSZ||yyyy-MM-dd'T
322343
323344-------------------------------------------------------------------------------
324345mkDocId :: IO DocId
325−mkDocId = (DocId . T.decodeUtf8 . toASCIIBytes) `fmap` randomIO
346+mkDocId = (DocId . T.decodeUtf8 . toASCIIBytes) `fmap` UUID4.nextRandom
326347
327348
328349-------------------------------------------------------------------------------
katip-elasticsearch/src/Katip/Scribes/ElasticSearch/Annotations.hs+2−1
@@ -31,7 +31,8 @@ instance ToJSON a => ToJSON (TypeAnnotated a) where
3131 toJSON = annotateValue . toJSON . typeAnnotatedValue
3232
3333
34−instance ToObject a => ToObject (TypeAnnotated a)
34+instance ToObject a => ToObject (TypeAnnotated a) where
35+ toObject = annotateKeys . toObject . typeAnnotatedValue
3536
3637
3738instance FromJSON a => FromJSON (TypeAnnotated a) where
katip-elasticsearch/test/Main.hs+41−27
@@ -100,8 +100,8 @@ esTests = testGroup "elasticsearch scribe"
100100 $(logT) (ExampleCtx True) mempty InfoS "tomorrow"
101101 liftIO $ do
102102 void done
103− todayLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-1-2")
104− tomorrowLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-1-3")
103+ todayLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-01-02")
104+ tomorrowLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-01-03")
105105 assertBool ("todayLogs has " <> show (length todayLogs) <> " items") (length todayLogs == 1)
106106 assertBool ("tomorrowLogs has " <> show (length tomorrowLogs) <> " items") (length tomorrowLogs == 1)
107107 let logToday = head todayLogs
@@ -118,8 +118,8 @@ esTests = testGroup "elasticsearch scribe"
118118 $(logT) (ExampleCtx True) mempty InfoS "tomorrow"
119119 liftIO $ do
120120 void done
121− todayLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-2-28") -- rounds back to previous sunday
122− tomorrowLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-3-6") -- is on sunday, so uses current date
121+ todayLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-02-28") -- rounds back to previous sunday
122+ tomorrowLogs <- getLogsByIndex (IndexName "katip-elasticsearch-tests-2016-03-06") -- is on sunday, so uses current date
123123 assertBool ("todayLogs has " <> show (length todayLogs) <> " items") (length todayLogs == 1)
124124 assertBool ("tomorrowLogs has " <> show (length tomorrowLogs) <> " items") (length tomorrowLogs == 1)
125125 let logToday = head todayLogs
@@ -163,12 +163,18 @@ instance LogItem ExampleCtx where
163163typeAnnotatedTests :: TestTree
164164typeAnnotatedTests = testGroup "TypeAnnotated"
165165 [
166− testCase "annotates values on toJSON" $ do
166+ testCase "annotates values on toJSON" $
167167 toJSON (TypeAnnotated exampleValue) @?= annotatedExampleValue
168− , testCase "deannotates on parseJSON" $ do
168+
169+ , testCase "annotates values on toObject" $
170+ toObject (TypeAnnotated exampleObject) @?= annotatedExampleObject
171+
172+ , testCase "deannotates on parseJSON" $
169173 parseEither parseJSON (toJSON exampleValue) @?= Right exampleValue
174+
170175 , testProperty "roundtrips the same as raw" $ \(v :: Value) ->
171− let res = typeAnnotatedValue <$> (parseEither parseJSON (toJSON (TypeAnnotated v)))
176+ let res = typeAnnotatedValue
177+ <$> parseEither parseJSON (toJSON (TypeAnnotated v))
172178 in res === Right v
173179 ]
174180
@@ -187,33 +193,41 @@ roundToSundayTests = testGroup "roundToSunday"
187193 where
188194 getDOW = view _3 . toWeekDate
189195
196+
197+-------------------------------------------------------------------------------
198+exampleObject :: Object
199+exampleObject = HM.fromList
200+ [ ("a bool", Bool False)
201+ , ("a long", Number 24)
202+ , ("a double", Number 52.3)
203+ , ("a string", String "s")
204+ , ("a null", Null)
205+ , ("a map", Object (HM.singleton "baz" (Bool True)))
206+ ]
207+
208+
209+-------------------------------------------------------------------------------
210+annotatedExampleObject :: Object
211+annotatedExampleObject = HM.fromList
212+ [ ("a map",Object $ HM.fromList [("baz::b", Bool True)])
213+ , ("a bool::b", Bool False)
214+ , ("a null::n", Null)
215+ , ("a string::s", String "s")
216+ , ("a double::d", Number 52.3)
217+ , ("a long::l", Number 24.0)
218+ ]
219+
220+
190221-------------------------------------------------------------------------------
191222exampleValue :: Value
192−exampleValue = Array $ V.fromList [Null, Object ob]
193− where
194− ob = HM.fromList [ ("a bool", Bool False)
195− , ("a long", Number 24)
196− , ("a double", Number 52.3)
197− , ("a string", String "s")
198− , ("a null", Null)
199− , ("a map", Object (HM.singleton "baz" (Bool True)))
200− ]
223+exampleValue = Array $ V.fromList [Null, Object exampleObject]
201224
202225
203226-------------------------------------------------------------------------------
204227annotatedExampleValue :: Value
205228annotatedExampleValue = Array $ V.fromList
206− [
207− Null
208− , Object $ HM.fromList
209− [
210− ("a map",Object $ HM.fromList [("baz::b", Bool True)])
211− , ("a bool::b", Bool False)
212− , ("a null::n", Null)
213− , ("a string::s", String "s")
214− , ("a double::d", Number 52.3)
215− , ("a long::l", Number 24.0)
216− ]
229+ [ Null
230+ , Object annotatedExampleObject
217231 ]
218232
219233
katip/README.md+7−3
@@ -2,7 +2,7 @@
22
33Katip is a structured logging framework for Haskell.
44
5−![Katip](./katip.jpg)
5+![Katip](https://github.com/Soostone/katip/blob/master/katip/katip.jpg)
66
77Kâtip (pronounced kah-tip) is the Turkish word for scribe.
88
@@ -69,8 +69,11 @@ Kâtip (pronounced kah-tip) is the Turkish word for scribe.
6969
7070
7171## Examples
72−Be sure to look in the [examples](./examples) directory for some examples of how
73−to integrate Katip into your own stack.
72+
73+Be sure to look in the
74+[examples](https://github.com/Soostone/katip/blob/master/katip/examples)
75+directory for some examples of how to integrate Katip into your own
76+stack.
7477
7578
7679## Contributors
@@ -78,3 +81,4 @@ to integrate Katip into your own stack.
7881* [Ozgun Ataman](https://github.com/ozataman)
7982* [Michael Xavier](https://github.com/MichaelXavier)
8083* [Doug Beardsley](https://github.com/mightybyte)
84+* [Leonid Onokhov](https://github.com/sopvop)
katip/bench/Main.hs+7−4
@@ -12,7 +12,7 @@ import Control.Concurrent
1212import Control.DeepSeq
1313import Criterion.Main
1414import Data.Aeson
15−import Data.Monoid as M
15+import Data.Monoid as M
1616import Data.Time.Calendar
1717import Data.Time.Clock
1818import System.IO
@@ -31,12 +31,15 @@ main = defaultMain [
3131-------------------------------------------------------------------------------
3232handleScribeBench :: Benchmark
3333handleScribeBench = bgroup "Katip.Scribes.Handle" [
34− env setupEnv $ \ ~(Scribe push, tid) ->
34+ env setupHandleEnv $ \ ~(Scribe push, tid) ->
3535 bench "Bytestring Builder" $
3636 whnfIO $ push $ exItem tid
3737 ]
38− where
39− setupEnv = do
38+
39+
40+-------------------------------------------------------------------------------
41+setupHandleEnv :: IO (Scribe, ThreadIdText)
42+setupHandleEnv = do
4043 scribe <- setup
4144 tid <- myThreadId
4245 return (scribe, mkThreadIdText tid)
katip/changelog.md+42−1
@@ -1,4 +1,45 @@
1+0.3.1.0
2+=======
3+* Add support for aeson 1.0.x
4+* Add Katip.Format.Time module and use much more efficient time formatting code in the Handle scribe.
5+
6+0.3.0.0
7+=======
8+* Switch from `regex-tdfa-rc` to `regex-tdfa`.
9+* Add `katipNoLogging` combinator.
10+* Add `Semigroup` instances.
11+* Drop `ToJSON` superclass requirement fro `ToObject`. Instead,
12+ `ToObject` will provide a default instance for types with an
13+ instance for `ToJSON`. This gets us to the same place as before
14+ without having to add a broader instance for something that's only
15+ going to show up in logs as an Object.
16+* Add a simple MVar lock for file handle scribes to avoid interleaved
17+ log lines from concurrent inputs.
18+
19+0.2.0.0
20+=======
21+
22+* Add GHC implicit callstack support, add logLoc.
23+* Drop lens in favor of type-compatible, lighter microlens.
24+* Renamed `logEnvNs` to clearer `logEnvApp`
25+* Added `katipAddNamespace` and `katipAddContext`
26+* Fixed nested objects not rendering in Handle scribe.
27+* LogContexts Monoid instance is now right-biased rather than left
28+ biased. This better fits the use case. For instance `ctx1 <> ctx2`
29+ will prefer keys in `ctx2` if there are conflicts. This makes the
30+ most sense because functions like `katipAddContext` will `mappend`
31+ on the right side.
32+* LogContext internally uses a `Seq` instead of a list for better
33+ complexity on context add.
34+* Improved documentation.
35+
36+0.1.1.0
37+==============
38+
39+* Set upper bounds for a few dependencies.
40+* Add ExceptT instance for Katip typeclass
41+
1420.1.0.0
243==============
344
4−Initial release
45+* Initial release
katip/examples/add_context_loop.hsnew file+69
@@ -0,0 +1,69 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+-- | This executable is a test which runs addContext with
3+-- key-compatible log contexts in a tight loop. At time of writing, I
4+-- suspect this will result in a memory leak as we just naively
5+-- concatenate it into a sequence. If instead we eagerly merge the log
6+-- objects, each loop will overwrite the previous context and there
7+-- will be no memory leak. You can pass a numeric argument to this
8+-- executable to specify how many loops to do. -1 goes infinite.
9+--
10+-- If we didn't have to retain the log context and could eagerly merge
11+-- it, this wouldn't be an issue, but unfortunately we don't know
12+-- which keys to keep until log time, not context add time. Further,
13+-- we couldn't accumulate and compose key retention functions for the
14+-- same reason.
15+module Main
16+ ( main
17+ ) where
18+
19+
20+-------------------------------------------------------------------------------
21+import Control.Monad (unless, forever)
22+import qualified Data.Aeson as A
23+import qualified Data.HashMap.Strict as HM
24+import qualified Data.String.Conv as SC
25+import qualified System.Environment as SE
26+-------------------------------------------------------------------------------
27+import qualified Katip as K
28+-------------------------------------------------------------------------------
29+
30+
31+main :: IO ()
32+main = do
33+ args <- SE.getArgs
34+ le <- K.initLogEnv "add_context_loop" "test"
35+ K.runKatipContextT le () "loop" $ case args of
36+ [] -> run (Just 1000000)
37+ ns:_ -> let n = read ns
38+ in if n < 0
39+ then run Nothing
40+ else run (Just n)
41+
42+
43+-------------------------------------------------------------------------------
44+-- | Semantically, each loop adds more context on. If we were using
45+-- something like forever, unfoldM, etc, which is probably what we
46+-- actually want, it would add the same context each time,
47+-- non-redundantly and thus no leak.
48+run :: Maybe Int -> K.KatipContextT IO ()
49+run mlim = go 0
50+ where
51+ go n = do
52+ K.katipAddContext (SomeContext 1000) $ do
53+ K.logFM K.InfoS "loop"
54+ unless (done n) $ go (n + 1)
55+ done n = case mlim of
56+ Nothing -> False
57+ Just lim -> n >= lim
58+
59+
60+-------------------------------------------------------------------------------
61+data SomeContext = SomeContext Int
62+
63+
64+instance K.ToObject SomeContext where
65+ toObject (SomeContext sz) = HM.fromList [ SC.toS (show n) A..= () | n <- [0..sz]]
66+
67+
68+instance K.LogItem SomeContext where
69+ payloadKeys _ _ = K.AllKeys
katip/examples/example.hs+38−4
@@ -1,18 +1,24 @@
11{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE FlexibleInstances #-}
23{-# LANGUAGE GeneralizedNewtypeDeriving #-}
34{-# LANGUAGE MultiParamTypeClasses #-}
45{-# LANGUAGE OverloadedStrings #-}
56{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE UndecidableInstances #-}
69module Main
710 ( main
811 ) where
912
1013
1114-------------------------------------------------------------------------------
15+import qualified Control.Applicative as A
16+import Control.Monad.Base
1217import Control.Monad.Reader
18+import Control.Monad.Trans.Control
1319import Data.Aeson
14−import Data.Monoid as M
15−import System.IO (stdout)
20+import Data.Monoid as M
21+import System.IO (stdout)
1622-------------------------------------------------------------------------------
1723import Katip
1824-------------------------------------------------------------------------------
@@ -22,7 +28,7 @@ import Katip
2228-- lens_example for a slightly cleaner and more general pattern.
2329main :: IO ()
2430main = do
25− le <- initLogEnv "main" "production"
31+ le <- initLogEnv "MyApp" "production"
2632 -- We'll set up a scribe that logs to stdout and will only log item
2733 -- fields permitted for Verbosity 2 and will throw out Debug
2834 -- messages entirely. Note that katip provides facilities like
@@ -44,6 +50,8 @@ main = do
4450 $(logTM) DebugS "Confrabulating widgets, with extra namespace and context"
4551 confrabulateWidgets
4652 $(logTM) InfoS "Namespace and context are back to normal"
53+ noLogging $
54+ $(logTM) DebugS "You'll never see this log message!"
4755
4856
4957-------------------------------------------------------------------------------
@@ -77,7 +85,27 @@ data MyState = MyState {
7785-------------------------------------------------------------------------------
7886newtype MyStack m a = MyStack {
7987 unStack :: ReaderT MyState m a
80− } deriving (MonadReader MyState, Functor, Applicative, Monad, MonadIO)
88+ } deriving (MonadReader MyState, Functor, A.Applicative, Monad, MonadIO, MonadTrans)
89+
90+
91+-- MonadBase, MonadTransControl, and MonadBaseControl aren't strictly
92+-- needed for this example, but they are commonly required and
93+-- MonadTransControl/MonadBaseControl are a pain to implement, so I've
94+-- included them. Note that KatipT and KatipContextT already do this work for you.
95+instance MonadBase b m => MonadBase b (MyStack m) where
96+ liftBase = liftBaseDefault
97+
98+
99+instance MonadTransControl MyStack where
100+ type StT MyStack a = StT (ReaderT Int) a
101+ liftWith = defaultLiftWith MyStack unStack
102+ restoreT = defaultRestoreT MyStack
103+
104+
105+instance MonadBaseControl b m => MonadBaseControl b (MyStack m) where
106+ type StM (MyStack m) a = ComposeSt MyStack m a
107+ liftBaseWith = defaultLiftBaseWith
108+ restoreM = defaultRestoreM
81109
82110
83111instance (MonadIO m) => Katip (MyStack m) where
@@ -103,6 +131,12 @@ addNamespace :: (MonadReader MyState m) => Namespace -> m a -> m a
103131addNamespace ns = local (\r -> r { msKNamespace = msKNamespace r <> ns })
104132
105133
134+-------------------------------------------------------------------------------
135+-- | Disable all log output temporarily
136+noLogging :: (MonadReader MyState m) => m a -> m a
137+noLogging = local (\r -> r { msLogEnv = clearScribes (msLogEnv r)})
138+
139+
106140-------------------------------------------------------------------------------
107141runStack :: MyState -> MyStack m a -> m a
108142runStack s f = runReaderT (unStack f) s
katip/examples/example_lens.hs+38−5
@@ -1,19 +1,25 @@
11{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE FlexibleInstances #-}
23{-# LANGUAGE GeneralizedNewtypeDeriving #-}
34{-# LANGUAGE MultiParamTypeClasses #-}
45{-# LANGUAGE OverloadedStrings #-}
56{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE UndecidableInstances #-}
69module Main
710 ( main
811 ) where
912
1013
1114-------------------------------------------------------------------------------
12−import Control.Lens hiding ((.=))
15+import Control.Applicative as A
16+import Control.Lens hiding ((.=))
17+import Control.Monad.Base
1318import Control.Monad.Reader
19+import Control.Monad.Trans.Control
1420import Data.Aeson
15−import Data.Monoid as M
16−import System.IO (stdout)
21+import Data.Monoid as M
22+import System.IO (stdout)
1723-------------------------------------------------------------------------------
1824import Katip
1925-------------------------------------------------------------------------------
@@ -38,7 +44,7 @@ makeClassy ''MyState
3844-- lens_example for a slightly cleaner and more general pattern.
3945main :: IO ()
4046main = do
41− le <- initLogEnv "main" "production"
47+ le <- initLogEnv "MyApp" "production"
4248 -- We'll set up a scribe that logs to stdout and will only log item
4349 -- fields permitted for Verbosity 2 and will throw out Debug
4450 -- messages entirely. Note that katip provides facilities like
@@ -60,6 +66,8 @@ main = do
6066 $(logTM) DebugS "Confrabulating widgets, with extra namespace and context"
6167 confrabulateWidgets
6268 $(logTM) InfoS "Namespace and context are back to normal"
69+ noLogging $
70+ $(logTM) DebugS "You'll never see this log message!"
6371
6472
6573-------------------------------------------------------------------------------
@@ -85,9 +93,28 @@ confrabulateWidgets = return ()
8593-------------------------------------------------------------------------------
8694newtype MyStack m a = MyStack {
8795 unStack :: ReaderT MyState m a
88− } deriving (MonadReader MyState, Functor, Applicative, Monad, MonadIO)
96+ } deriving (MonadReader MyState, Functor, A.Applicative, Monad, MonadIO, MonadTrans)
8997
9098
99+-- MonadBase, MonadTransControl, and MonadBaseControl aren't strictly
100+-- needed for this example, but they are commonly required and
101+-- MonadTransControl/MonadBaseControl are a pain to implement, so I've
102+-- included them. Note that KatipT and KatipContextT already do this work for you.
103+instance MonadBase b m => MonadBase b (MyStack m) where
104+ liftBase = liftBaseDefault
105+
106+
107+instance MonadTransControl MyStack where
108+ type StT MyStack a = StT (ReaderT Int) a
109+ liftWith = defaultLiftWith MyStack unStack
110+ restoreT = defaultRestoreT MyStack
111+
112+
113+instance MonadBaseControl b m => MonadBaseControl b (MyStack m) where
114+ type StM (MyStack m) a = ComposeSt MyStack m a
115+ liftBaseWith = defaultLiftBaseWith
116+ restoreM = defaultRestoreM
117+
91118instance (MonadIO m) => Katip (MyStack m) where
92119 getLogEnv = view msLogEnv
93120
@@ -111,6 +138,12 @@ addNamespace :: (MonadReader r m, HasMyState r) => Namespace -> m a -> m a
111138addNamespace ns = local (\r -> r & msKNamespace <>~ ns)
112139
113140
141+-------------------------------------------------------------------------------
142+-- | Disable all log output temporarily
143+noLogging :: (MonadReader r m, HasMyState r) => m a -> m a
144+noLogging = local (\r -> r & msLogEnv %~ clearScribes)
145+
146+
114147-------------------------------------------------------------------------------
115148runStack :: MyState -> MyStack m a -> m a
116149runStack s f = runReaderT (unStack f) s
katip/katip.cabal+29−16
@@ -1,15 +1,17 @@
11name: katip
2−version: 0.1.0.0
2+version: 0.3.1.0
33synopsis: A structured logging framework.
44description:
55 Katip is a structured logging framework. See README.md for more details.
66
77license: BSD3
88license-file: LICENSE
9−author: Ozgun Ataman
10−maintainer: ozgun.ataman@soostone.com
9+author: Ozgun Ataman, Michael Xavier
10+maintainer: michael.xavier@soostone.com
1111copyright: Soostone Inc, 2015-2016
1212category: Data, Text, Logging
13+homepage: https://github.com/Soostone/katip
14+bug-reports: https://github.com/Soostone/katip/issues
1315build-type: Simple
1416cabal-version: >=1.10
1517extra-source-files:
@@ -21,6 +23,11 @@ extra-source-files:
2123 test/Main.hs
2224 test/Katip/Tests.hs
2325 test/Katip/Tests/Scribes/Handle.hs
26+tested-with: GHC == 7.8.4, GHC== 7.10.3
27+
28+source-repository head
29+ type: git
30+ location: https://github.com/Soostone/katip.git
2431
2532flag lib-Werror
2633 default: False
@@ -30,6 +37,7 @@ library
3037 exposed-modules:
3138 Katip
3239 Katip.Core
40+ Katip.Format.Time
3341 Katip.Monadic
3442 Katip.Scribes.Handle
3543
@@ -44,29 +52,29 @@ library
4452 OverloadedStrings
4553
4654 build-depends: base >=4.5 && <5
47− , aeson >=0.6 && <0.12
55+ , aeson >=0.6 && <1.1
4856 , auto-update >= 0.1 && < 0.2
4957 , bytestring >= 0.9 && < 0.11
5058 , containers >=0.4 && <0.6
5159 , either >= 4 && < 4.5
5260 , exceptions >= 0.5 && < 0.9
5361 , hostname >=1.0 && <1.1
54− , lens >= 4.4 && <4.14
55− , lens-aeson < 1.1
5662 , old-locale >= 1.0 && < 1.1
5763 , string-conv >= 0.1 && < 0.2
58− , template-haskell >= 2.8 && < 2.11
64+ , template-haskell >= 2.8 && < 2.12
5965 , text >= 0.11 && <1.3
60− , time >= 1 && < 1.6
61− , time-locale-compat >= 0.1.0.1 && < 0.2
62− , transformers >= 0.3 && < 0.5
66+ , time >= 1 && < 1.7
67+ , transformers >= 0.3 && < 0.6
6368 , transformers-compat
6469 , unix >= 2.5 && < 2.8
6570 , unordered-containers >= 0.2 && < 0.3
6671 , monad-control >= 1.0 && < 1.1
67− , mtl
68− , transformers-base
69− , resourcet
72+ , mtl >= 2.0 && < 2.3
73+ , transformers-base >= 0.3 && < 0.6
74+ , resourcet >= 1.1 && < 1.2
75+ , microlens >= 0.2.0.0 && < 0.5
76+ , microlens-th >= 0.1.0.0 && < 0.5
77+ , semigroups
7078
7179 hs-source-dirs: src
7280 default-language: Haskell2010
@@ -82,24 +90,28 @@ test-suite test
8290 build-depends: base
8391 , katip
8492 , aeson
93+ , bytestring
8594 , tasty >= 0.10.1.2
95+ , tasty-golden
8696 , tasty-hunit
8797 , tasty-quickcheck
8898 , quickcheck-instances
8999 , template-haskell
90100 , text
91101 , time
102+ , time-locale-compat >= 0.1.0.1 && < 0.2
92103 , temporary
93104 , directory
94− , regex-tdfa-rc
95−
105+ , regex-tdfa
106+ , unordered-containers
107+ , microlens
96108
97109benchmark bench
98110 type: exitcode-stdio-1.0
99111 main-is: Main.hs
100112 hs-source-dirs: bench
101113 default-language: Haskell2010
102− ghc-options: -O2 -Wall
114+ ghc-options: -O2 -Wall -threaded -rtsopts -with-rtsopts=-N
103115 if flag(lib-Werror)
104116 ghc-options: -Werror
105117 build-depends:
@@ -113,3 +125,4 @@ benchmark bench
113125 , time
114126 , transformers
115127 , deepseq
128+ , async
katip/src/Katip.hs+109−13
@@ -26,17 +26,22 @@ module Katip
2626 (
2727
2828 -- * Framework Types
29−
30− Katip (..)
31− , Namespace (..)
29+ Namespace (..)
3230 , Environment (..)
3331 , Severity (..)
3432 , renderSeverity
35− , severityText
33+ , textToSeverity
3634 , Verbosity (..)
3735 , ToObject (..)
3836 , LogItem (..)
3937 , Item(..)
38+ , ThreadIdText(..)
39+ , PayloadSelection (..)
40+ , Scribe (..)
41+ , LogEnv (..)
42+ , SimpleLogPayload, sl
43+
44+ -- ** @lens@-compatible Lenses
4045 , itemApp
4146 , itemEnv
4247 , itemSeverity
@@ -48,20 +53,12 @@ module Katip
4853 , itemTime
4954 , itemNamespace
5055 , itemLoc
51− , ThreadIdText(..)
52− , PayloadSelection (..)
53− , Scribe (..)
54− , LogEnv (..)
5556 , logEnvHost
5657 , logEnvPid
57− , logEnvNs
58+ , logEnvApp
5859 , logEnvEnv
5960 , logEnvTimer
6061 , logEnvScribes
61− , KatipContext(..)
62− , AnyLogContext
63− , LogContexts, liftPayload
64− , SimpleLogPayload, sl
6562
6663 -- * A Built-in Monad For Logging
6764 , KatipT (..)
@@ -77,15 +74,23 @@ module Katip
7774 , LogStr (..)
7875 , logStr, ls, showLS
7976
77+ -- ** 'Katip' LoggingFunctions
78+ -- $katiplogging
79+ , Katip (..)
8080 , logF
8181 , logMsg
8282 , logT
8383 , logItem
8484 , logException
85+ -- ** 'KatipContext' Logging Functions
86+ -- $katipcontextlogging
87+ , KatipContext (..)
8588 , logFM
8689 , logTM
8790 , logItemM
8891 , logExceptionM
92+ , AnyLogContext
93+ , LogContexts, liftPayload
8994
9095 -- * Included Scribes
9196 , mkHandleScribe
@@ -99,6 +104,9 @@ module Katip
99104 -- * KatipContextT - Utility transformer that provides Katip and KatipContext instances
100105 , KatipContextT
101106 , runKatipContextT
107+ , katipAddNamespace
108+ , katipAddContext
109+ , katipNoLogging
102110 ) where
103111
104112-------------------------------------------------------------------------------
@@ -106,3 +114,91 @@ import Katip.Core
106114import Katip.Monadic
107115import Katip.Scribes.Handle
108116-------------------------------------------------------------------------------
117+
118+
119+{- $katiplogging
120+
121+ These logging functions use the basic 'Katip' constraint and thus
122+will require varying degrees of explicit detail such as 'Namespace'
123+and individual log items to be passed in. These can be described as
124+the primitives of Katip logging. If you find yourself making multiple
125+log statements within a logical logging context for your app, you may
126+want to look into the 'KatipContext' family of logging functions like
127+'logFM' and 'logTM'. 'KatipContext' in most applications should be
128+considered the default. Here's an example of the pain point:
129+
130+@
131+doDatabaseThings = do
132+ connId <- getConnectionId
133+ logF (ConnectionIDContext connId) "database" InfoS "Doing database stuff"
134+ \-\- ...
135+ logF (ConnectionIDContext connId) "database" InfoS "Wow, passing in the same context is getting tedious"
136+@
137+
138+Another pain point to look out for is nesting actions that log in
139+eachother. Let's say you were writing a web app. You want to capture
140+some detail such as the user's ID in the logs, but you also want that
141+info to show up in doDatabaseThings' logs so you can associate those
142+two pieces of information:
143+
144+
145+@
146+webRequestHandler = do
147+ uid <- getUserId
148+ logF (UserIDContext uid) "web" InfoS "Starting web request"
149+ doDatabaseThings
150+@
151+
152+In the above example, doDatabaseThings would overwrite that
153+UderIDContext with its own context and namespace. Sometimes this is
154+what you want and that's why 'logF' and other functions which only
155+require 'Katip' exist. If you are interested in combining log
156+contexts and namespaces, see 'KatipContext'.
157+-}
158+
159+
160+{- $katipcontextlogging
161+
162+ These logging functions use the 'KatipContext' constraint which is a
163+superclass of 'Katip' that also has a mechanism for keeping track of
164+the current context and namespace. This means a few things:
165+
166+1. Functions that use 'KatipContext' like 'logFM' and 'logTM' do not
167+require you to pass in 'LogItem's or 'Namespaces', they pull them from
168+the monadic environment.
169+
170+2. It becomes easy to add functions which add namespaces and/or
171+contexts to the current stack of them. You can (and should) make that
172+action scoped to a monadic action so that when it finishes, the
173+previous context and namespace will be automatically restored.
174+
175+
176+'KatipContextT' provides a simple, 'ReaderT'-based implementation of
177+the 'KatipContext' typeclass, and provides 'katipAddContext' and
178+'katipAddNamespace' functions to append to the context for the
179+duration of a block:
180+
181+
182+@
183+main = do
184+ le <- initLogEnv "MyApp" "production"
185+ \-\- set up scribes here
186+ runKatipContext le () "main" $ do
187+ katipAddNamespace "nextlevel" $ do
188+ $(logTM) InfoS "Logs here will have namespace MyApp.main.nextlevel"
189+
190+ katipAddContext TrivialContext $ do
191+ $(logTM) InfoS "Logs here will have context from TrivialContext"
192+
193+ katipAddContext AnotherContext $ do
194+ $(logTM) InfoS "Logs here will have context from TrivialContext *merged with* context from AnotherContext!"
195+
196+ $(logTM) InfoS "Log context restored to () and namespace to MyApp.main"
197+@
198+
199+'katipAddNamespace' and 'katipAddContext' are one-liners, implemented
200+in terms of 'local' from 'MonadReader'. If you have a custom monad
201+transformer stack and want to add your own version of these, check out
202+<https://github.com/Soostone/katip/tree/master/katip/examples these
203+examples>.
204+-}
katip/src/Katip/Core.hs+154−52
@@ -1,9 +1,12 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DefaultSignatures #-}
13{-# LANGUAGE DeriveFunctor #-}
24{-# LANGUAGE DeriveGeneric #-}
35{-# LANGUAGE ExistentialQuantification #-}
46{-# LANGUAGE FlexibleContexts #-}
57{-# LANGUAGE FlexibleInstances #-}
68{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9+{-# LANGUAGE ImplicitParams #-}
710{-# LANGUAGE MultiParamTypeClasses #-}
811{-# LANGUAGE OverloadedStrings #-}
912{-# LANGUAGE RankNTypes #-}
@@ -11,14 +14,17 @@
1114{-# LANGUAGE TemplateHaskell #-}
1215{-# LANGUAGE TypeFamilies #-}
1316{-# LANGUAGE UndecidableInstances #-}
17+#if MIN_VERSION_base(4, 9, 0)
18+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
19+#endif
20+-- | This module is not meant to be imported directly and may contain
21+-- internal mechanisms that will change without notice.
1422module Katip.Core where
1523
1624-------------------------------------------------------------------------------
1725import Control.Applicative as A
1826import Control.AutoUpdate
1927import Control.Concurrent
20−import Control.Lens
21−import Control.Monad
2228import Control.Monad.Base
2329import Control.Monad.Catch
2430import Control.Monad.IO.Class
@@ -38,7 +44,7 @@ import Data.Foldable as FT
3844import qualified Data.HashMap.Strict as HM
3945import Data.List
4046import qualified Data.Map.Strict as M
41−import Data.Monoid
47+import Data.Semigroup
4248import Data.String
4349import Data.String.Conv
4450import Data.Text (Text)
@@ -46,13 +52,28 @@ import qualified Data.Text as T
4652import qualified Data.Text.Lazy.Builder as B
4753import Data.Time
4854import GHC.Generics hiding (to)
55+#if MIN_VERSION_base(4, 8, 0)
56+#if !MIN_VERSION_base(4, 9, 0)
57+import GHC.SrcLoc
58+#endif
59+import GHC.Stack
60+#endif
4961import Language.Haskell.TH
5062import qualified Language.Haskell.TH.Syntax as TH
63+import Lens.Micro
64+import Lens.Micro.TH
5165import Network.HostName
5266import System.Posix
5367-------------------------------------------------------------------------------
5468
5569
70+readMay :: Read a => String -> Maybe a
71+readMay s = case [x | (x,t) <- reads s, ("","") <- lex t] of
72+ [x] -> Just x
73+ [] -> Nothing -- no parse
74+ _ -> Nothing -- Ambiguous parse
75+
76+
5677-------------------------------------------------------------------------------
5778-- | Represents a heirarchy of namespaces going from general to
5879-- specific. For instance: ["processname", "subsystem"]. Note that
@@ -60,7 +81,7 @@ import System.Posix
6081-- IsString/OverloadedStrings, so "foo" will result in Namespace
6182-- ["foo"].
6283newtype Namespace = Namespace { unNamespace :: [Text] }
63− deriving (Eq,Show,Read,Ord,Generic,ToJSON,FromJSON,Monoid)
84+ deriving (Eq,Show,Read,Ord,Generic,ToJSON,FromJSON,Semigroup,Monoid)
6485
6586instance IsString Namespace where
6687 fromString s = Namespace [fromString s]
@@ -117,27 +138,25 @@ renderSeverity s = case s of
117138
118139
119140-------------------------------------------------------------------------------
120−severityText :: Prism' Text Severity
121−severityText = prism renderSeverity toSev
122− where
123− toSev "Debug" = Right DebugS
124− toSev "Info" = Right InfoS
125− toSev "Notice" = Right NoticeS
126− toSev "Warning" = Right WarningS
127− toSev "Error" = Right ErrorS
128− toSev "Critical" = Right CriticalS
129− toSev "Alert" = Right AlertS
130− toSev "Emergency" = Right EmergencyS
131− toSev x = Left x
141+textToSeverity :: Text -> Maybe Severity
142+textToSeverity "Debug" = Just DebugS
143+textToSeverity "Info" = Just InfoS
144+textToSeverity "Notice" = Just NoticeS
145+textToSeverity "Warning" = Just WarningS
146+textToSeverity "Error" = Just ErrorS
147+textToSeverity "Critical" = Just CriticalS
148+textToSeverity "Alert" = Just AlertS
149+textToSeverity "Emergency" = Just EmergencyS
150+textToSeverity _ = Nothing
132151
133−instance ToJSON Severity where
134− toJSON s = A.String (s ^. re severityText)
135152
153+instance ToJSON Severity where
154+ toJSON s = A.String (renderSeverity s)
136155
137156instance FromJSON Severity where
138157 parseJSON = A.withText "Severity" parseSeverity
139158 where
140− parseSeverity t = case t ^? severityText of
159+ parseSeverity t = case textToSeverity t of
141160 Just x -> return x
142161 Nothing -> fail $ "Invalid Severity " ++ toS t
143162
@@ -150,10 +169,16 @@ newtype LogStr = LogStr { unLogStr :: B.Builder }
150169instance IsString LogStr where
151170 fromString = LogStr . B.fromString
152171
172+
173+instance Semigroup LogStr where
174+ (LogStr a) <> (LogStr b) = LogStr (a <> b)
175+
176+
153177instance Monoid LogStr where
154− mappend (LogStr a) (LogStr b) = LogStr (a `mappend` b)
178+ mappend = (<>)
155179 mempty = LogStr mempty
156180
181+
157182instance FromJSON LogStr where
158183 parseJSON = A.withText "LogStr" parseLogStr
159184 where
@@ -290,13 +315,12 @@ instance FromJSON a => FromJSON (Item a) where
290315 <*> (fmap getLocJs <$> o A..: "loc")
291316
292317
293−processIDText :: Prism' Text ProcessID
294−processIDText = prism fromProcessID toProcessID
295− where
296− fromProcessID = toS . show
297− toProcessID t = case toS t ^? _Show of
298− Just i -> Right i
299− Nothing -> Left t
318+processIDToText :: ProcessID -> Text
319+processIDToText = toS . show
320+
321+
322+textToProcessID :: Text -> Maybe ProcessID
323+textToProcessID = readMay . toS
300324
301325
302326newtype ProcessIDJs = ProcessIDJs {
@@ -305,13 +329,13 @@ newtype ProcessIDJs = ProcessIDJs {
305329
306330
307331instance ToJSON ProcessIDJs where
308− toJSON (ProcessIDJs p) = A.String (p ^. re processIDText)
332+ toJSON (ProcessIDJs p) = A.String (processIDToText p)
309333
310334
311335instance FromJSON ProcessIDJs where
312336 parseJSON = A.withText "ProcessID" parseProcessID
313337 where
314− parseProcessID t = case t ^? processIDText of
338+ parseProcessID t = case textToProcessID t of
315339 Just p -> return $ ProcessIDJs p
316340 Nothing -> fail $ "Invalid ProcessIDJs " ++ toS t
317341
@@ -321,12 +345,17 @@ instance FromJSON ProcessIDJs where
321345data PayloadSelection
322346 = AllKeys
323347 | SomeKeys [Text]
348+ deriving (Show, Eq)
349+
350+instance Semigroup PayloadSelection where
351+ AllKeys <> _ = AllKeys
352+ _ <> AllKeys = AllKeys
353+ SomeKeys as <> SomeKeys bs = SomeKeys (as <> bs)
354+
324355
325356instance Monoid PayloadSelection where
326357 mempty = SomeKeys []
327− mappend AllKeys _ = AllKeys
328− mappend _ AllKeys = AllKeys
329− mappend (SomeKeys as) (SomeKeys bs) = SomeKeys (as++bs)
358+ mappend = (<>)
330359
331360
332361-------------------------------------------------------------------------------
@@ -339,8 +368,9 @@ instance Monoid PayloadSelection where
339368-- a ToJSON instance like:
340369--
341370-- > instance ToObject Foo
342−class ToJSON a => ToObject a where
371+class ToObject a where
343372 toObject :: a -> A.Object
373+ default toObject :: ToJSON a => a -> A.Object
344374 toObject v = case toJSON v of
345375 A.Object o -> o
346376 _ -> mempty
@@ -389,15 +419,22 @@ instance ToJSON SimpleLogPayload where
389419 toJSON (SimpleLogPayload as) = object $ map go as
390420 where go (k, AnyLogPayload v) = k A..= v
391421
422+
392423instance ToObject SimpleLogPayload
393424
425+
394426instance LogItem SimpleLogPayload where
395427 payloadKeys V0 _ = SomeKeys []
396428 payloadKeys _ _ = AllKeys
397429
430+
431+instance Semigroup SimpleLogPayload where
432+ SimpleLogPayload a <> SimpleLogPayload b = SimpleLogPayload (a <> b)
433+
434+
398435instance Monoid SimpleLogPayload where
399436 mempty = SimpleLogPayload []
400− SimpleLogPayload a `mappend` SimpleLogPayload b = SimpleLogPayload (a `mappend` b)
437+ mappend = (<>)
401438
402439
403440-------------------------------------------------------------------------------
@@ -412,7 +449,7 @@ sl a b = SimpleLogPayload [(a, AnyLogPayload b)]
412449payloadObject :: LogItem a => Verbosity -> a -> A.Object
413450payloadObject verb a = case FT.foldMap (flip payloadKeys a) [(V0)..verb] of
414451 AllKeys -> toObject a
415− SomeKeys ks -> HM.filterWithKey (\ k _ -> k `elem` ks) $ toObject a
452+ SomeKeys ks -> HM.filterWithKey (\ k _ -> k `FT.elem` ks) $ toObject a
416453
417454
418455-------------------------------------------------------------------------------
@@ -459,11 +496,15 @@ data Scribe = Scribe {
459496 }
460497
461498
499+instance Semigroup Scribe where
500+ (Scribe a) <> (Scribe b) = Scribe $ \ item -> do
501+ a item
502+ b item
503+
504+
462505instance Monoid Scribe where
463506 mempty = Scribe $ const $ return ()
464− mappend (Scribe a) (Scribe b) = Scribe $ \ item -> do
465− a item
466− b item
507+ mappend = (<>)
467508
468509
469510-------------------------------------------------------------------------------
@@ -476,12 +517,18 @@ permitItem sev i = _itemSeverity i >= sev
476517data LogEnv = LogEnv {
477518 _logEnvHost :: HostName
478519 , _logEnvPid :: ProcessID
479− , _logEnvNs :: Namespace
520+ , _logEnvApp :: Namespace
521+ -- ^ Name of application. This will typically never change. This
522+ -- field gets prepended to the namespace of your individual log
523+ -- messages. For example, if your app is MyApp and you write a log
524+ -- using "logItem" and the namespace "WebServer", the final
525+ -- namespace will be "MyApp.WebServer"
480526 , _logEnvEnv :: Environment
481527 , _logEnvTimer :: IO UTCTime
482528 -- ^ Action to fetch the timestamp. You can use something like
483529 -- 'AutoUpdate' for high volume logs but note that this may cause
484− -- some output forms to display logs out of order.
530+ -- some output forms to display logs out of order. Alternatively,
531+ -- you could just use 'getCurrentTime'.
485532 , _logEnvScribes :: M.Map Text Scribe
486533 }
487534makeLenses ''LogEnv
@@ -516,7 +563,7 @@ registerScribe
516563 -> Scribe
517564 -> LogEnv
518565 -> LogEnv
519−registerScribe nm h = logEnvScribes . at nm .~ Just h
566+registerScribe nm h = logEnvScribes %~ M.insert nm h
520567
521568
522569-------------------------------------------------------------------------------
@@ -527,12 +574,15 @@ unregisterScribe
527574 -- ^ Name of the scribe
528575 -> LogEnv
529576 -> LogEnv
530−unregisterScribe nm = logEnvScribes . at nm .~ Nothing
577+unregisterScribe nm = logEnvScribes %~ M.delete nm
531578
532579
533580-------------------------------------------------------------------------------
534581-- | Unregister *all* scribes. Logs will go off into space from this
535−-- point onward until new scribes are added.
582+-- point onward until new scribes are added. Note that you could use
583+-- this with `local` if you're using a Reader based stack to
584+-- temporarily disable log output. See `katipNoLogging` for an
585+-- example.
536586clearScribes
537587 :: LogEnv
538588 -> LogEnv
@@ -540,8 +590,16 @@ clearScribes = logEnvScribes .~ mempty
540590
541591
542592-------------------------------------------------------------------------------
543−-- | Monads where katip logging actions can be performed
544−class MonadIO m => Katip m where
593+-- | Monads where katip logging actions can be performed. Katip is the
594+-- most basic logging monad. You will typically use this directly if
595+-- you either don't want to use namespaces/contexts heavily or if you
596+-- want to pass in specific contexts and/or namespaces at each log site.
597+--
598+-- For something more powerful, look at the docs for 'KatipContext',
599+-- which keeps a namespace and merged context. You can write simple
600+-- functions that add additional namespacing and merges additional
601+-- context on the fly.
602+class MonadIO m => Katip m where
545603 getLogEnv :: m LogEnv
546604
547605
@@ -616,7 +674,7 @@ logItem a ns loc sev msg = do
616674 LogEnv{..} <- getLogEnv
617675 liftIO $ do
618676 item <- Item
619− <$> pure _logEnvNs
677+ <$> pure _logEnvApp
620678 <*> pure _logEnvEnv
621679 <*> pure sev
622680 <*> (mkThreadIdText <$> myThreadId)
@@ -625,9 +683,9 @@ logItem a ns loc sev msg = do
625683 <*> pure a
626684 <*> pure msg
627685 <*> _logEnvTimer
628− <*> pure (_logEnvNs <> ns)
686+ <*> pure (_logEnvApp <> ns)
629687 <*> pure loc
630− forM_ (M.elems _logEnvScribes) $ \ (Scribe h) -> h item
688+ FT.forM_ (M.elems _logEnvScribes) $ \ (Scribe h) -> h item
631689
632690
633691-------------------------------------------------------------------------------
@@ -713,9 +771,33 @@ liftLoc (Loc a b c (d1, d2) (e1, e2)) = [|Loc
713771
714772-------------------------------------------------------------------------------
715773-- | For use when you want to include location in your logs. This will
716−-- fill the 'Maybe Loc' gap in 'logF' of this module.
717−getLoc :: Q Exp
718−getLoc = [| $(location >>= liftLoc) |]
774+-- fill the 'Maybe Loc' gap in 'logF' of this module, and relies on implicit
775+-- callstacks when available (GHC > 7.8).
776+#if MIN_VERSION_base(4, 8, 0)
777+getLoc :: (?loc :: CallStack) => Maybe Loc
778+getLoc = case getCallStack ?loc of
779+ [] -> Nothing
780+ xs -> Just . toLoc . last $ xs
781+ where
782+ toLoc :: (String, SrcLoc) -> Loc
783+ toLoc (_, l) = Loc {
784+ loc_filename = srcLocFile l
785+ , loc_package = srcLocPackage l
786+ , loc_module = srcLocModule l
787+ , loc_start = (srcLocStartLine l, srcLocStartCol l)
788+ , loc_end = (srcLocEndLine l, srcLocEndCol l)
789+ }
790+#else
791+getLoc :: Maybe Loc
792+getLoc = Nothing
793+#endif
794+
795+
796+-------------------------------------------------------------------------------
797+-- Like `getLoc`, but uses template-haskell and works with older versions of
798+-- the compiler (GHC 7.8 or older).
799+getLocTH :: ExpQ
800+getLocTH = [| $(location >>= liftLoc) |]
719801
720802
721803-------------------------------------------------------------------------------
@@ -723,7 +805,27 @@ getLoc = [| $(location >>= liftLoc) |]
723805--
724806-- @$(logT) obj mempty InfoS "Hello world"@
725807logT :: ExpQ
726−logT = [| \ a ns sev msg -> logItem a ns (Just $(getLoc)) sev msg |]
808+logT = [| \ a ns sev msg -> logItem a ns (Just $(getLocTH)) sev msg |]
809+
810+
811+-------------------------------------------------------------------------------
812+-- | 'Loc'-tagged logging using implicit-callstacks when available.
813+--
814+-- This function does not require template-haskell as it
815+-- automatically uses <https://hackage.haskell.org/package/base-4.8.2.0/docs/GHC-Stack.html#v:getCallStack implicit-callstacks>
816+-- when the code is compiled using GHC > 7.8. Using an older version of the
817+-- compiler will result in the emission of a log line without any location information,
818+-- so be aware of it. Users using GHC <= 7.8 may want to use the template-haskell function
819+-- `logT` for maximum compatibility.
820+--
821+-- @logLoc obj mempty InfoS "Hello world"@
822+logLoc :: (Applicative m, LogItem a, Katip m)
823+ => a
824+ -> Namespace
825+ -> Severity
826+ -> LogStr
827+ -> m ()
828+logLoc a ns = logItem a ns getLoc
727829
728830
729831-- taken from the file-location package
katip/src/Katip/Format/Time.hsnew file+213
@@ -0,0 +1,213 @@
1+-- | Time and memory efficient time encoding helper functions.
2+--
3+module Katip.Format.Time
4+ ( formatAsLogTime
5+ , formatAsIso8601
6+ ) where
7+
8+import Control.Monad.ST (ST)
9+
10+import Data.Int (Int64)
11+import qualified Data.Text.Array as TA
12+import Data.Text (Text)
13+import Data.Text.Internal (Text(..))
14+import Data.Time (UTCTime(..), toGregorian, Day, DiffTime)
15+import Data.Word (Word16)
16+import Unsafe.Coerce (unsafeCoerce)
17+
18+-- Note: All functions here are optimized to never allocate anything
19+-- on heap. At least on ghc 8.0.1 no extra strictness annotations are
20+-- seem to be needed.
21+--
22+-- Exported functions are INLINEABLE
23+
24+
25+-- | Format 'UTCTime' into a short human readable format.
26+--
27+-- >>> formatAsLogTime $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012
28+-- "2016-01-23 01:23:45"
29+--
30+formatAsLogTime :: UTCTime -> Text
31+formatAsLogTime (UTCTime day time) = toText $ TA.run2 $ do
32+ buf <- TA.new 19 -- length "2016-10-20 12:34:56"
33+ _ <- writeDay buf 0 day
34+ TA.unsafeWrite buf 10 0x20 -- space
35+ _ <- writeTimeOfDay False buf 11 (diffTimeOfDay64 time)
36+ return (buf, 19)
37+ where
38+ toText (arr, len) = Text arr 0 len
39+
40+{-# INLINEABLE formatAsLogTime #-}
41+
42+-- | Format 'UTCTime' into a Iso8601 format.
43+--
44+-- Note that this function may overcommit up to 12*2 bytes, depending
45+-- on sub-second precision. If this is an issue, make a copy with a
46+-- 'Data.Text.copy'.
47+--
48+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012
49+-- "2016-11-23T01:23:45.123456789012Z"
50+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123
51+-- "2016-01-23T01:23:45.123Z"
52+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025
53+-- "2016-01-23T01:23:45Z"
54+
55+--
56+formatAsIso8601 :: UTCTime -> Text
57+formatAsIso8601 (UTCTime day time) = toText $ TA.run2 $ do
58+ buf <- TA.new 33 -- length "2016-10-20 12:34:56.123456789012Z"
59+ _ <- writeDay buf 0 day
60+ TA.unsafeWrite buf 10 0x54 -- T
61+ next <- writeTimeOfDay True buf 11 (diffTimeOfDay64 time)
62+ TA.unsafeWrite buf next 0x5A -- Z
63+ return (buf, next+1)
64+ where
65+ toText (arr, len) = Text arr 0 len
66+
67+{-# INLINEABLE formatAsIso8601 #-}
68+
69+-- | Writes the @YYYY-MM-DD@ part of timestamp
70+writeDay :: TA.MArray s -> Int -> Day -> ST s Int
71+writeDay buf off day =
72+ do
73+ TA.unsafeWrite buf (off + 0) (digit y1)
74+ TA.unsafeWrite buf (off + 1) (digit y2)
75+ TA.unsafeWrite buf (off + 2) (digit y3)
76+ TA.unsafeWrite buf (off + 3) (digit y4)
77+ TA.unsafeWrite buf (off + 4) 0x2d -- dash
78+ TA.unsafeWrite buf (off + 5) m1
79+ TA.unsafeWrite buf (off + 6) m2
80+ TA.unsafeWrite buf (off + 7) 0x2d -- dash
81+ TA.unsafeWrite buf (off + 8) d1
82+ TA.unsafeWrite buf (off + 9) d2
83+ return (off + 10)
84+ where
85+ (yr,m,d) = toGregorian day
86+ (y1, ya) = fromIntegral (abs yr) `quotRem` 1000
87+ (y2, yb) = ya `quotRem` 100
88+ (y3, y4) = yb `quotRem` 10
89+ T m1 m2 = twoDigits m
90+ T d1 d2 = twoDigits d
91+{-# INLINE writeDay #-}
92+
93+-- | Write time of day, optionally with sub seconds
94+writeTimeOfDay :: Bool -> TA.MArray s -> Int -> TimeOfDay64 -> ST s Int
95+writeTimeOfDay doSubSeconds buf off (TOD hh mm ss) =
96+ do
97+
98+ TA.unsafeWrite buf off h1
99+ TA.unsafeWrite buf (off + 1) h2
100+ TA.unsafeWrite buf (off + 2) 0x3A -- colon
101+ TA.unsafeWrite buf (off + 3) m1
102+ TA.unsafeWrite buf (off + 4) m2
103+ TA.unsafeWrite buf (off + 5) 0x3A -- colon
104+ TA.unsafeWrite buf (off + 6) s1
105+ TA.unsafeWrite buf (off + 7) s2
106+ if doSubSeconds && frac /= 0
107+ then writeFracSeconds buf (off + 8) frac
108+ else return (off + 8)
109+ where
110+ T h1 h2 = twoDigits hh
111+ T m1 m2 = twoDigits mm
112+ T s1 s2 = twoDigits (fromIntegral real)
113+ (real,frac) = ss `quotRem` pico
114+ pico = 1000000000000 -- number of picoseconds in 1 second
115+
116+
117+writeFracSeconds :: TA.MArray s -> Int -> Int64 -> ST s Int
118+writeFracSeconds buf off frac =
119+ do
120+ TA.unsafeWrite buf off 0x2e -- period
121+ if mills == 0
122+ then do
123+ writeTrunc6 buf (off + 1) (fromIntegral mics)
124+ else do
125+ writeDigit6 buf (off + 1) (fromIntegral mics)
126+ writeTrunc6 buf (off + 7) (fromIntegral mills)
127+
128+ where
129+ (mics, mills) = frac `quotRem` micro
130+ micro = 1000000 -- number of microseconds in 1 second
131+
132+
133+writeDigit6 :: TA.MArray s -> Int -> Int -> ST s ()
134+writeDigit6 buf off i =
135+ do
136+ writeDigit3 buf off f1
137+ writeDigit3 buf (off+3) f2
138+ where
139+ (f1, f2) = i `quotRem` 1000
140+
141+{-# INLINE writeDigit6 #-}
142+
143+writeDigit3 :: TA.MArray s -> Int -> Int -> ST s ()
144+writeDigit3 buf off i =
145+ do
146+ TA.unsafeWrite buf off (digit d1)
147+ TA.unsafeWrite buf (off+1) (digit d2)
148+ TA.unsafeWrite buf (off+2) (digit d3)
149+ where
150+ (d1, d) = i `quotRem` 100
151+ (d2, d3) = d `quotRem` 10
152+
153+{-# INLINE writeDigit3 #-}
154+
155+writeTrunc6 :: TA.MArray s -> Int -> Int -> ST s Int
156+writeTrunc6 buf off i =
157+ if f2 == 0
158+ then writeTrunc3 buf off f1
159+ else do
160+ writeDigit3 buf off f1
161+ writeTrunc3 buf (off+3) f2
162+ where
163+ (f1, f2) = i `quotRem` 1000
164+
165+{-# INLINE writeTrunc6 #-}
166+
167+
168+writeTrunc3 :: TA.MArray s -> Int -> Int -> ST s Int
169+writeTrunc3 buf off i
170+ | d == 0 = do
171+ TA.unsafeWrite buf off (digit d1)
172+ return (off+1)
173+ | d3 == 0 = do
174+ TA.unsafeWrite buf off (digit d1)
175+ TA.unsafeWrite buf (off+1) (digit d2)
176+ return (off+2)
177+
178+ | otherwise = do
179+ TA.unsafeWrite buf off (digit d1)
180+ TA.unsafeWrite buf (off+1) (digit d2)
181+ TA.unsafeWrite buf (off+2) (digit d3)
182+ return (off+3)
183+ where
184+ (d1, d) = i `quotRem` 100
185+ (d2, d3) = d `quotRem` 10
186+
187+{-# INLINE writeTrunc3 #-}
188+
189+
190+-- Following code was adapted from aeson package.
191+--
192+-- Copyright: (c) 2015-2016 Bryan O'Sullivan
193+-- License: BSD3
194+
195+data T = T {-# UNPACK #-} !Word16 {-# UNPACK #-} !Word16
196+
197+twoDigits :: Int -> T
198+twoDigits a = T (digit hi) (digit lo)
199+ where (hi,lo) = a `quotRem` 10
200+
201+digit :: Int -> Word16
202+digit x = fromIntegral (x + 48)
203+
204+
205+data TimeOfDay64 = TOD {-# UNPACK #-} !Int
206+ {-# UNPACK #-} !Int
207+ {-# UNPACK #-} !Int64
208+
209+diffTimeOfDay64 :: DiffTime -> TimeOfDay64
210+diffTimeOfDay64 t = TOD (fromIntegral h) (fromIntegral m) s
211+ where (h,mp) = fromIntegral pico `quotRem` 3600000000000000
212+ (m,s) = mp `quotRem` 60000000000000
213+ pico = unsafeCoerce t :: Integer
katip/src/Katip/Monadic.hs+98−10
@@ -7,7 +7,9 @@
77{-# LANGUAGE TemplateHaskell #-}
88{-# LANGUAGE TypeFamilies #-}
99{-# LANGUAGE UndecidableInstances #-}
10−
10+#if MIN_VERSION_base(4, 9, 0)
11+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
12+#endif
1113-- | Provides support for treating payloads and namespaces as
1214-- composable contexts. The common pattern would be to provide a
1315-- 'KatipContext' instance for your base monad.
@@ -16,6 +18,7 @@ module Katip.Monadic
1618 -- * Monadic variants of logging functions from "Katip.Core"
1719 logFM
1820 , logTM
21+ , logLocM
1922 , logItemM
2023 , logExceptionM
2124
@@ -28,6 +31,9 @@ module Katip.Monadic
2831 -- * KatipContextT - Utility transformer that provides Katip and KatipContext instances
2932 , KatipContextT(..)
3033 , runKatipContextT
34+ , katipAddNamespace
35+ , katipAddContext
36+ , katipNoLogging
3137 , KatipContextTState(..)
3238 ) where
3339
@@ -53,8 +59,10 @@ import qualified Control.Monad.Trans.State.Strict as Strict (StateT)
5359import qualified Control.Monad.Trans.Writer.Strict as Strict (WriterT)
5460import Control.Monad.Writer
5561import Data.Aeson
62+import qualified Data.Foldable as FT
5663import qualified Data.HashMap.Strict as HM
5764import Data.Monoid as M
65+import Data.Sequence as Seq
5866import Data.Text (Text)
5967import Language.Haskell.TH
6068-------------------------------------------------------------------------------
@@ -72,17 +80,29 @@ data AnyLogContext where
7280-- 'LogContext' instance for combining multiple payload policies. This
7381-- is critical for log contexts deep down in a stack to be able to
7482-- inject their own context without worrying about other context that
75−-- has already been set.
76−newtype LogContexts = LogContexts [AnyLogContext] deriving (Monoid)
83+-- has already been set. Also note that contexts are treated as a
84+-- sequence and '<>' will be appended to the right hand side of the
85+-- sequence. If there are conflicting keys in the contexts, the /right
86+-- side will take precedence/, which is counter to how monoid works
87+-- for 'Map' and 'HashMap', so bear that in mind. The reasoning is
88+-- that if the user is /sequentially/ adding contexts to the right
89+-- side of the sequence, on conflict the intent is to overwrite with
90+-- the newer value (i.e. the rightmost value).
91+--
92+-- Additional note: you should not mappend LogContexts in any sort of
93+-- infinite loop, as it retains all data, so that would be a memory
94+-- leak.
95+newtype LogContexts = LogContexts (Seq AnyLogContext) deriving (Monoid)
7796
7897instance ToJSON LogContexts where
7998 toJSON (LogContexts cs) =
80− Object $ mconcat $ map (\(AnyLogContext v) -> toObject v) cs
99+ -- flip mappend to get right-biased merge
100+ Object $ FT.foldr (flip mappend) mempty $ fmap (\(AnyLogContext v) -> toObject v) cs
81101
82102instance ToObject LogContexts
83103
84104instance LogItem LogContexts where
85− payloadKeys verb (LogContexts vs) = mconcat $ map payloadKeys' vs
105+ payloadKeys verb (LogContexts vs) = FT.foldr (flip mappend) mempty $ fmap payloadKeys' vs
86106 where
87107 -- To ensure AllKeys doesn't leak keys from other values when
88108 -- combined, we resolve AllKeys to its equivalent SomeKeys
@@ -96,7 +116,7 @@ instance LogItem LogContexts where
96116-- | Lift a log context into the generic wrapper so that it can
97117-- combine with the existing log context.
98118liftPayload :: (LogItem a) => a -> LogContexts
99−liftPayload = LogContexts . (:[]) . AnyLogContext
119+liftPayload = LogContexts . Seq.singleton . AnyLogContext
100120
101121
102122-------------------------------------------------------------------------------
@@ -139,7 +159,7 @@ deriving instance (Monad m, KatipContext m) => KatipContext (KatipT m)
139159-- very low level and you typically can use 'logTM' in its
140160-- place. Automaticallysupplies payload and namespace.
141161logItemM
142− :: (Applicative m, KatipContext m, Katip m)
162+ :: (Applicative m, KatipContext m)
143163 => Maybe Loc
144164 -> Severity
145165 -> LogStr
@@ -155,7 +175,7 @@ logItemM loc sev msg = do
155175-- | Log with full context, but without any code
156176-- location. Automatically supplies payload and namespace.
157177logFM
158− :: (Applicative m, KatipContext m, Katip m)
178+ :: (Applicative m, KatipContext m)
159179 => Severity
160180 -- ^ Severity of the message
161181 -> LogStr
@@ -171,9 +191,30 @@ logFM sev msg = do
171191-- | 'Loc'-tagged logging when using template-haskell. Automatically
172192-- supplies payload and namespace.
173193--
174−-- @$(logt) InfoS "Hello world"@
194+-- @$(logTM) InfoS "Hello world"@
175195logTM :: ExpQ
176−logTM = [| logItemM (Just $(getLoc)) |]
196+logTM = [| logItemM (Just $(getLocTH)) |]
197+
198+
199+-------------------------------------------------------------------------------
200+-- | 'Loc'-tagged logging when using template-haskell. Automatically
201+-- supplies payload and namespace.
202+--
203+-- Same consideration as `logLoc` applies.
204+--
205+-- This function does not require template-haskell as it
206+-- automatically uses <https://hackage.haskell.org/package/base-4.8.2.0/docs/GHC-Stack.html#v:getCallStack implicit-callstacks>
207+-- when the code is compiled using GHC > 7.8. Using an older version of the
208+-- compiler will result in the emission of a log line without any location information,
209+-- so be aware of it. Users using GHC <= 7.8 may want to use the template-haskell function
210+-- `logTM` for maximum compatibility.
211+--
212+-- @logLocM InfoS "Hello world"@
213+logLocM :: (Applicative m, KatipContext m)
214+ => Severity
215+ -> LogStr
216+ -> m ()
217+logLocM = logItemM getLoc
177218
178219
179220-------------------------------------------------------------------------------
@@ -270,3 +311,50 @@ runKatipContextT :: (LogItem c) => LogEnv -> c -> Namespace -> KatipContextT m a
270311runKatipContextT le ctx ns = flip runReaderT lts . unKatipContextT
271312 where
272313 lts = KatipContextTState le (liftPayload ctx) ns
314+
315+
316+-------------------------------------------------------------------------------
317+-- | Append a namespace segment to the current namespace for the given
318+-- monadic action, then restore the previous state
319+-- afterwards.
320+katipAddNamespace
321+ :: (Monad m)
322+ => Namespace
323+ -> KatipContextT m a
324+ -> KatipContextT m a
325+katipAddNamespace ns (KatipContextT f) =
326+ KatipContextT (local (\r -> r { ltsNamespace = (ltsNamespace r) <> ns}) f)
327+
328+
329+-------------------------------------------------------------------------------
330+-- | Append some context to the current context for the given monadic
331+-- action, then restore the previous state afterwards. Important note:
332+-- be careful using this in a loop. If you're using something like
333+-- 'forever' or 'replicateM_' that does explicit sharing to avoid a
334+-- memory leak, youll be fine as it will *sequence* calls to
335+-- 'katipAddNamespace', so each loop will get the same context
336+-- added. If you instead roll your own recursion and you're recursing
337+-- in the action you provide, you'll instead accumulate tons of
338+-- redundant contexts and even if they all merge on log, they are
339+-- stored in a sequence and will leak memory.
340+katipAddContext
341+ :: ( LogItem i
342+ , Monad m
343+ )
344+ => i
345+ -> KatipContextT m a
346+ -> KatipContextT m a
347+katipAddContext i (KatipContextT f) =
348+ KatipContextT (local (\r -> r { ltsContext = (ltsContext r) <> liftPayload i}) f)
349+
350+
351+-------------------------------------------------------------------------------
352+-- | Disable all scribes for the given monadic action, then restore
353+-- them afterwards.
354+katipNoLogging
355+ :: ( Monad m
356+ )
357+ => KatipContextT m a
358+ -> KatipContextT m a
359+katipNoLogging (KatipContextT f) =
360+ KatipContextT (local (\r -> r { ltsLogEnv = clearScribes (ltsLogEnv r)}) f)
katip/src/Katip/Scribes/Handle.hs+26−18
@@ -3,19 +3,21 @@
33module Katip.Scribes.Handle where
44
55-------------------------------------------------------------------------------
6−import Control.Lens
6+import Control.Applicative as A
7+import Control.Concurrent
8+import Control.Exception (bracket_)
79import Control.Monad
8−import Data.Aeson.Lens
10+import Data.Aeson
911import qualified Data.HashMap.Strict as HM
1012import Data.Monoid
13+import Data.Text (Text)
1114import Data.Text.Lazy.Builder
1215import Data.Text.Lazy.IO as T
13−import Data.Time
14−import qualified Data.Time.Locale.Compat as LC
1516import System.IO
1617import System.IO.Unsafe (unsafePerformIO)
1718-------------------------------------------------------------------------------
1819import Katip.Core
20+import Katip.Format.Time (formatAsLogTime)
1921-------------------------------------------------------------------------------
2022
2123
@@ -26,18 +28,17 @@ brackets m = fromText "[" <> m <> fromText "]"
2628
2729-------------------------------------------------------------------------------
2830getKeys :: LogItem s => Verbosity -> s -> [Builder]
29−getKeys verb a = payloadObject verb a ^..
30− to HM.toList . traverse . to rendPair
31+getKeys verb a = concat (renderPair A.<$> HM.toList (payloadObject verb a))
3132 where
32− rendPair (k,v) = fromText k <> fromText ":" <> (v ^. _Primitive . to renderPrim)
33−
34−
35−-------------------------------------------------------------------------------
36−renderPrim :: Primitive -> Builder
37−renderPrim (StringPrim t) = fromText t
38−renderPrim (NumberPrim s) = fromString (show s)
39−renderPrim (BoolPrim b) = fromString (show b)
40−renderPrim NullPrim = fromText "null"
33+ renderPair :: (Text, Value) -> [Builder]
34+ renderPair (k,v) =
35+ case v of
36+ Object o -> concat [renderPair (k <> "." <> k', v') | (k', v') <- HM.toList o]
37+ String t -> [fromText (k <> ":" <> t)]
38+ Number n -> [fromText (k <> ":") <> fromString (show n)]
39+ Bool b -> [fromText (k <> ":") <> fromString (show b)]
40+ Null -> [fromText (k <> ":null")]
41+ _ -> mempty -- Can't think of a sensible way to handle arrays
4142
4243
4344-------------------------------------------------------------------------------
@@ -49,15 +50,22 @@ data ColorStrategy
4950
5051
5152-------------------------------------------------------------------------------
52−-- | Logs to a file handle such as stdout, stderr, or a file.
53+-- | Logs to a file handle such as stdout, stderr, or a file. Contexts
54+-- and other information will be flattened out into bracketed
55+-- fields. For example:
56+--
57+-- > [2016-05-11 21:01:15][MyApp][Info][myhost.example.com][1724][ThreadId 1154][main:Helpers.Logging Helpers/Logging.hs:32:7] Started
58+-- > [2016-05-11 21:01:15][MyApp.confrabulation][Debug][myhost.example.com][1724][ThreadId 1154][confrab_factor:42.0][main:Helpers.Logging Helpers/Logging.hs:41:9] Confrabulating widgets, with extra namespace and context
59+-- > [2016-05-11 21:01:15][MyApp][Info][myhost.example.com][1724][ThreadId 1154][main:Helpers.Logging Helpers/Logging.hs:43:7] Namespace and context are back to normal
5360mkHandleScribe :: ColorStrategy -> Handle -> Severity -> Verbosity -> IO Scribe
5461mkHandleScribe cs h sev verb = do
5562 hSetBuffering h LineBuffering
5663 colorize <- case cs of
5764 ColorIfTerminal -> hIsTerminalDevice h
5865 ColorLog b -> return b
66+ lock <- newMVar ()
5967 return $ Scribe $ \ i@Item{..} -> do
60− when (permitItem sev i) $
68+ when (permitItem sev i) $ bracket_ (takeMVar lock) (putMVar lock ()) $
6169 T.hPutStrLn h $ toLazyText $ formatItem colorize verb i
6270
6371
@@ -74,7 +82,7 @@ formatItem withColor verb Item{..} =
7482 maybe mempty (brackets . fromString . locationToString) _itemLoc <>
7583 fromText " " <> (unLogStr _itemMessage)
7684 where
77− nowStr = fromString $ formatTime LC.defaultTimeLocale "%Y-%m-%d %H:%M:%S" _itemTime
85+ nowStr = fromText (formatAsLogTime _itemTime)
7886 ks = map brackets $ getKeys verb _itemPayload
7987 renderSeverity' s = case s of
8088 EmergencyS -> red $ renderSeverity s
katip/test/Katip/Tests.hs+58−1
@@ -1,4 +1,7 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RecordWildCards #-}
25{-# LANGUAGE ScopedTypeVariables #-}
36{-# LANGUAGE StandaloneDeriving #-}
47{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -10,6 +13,9 @@ module Katip.Tests
1013-------------------------------------------------------------------------------
1114import Control.Applicative
1215import Data.Aeson
16+import qualified Data.HashMap.Strict as HM
17+import Data.Monoid
18+import Data.Text (Text)
1319import qualified Data.Text.Lazy.Builder as B
1420import Data.Time
1521import Data.Time.Clock.POSIX
@@ -17,9 +23,11 @@ import Language.Haskell.TH
1723import System.Posix.Types
1824import Test.QuickCheck.Instances ()
1925import Test.Tasty
26+import Test.Tasty.HUnit
2027import Test.Tasty.QuickCheck
2128-------------------------------------------------------------------------------
2229import Katip
30+import Katip.Core
2331-------------------------------------------------------------------------------
2432
2533
@@ -27,10 +35,56 @@ tests :: TestTree
2735tests = testGroup "Katip"
2836 [
2937 testProperty "JSON cycle Item" $ \(i :: Item ()) ->
30− prop_json_cycle i
38+ prop_json_cycle i
39+ , testProperty "renderSeverity/textToSeverity cycle" $ \sev ->
40+ textToSeverity(renderSeverity sev) === Just sev
41+ , testProperty "processIDToText/textToProcessID cycle" $ \pid ->
42+ textToProcessID (processIDToText pid) === Just pid
43+ , testCase "processIDToText is just the number" $ do
44+ processIDToText 123 @?= "123"
45+ , logContextsTests
3146 ]
3247
3348
49+-------------------------------------------------------------------------------
50+logContextsTests :: TestTree
51+logContextsTests = testGroup "logContexts"
52+ [
53+ testCase "overwrites with the right-hand side (right-bias)" $ do
54+ let l1 = liftPayload (SimpleLogPayload [("foo", AnyLogPayload ("a" :: Text))])
55+ l2 = liftPayload (SimpleLogPayload [("foo", AnyLogPayload ("b" :: Text))])
56+ l3 = liftPayload (SimpleLogPayload [("foo", AnyLogPayload ("c" :: Text))])
57+ both = l1 <> l2 <> l3
58+ toObject both @?= HM.singleton "foo" (String "c")
59+ , testCase "respects payloadKeys for each constituent payload" $ do
60+ let everything = liftPayload (SimpleLogPayload [("foo", AnyLogPayload ("a" :: Text))])
61+ conservative = liftPayload (ConservativePayload "always" "rarely")
62+ both = everything <> conservative
63+ payloadKeys V2 both @?= SomeKeys ["often_shown", "rarely_shown", "foo"]
64+ payloadKeys V1 both @?= SomeKeys ["often_shown", "foo"]
65+ ]
66+
67+
68+-------------------------------------------------------------------------------
69+data ConservativePayload = ConservativePayload {
70+ oftenShown :: Text
71+ , rarelyShown :: Text
72+ }
73+
74+
75+instance ToJSON ConservativePayload where
76+ toJSON ConservativePayload {..} = object ["often_shown" .= oftenShown
77+ ,"rarely_shown" .= rarelyShown]
78+
79+
80+instance ToObject ConservativePayload
81+
82+
83+instance LogItem ConservativePayload where
84+ payloadKeys V1 _ = SomeKeys ["often_shown"]
85+ payloadKeys V0 _ = SomeKeys []
86+ payloadKeys _ _ = AllKeys
87+
3488-------------------------------------------------------------------------------
3589prop_json_cycle :: (ToJSON a, FromJSON a, Eq a, Show a) => a -> Property
3690prop_json_cycle a = eitherDecode (encode a) === Right a
@@ -69,6 +123,9 @@ deriving instance Arbitrary Namespace
69123deriving instance Arbitrary Environment
70124deriving instance Arbitrary ThreadIdText
71125deriving instance Arbitrary CPid
126+#if !MIN_VERSION_base(4, 8, 0)
127+deriving instance Eq Loc
128+#endif
72129deriving instance Eq LogStr
73130deriving instance (Eq a) => Eq (Item a)
74131
katip/test/Katip/Tests/Format/Time.hsnew file+81
@@ -0,0 +1,81 @@
1+
2+module Katip.Tests.Format.Time
3+ ( tests
4+ ) where
5+
6+import Data.Aeson (toJSON)
7+import qualified Data.Text as Text
8+import Data.Time
9+import Data.Time.Clock.POSIX
10+import qualified Data.Time.Locale.Compat as LC
11+
12+import Test.Tasty
13+import Test.Tasty.HUnit
14+import Test.Tasty.QuickCheck
15+
16+import Katip.Format.Time
17+
18+tests :: TestTree
19+tests = testGroup "Katip.Format.Time"
20+ [ testCase "formatAsLogTime" $ do
21+ assertEqual "time 1" (formatDT t1) (formatAsLogTime t1)
22+ assertEqual "time 2" (formatDT t2) (formatAsLogTime t2)
23+ assertEqual "time 3" (formatDT t3) (formatAsLogTime t3)
24+ assertEqual "time 4" (formatDT t4) (formatAsLogTime t4)
25+ assertEqual "time 5" (formatDT t5) (formatAsLogTime t5)
26+
27+ , testCase "formatAsIso8601" $ do
28+ assertEqual "time 1" (formatDTISO t1) (formatAsIso8601 t1)
29+ assertEqual "time 2" (formatDTISO t2) (formatAsIso8601 t2)
30+ assertEqual "time 3" (formatDTISO t3) (formatAsIso8601 t3)
31+ assertEqual "time 4" (formatDTISO t4) (formatAsIso8601 t4)
32+ assertEqual "time 5" (formatDTISO t5) (formatAsIso8601 t5)
33+
34+ , testCase "formatAsIso8601 Aeson" $ do
35+ assertEqual "time 1" (toJSON t1) (toJSON $ formatAsIso8601 t1)
36+ assertEqual "time 2" (toJSON t2) (toJSON $ formatAsIso8601 t2)
37+ assertEqual "time 3" (toJSON t3) (toJSON $ formatAsIso8601 t3)
38+ assertEqual "time 4" (toJSON t4) (toJSON $ formatAsIso8601 t4)
39+ assertEqual "time 5" (toJSON t5) (toJSON $ formatAsIso8601 t5)
40+
41+
42+ , testProperty "LogTime same as Date.Time" $ \(ArbUTCTime t) ->
43+ prop_format_log t
44+
45+ , testProperty "ISO 8601 same as Date.Time" $ \(ArbUTCTime t) ->
46+ prop_format_iso t
47+
48+ , testProperty "ISO 8601 same as Aeson" $ \(ArbUTCTime t) ->
49+ prop_format_aeson t
50+
51+ ]
52+ where
53+ t1 = UTCTime (fromGregorian 2016 12 34) 5025.123456789012
54+ t2 = UTCTime (fromGregorian 2016 1 23) 5025.123
55+ t3 = UTCTime (fromGregorian 16 12 1) 025
56+ t4 = UTCTime (fromGregorian 2000 1 1) 0.000000000001
57+ t5 = UTCTime (fromGregorian 2000 12 31) 0.100000000001
58+
59+formatDT :: UTCTime -> Text.Text
60+formatDT = Text.pack . formatTime LC.defaultTimeLocale "%0Y-%m-%d %H:%M:%S"
61+
62+formatDTISO :: UTCTime -> Text.Text
63+formatDTISO = Text.pack . formatTime LC.defaultTimeLocale "%0Y-%m-%dT%H:%M:%S%QZ"
64+
65+prop_format_log :: UTCTime -> Property
66+prop_format_log a = formatDT a === formatAsLogTime a
67+
68+prop_format_iso :: UTCTime -> Property
69+prop_format_iso a = formatDTISO a === formatAsIso8601 a
70+
71+prop_format_aeson :: UTCTime -> Property
72+prop_format_aeson a = toJSON a === toJSON (formatAsIso8601 a)
73+
74+
75+newtype ArbUTCTime = ArbUTCTime {
76+ unArbUTCTime :: UTCTime
77+ } deriving (Show)
78+
79+instance Arbitrary ArbUTCTime where
80+ arbitrary = fmap (ArbUTCTime . posixSecondsToUTCTime . fromInteger)
81+ arbitrary
katip/test/Katip/Tests/Scribes/Handle-text.goldennew file+40
@@ -0,0 +1,40 @@
1+[2016-06-12 12:34:56][foo][Debug][example][7331][1337][note.deep:some note] message
2+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
3+[2016-06-12 12:34:56][foo][Notice][example][7331][1337][note.deep:some note] message
4+[2016-06-12 12:34:56][foo][Warning][example][7331][1337][note.deep:some note] message
5+[2016-06-12 12:34:56][foo][Error][example][7331][1337][note.deep:some note] message
6+[2016-06-12 12:34:56][foo][Critical][example][7331][1337][note.deep:some note] message
7+[2016-06-12 12:34:56][foo][Alert][example][7331][1337][note.deep:some note] message
8+[2016-06-12 12:34:56][foo][Emergency][example][7331][1337][note.deep:some note] message
9+[2016-06-12 12:34:56][foo][Info][example][7331][0][note.deep:some note] message
10+[2016-06-12 12:34:56][foo][Info][example][7331][1][note.deep:some note] message
11+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
12+[2016-06-12 12:34:56][foo][Info][example][7331][2147483647][note.deep:some note] message
13+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
14+[2016-06-12 12:34:56][foo][Info][www.example.com][7331][1337][note.deep:some note] message
15+[2016-06-12 12:34:56][foo][Info][127.0.0.1][7331][1337][note.deep:some note] message
16+[2016-06-12 12:34:56][foo][Info][example][0][1337][note.deep:some note] message
17+[2016-06-12 12:34:56][foo][Info][example][1][1337][note.deep:some note] message
18+[2016-06-12 12:34:56][foo][Info][example][1337][1337][note.deep:some note] message
19+[2016-06-12 12:34:56][foo][Info][example][2147483647][1337][note.deep:some note] message
20+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
21+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
22+with newline
23+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message a really long message
24+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] сообщение
25+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] 哈囉世界
26+[2000-01-01 00:00:00][foo][Info][example][7331][1337][note.deep:some note] message
27+[2123-12-31 23:59:59][foo][Info][example][7331][1337][note.deep:some note] message
28+[2016-10-10 01:01:05][foo][Info][example][7331][1337][note.deep:some note] message
29+[2100-12-31 12:59:10][foo][Info][example][7331][1337][note.deep:some note] message
30+[1982-01-01 12:30:00][foo][Info][example][7331][1337][note.deep:some note] message
31+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
32+[2016-06-12 12:34:56][foo.bar][Info][example][7331][1337][note.deep:some note] message
33+[2016-06-12 12:34:56][фу.бар][Info][example][7331][1337][note.deep:some note] message
34+[2016-06-12 12:34:56][with
35+newline][Info][example][7331][1337][note.deep:some note] message
36+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note] message
37+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note][main:Some.Module path/Some/Module.hs:30:1] message
38+[2016-06-12 12:34:56][foo][Info][example][7331][1337][note.deep:some note][main:Some.Module путь/Some/Module.hs:3000:9000] message
39+[2016-06-12 12:34:56][foo][Info][example][7331][1337][sub:null][text:][num:0.0][float:0.0] message
40+[2016-06-12 12:34:56][foo][Info][example][7331][1337][sub.note.deep:some note][text:note][num:10.0][float:5.5] message
katip/test/Katip/Tests/Scribes/Handle.hs+166−6
@@ -1,4 +1,4 @@
1−{-# LANGUAGE OverloadedStrings #-}
1+{-# LANGUAGE OverloadedStrings #-}
22module Katip.Tests.Scribes.Handle
33 ( tests
44 ) where
@@ -6,16 +6,26 @@ module Katip.Tests.Scribes.Handle
66-------------------------------------------------------------------------------
77import Control.Monad
88import Data.Aeson
9−import Data.Text (Text)
10−import qualified Data.Text.IO as T
9+import qualified Data.ByteString.Lazy as BL
10+import Data.Monoid
11+import Data.Text (Text)
12+import qualified Data.Text as T
13+import qualified Data.Text.IO as T
14+import qualified Data.Text.Lazy as LT
15+import qualified Data.Text.Lazy.Builder as LT
16+import Data.Time
17+import Language.Haskell.TH.Syntax (Loc (..))
18+import Lens.Micro ((.~))
1119import System.Directory
1220import System.IO
1321import System.IO.Temp
1422import Test.Tasty
23+import Test.Tasty.Golden
1524import Test.Tasty.HUnit
1625import Text.Regex.TDFA
1726-------------------------------------------------------------------------------
1827import Katip
28+import Katip.Scribes.Handle
1929-------------------------------------------------------------------------------
2030
2131
@@ -27,9 +37,13 @@ tests = testGroup "Katip.Scribes.Handle"
2737 runKatipT le $ logItem dummyLogItem "test" Nothing InfoS "test message"
2838 hClose h
2939 res <- readFile path
30− let pat = "\\[[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\]\\[katip-test.test\\]\\[Info\\]\\[.+\\]\\[[[:digit:]]+\\]\\[ThreadId [[:digit:]]+\\]\\[note:some note\\] test message" :: String
40+ let pat = "\\[[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\]\\[katip-test.test\\]\\[Info\\]\\[.+\\]\\[[[:digit:]]+\\]\\[ThreadId [[:digit:]]+\\]\\[note.deep:some note\\] test message" :: String
3141 let matches = res =~ pat
32− matches @?= True
42+ assertBool (res <> " did not match") matches
43+ , withResource setupTempFile teardownTempFile $ \setup ->
44+ goldenVsString "Text-golden"
45+ "test/Katip/Tests/Scribes/Handle-text.golden"
46+ (setup >>= writeTextLog)
3347 ]
3448
3549
@@ -40,7 +54,10 @@ data DummyLogItem = DummyLogItem {
4054
4155
4256instance ToJSON DummyLogItem where
43− toJSON (DummyLogItem n) = object ["note" .= n]
57+ toJSON (DummyLogItem n) = object
58+ [ "note" .= object [ "deep" .= n
59+ ]
60+ ]
4461
4562
4663instance ToObject DummyLogItem
@@ -70,3 +87,146 @@ teardown :: (FilePath, Handle, LogEnv) -> IO ()
7087teardown (_, h, _) = do
7188 chk <- hIsOpen h
7289 when chk $ hClose h
90+
91+
92+
93+-- Following code tests Handle scribe output against a golden file.
94+-- This test will fail on non utf8 locales because golden file is in utf-8.
95+-- It generates all meaningfull variations of Item, and also tests
96+-- writing of payload of different Aeson constructors
97+--
98+-- Note: currently Handle scribe does not write Array items at all
99+-------------------------------------------------------------------------------
100+data AllTypesLogItem = AllTypesLogItem
101+ { atlText :: Text
102+ , atlNum :: Int
103+ , atlFloat :: Float
104+ , atlList :: [Text]
105+ , atlSub :: Maybe DummyLogItem
106+ }
107+
108+
109+instance ToJSON AllTypesLogItem where
110+ toJSON it = object
111+ [ "text" .= atlText it
112+ , "num" .= atlNum it
113+ , "float" .= atlFloat it
114+ , "list" .= atlList it
115+ , "sub" .= atlSub it
116+ ]
117+
118+
119+instance ToObject AllTypesLogItem
120+
121+
122+instance LogItem AllTypesLogItem where
123+ payloadKeys _ _ = AllKeys
124+
125+
126+-------------------------------------------------------------------------------
127+theItem :: Item DummyLogItem
128+theItem = Item (Namespace ["app"])
129+ (Environment "production")
130+ (InfoS)
131+ (ThreadIdText "1337")
132+ "example"
133+ 7331
134+ dummyLogItem
135+ "message"
136+ (mkUTCTime 2016 6 12 12 34 56)
137+ (Namespace ["foo"])
138+ Nothing
139+
140+genItems :: [Item DummyLogItem]
141+genItems = concat $
142+ [ [ itemSeverity .~ s $ theItem
143+ | s <- [minBound .. maxBound]
144+ ]
145+ , [ itemThread .~ (ThreadIdText . T.pack $ show t) $ theItem
146+ | t <- [0, 1, 1337, 2147483647]
147+ ]
148+ , [ itemHost .~ h $ theItem
149+ | h <- ["example", "www.example.com", "127.0.0.1"]
150+ ]
151+ , [ itemProcess .~ p $ theItem
152+ | p <- [0, 1, 1337, 2147483647]
153+ ]
154+ , [ itemMessage .~ LogStr m $ theItem
155+ | m <- [ "message"
156+ , "message\nwith newline"
157+ , LT.fromLazyText (LT.replicate 40 " a really long message")
158+ , "сообщение"
159+ , "哈囉世界"
160+ ]
161+ ]
162+ , [ itemTime .~ t $ theItem
163+ | t <- genDates
164+ ]
165+ , [ itemNamespace .~ Namespace ns $ theItem
166+ | ns <- [ ["foo"]
167+ , ["foo", "bar"]
168+ , ["фу", "бар"]
169+ , ["with\nnewline"] ]
170+ ]
171+ , [ itemLoc .~ l $ theItem | l <- genLocs
172+ ]
173+ ]
174+
175+genDates :: [UTCTime]
176+genDates =
177+ [ mkUTCTime 2000 1 1 0 0 0.0
178+ , mkUTCTime 2123 12 31 23 59 59.999999999999
179+ , mkUTCTime 2016 10 10 1 1 5.0
180+ , mkUTCTime 2100 12 31 12 59 10.1
181+ , mkUTCTime 1982 1 1 12 30 0.000000000001
182+ ]
183+
184+genLocs :: [Maybe Loc]
185+genLocs =
186+ [ Nothing
187+ , Just $ Loc "path/Some/Module.hs"
188+ "main"
189+ "Some.Module"
190+ (30,1) (30,14)
191+ , Just $ Loc "путь/Some/Module.hs"
192+ "main"
193+ "Some.Module"
194+ (3000,9000) (4000,1)
195+ ]
196+
197+genTypedItems :: [Item AllTypesLogItem]
198+genTypedItems =
199+ [ itemPayload .~ p $ theItem
200+ | p <- [ AllTypesLogItem "" 0 0.0 [] Nothing
201+ , AllTypesLogItem "note" 10 5.5 ["one", "two", "three"]
202+ (Just dummyLogItem)
203+ ]
204+ ]
205+
206+mkUTCTime :: Integer -> Int -> Int -> DiffTime -> DiffTime -> DiffTime -> UTCTime
207+mkUTCTime y mt d h mn s = UTCTime day dt
208+ where
209+ day = fromGregorian y mt d
210+ dt = h * 60 * 60 + mn * 60 + s
211+
212+-------------------------------------------------------------------------------
213+writeTextLog :: (FilePath, Handle) -> IO (BL.ByteString)
214+writeTextLog (path, h) = do
215+ mapM_ (T.hPutStrLn h . formatOne) genItems
216+ mapM_ (T.hPutStrLn h . formatOne) genTypedItems
217+ hClose h
218+ BL.readFile path
219+ where
220+ formatOne :: LogItem a => Item a -> Text
221+ formatOne = LT.toStrict . LT.toLazyText . formatItem False V3
222+
223+setupTempFile :: IO (FilePath, Handle)
224+setupTempFile = do
225+ tempDir <- getTemporaryDirectory
226+ (fp, h) <- openBinaryTempFile tempDir "katip.log"
227+ return (fp, h)
228+
229+teardownTempFile :: (FilePath, Handle) -> IO ()
230+teardownTempFile (_, h) = do
231+ chk <- hIsOpen h
232+ when chk $ hClose h
katip/test/Main.hs+2
@@ -4,6 +4,7 @@ module Main (main) where
44import Test.Tasty
55-------------------------------------------------------------------------------
66import qualified Katip.Tests
7+import qualified Katip.Tests.Format.Time
78import qualified Katip.Tests.Scribes.Handle
89-------------------------------------------------------------------------------
910
@@ -15,5 +16,6 @@ testSuite :: TestTree
1516testSuite = testGroup "katip"
1617 [
1718 Katip.Tests.tests
19+ , Katip.Tests.Format.Time.tests
1820 , Katip.Tests.Scribes.Handle.tests
1921 ]
stack-lts-2.yamlnew file+32
@@ -0,0 +1,32 @@
1+resolver: lts-2.22
2+packages:
3+ - katip
4+ - katip-elasticsearch
5+extra-deps:
6+ - bloodhound-0.11.0.0
7+ - string-conv-0.1
8+ - aeson-0.11.1.0
9+ - criterion-1.1.0.0
10+ - attoparsec-0.13.0.1
11+ - rng-utils-0.2.1
12+ - retry-0.7.4
13+ - tasty-0.11.0.2
14+ - tagged-0.8.3
15+ - fail-4.9.0.0
16+ - uuid-1.3.12
17+ # just for 7.8/lts-2
18+ - microlens-th-0.3.0.1
19+ - microlens-0.4.2.0
20+ - time-1.5.0.1
21+ - lens-aeson-1.0.0.5
22+ - time-locale-compat-0.1.1.1
23+ - cryptonite-0.16
24+ - memory-0.13
25+
26+flags:
27+ katip:
28+ lib-Werror: true
29+ katip-elasticsearch:
30+ lib-Werror: true
31+ time-locale-compat:
32+ old-locale: false
stack-nightly.yamlnew file+23
@@ -0,0 +1,23 @@
1+resolver: nightly-2016-06-01
2+packages:
3+ - katip
4+ - katip-elasticsearch
5+extra-deps:
6+ - bloodhound-0.11.0.0
7+ - string-conv-0.1
8+ - aeson-0.11.1.0
9+ - attoparsec-0.13.0.1
10+ - rng-utils-0.2.1
11+ - retry-0.7.4
12+ - tasty-0.11.0.2
13+ - tagged-0.8.3
14+ - fail-4.9.0.0
15+ - regex-tdfa-rc-1.1.8.3
16+ - turtle-1.2.8
17+ - managed-1.0.5
18+ - optional-args-1.0.1
19+flags:
20+ katip:
21+ lib-Werror: true
22+ katip-elasticsearch:
23+ lib-Werror: true
stack.yaml+4−2
@@ -6,13 +6,15 @@ extra-deps:
66 - bloodhound-0.11.0.0
77 - string-conv-0.1
88 - aeson-0.11.1.0
9− - criterion-1.1.0.0
9+ - criterion-1.1.1.0
1010 - attoparsec-0.13.0.1
1111 - rng-utils-0.2.1
12− - retry-0.7.1
12+ - retry-0.7.4
1313 - tasty-0.11.0.2
1414 - tagged-0.8.3
1515 - fail-4.9.0.0
16+ - uuid-1.3.12
17+ - regex-tdfa-rc-1.1.8.3
1618flags:
1719 katip:
1820 lib-Werror: true