packages feed

katip 0.3.0.0 → 0.3.1.0

raw patch · 8 files changed

+399/−11 lines, 8 filesdep +tasty-goldendep ~aesondep ~bytestringdep ~microlensPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: tasty-golden

Dependency ranges changed: aeson, bytestring, microlens

API changes (from Hackage documentation)

+ Katip.Format.Time: formatAsIso8601 :: UTCTime -> Text
+ Katip.Format.Time: formatAsLogTime :: UTCTime -> Text
- Katip: itemApp :: Lens' (Item a_agn7) Namespace
+ Katip: itemApp :: Lens' (Item a_am8C) Namespace
- Katip: itemEnv :: Lens' (Item a_agn7) Environment
+ Katip: itemEnv :: Lens' (Item a_am8C) Environment
- Katip: itemHost :: Lens' (Item a_agn7) HostName
+ Katip: itemHost :: Lens' (Item a_am8C) HostName
- Katip: itemLoc :: Lens' (Item a_agn7) (Maybe Loc)
+ Katip: itemLoc :: Lens' (Item a_am8C) (Maybe Loc)
- Katip: itemMessage :: Lens' (Item a_agn7) LogStr
+ Katip: itemMessage :: Lens' (Item a_am8C) LogStr
- Katip: itemNamespace :: Lens' (Item a_agn7) Namespace
+ Katip: itemNamespace :: Lens' (Item a_am8C) Namespace
- Katip: itemPayload :: Lens (Item a_agn7) (Item a_amOE) a_agn7 a_amOE
+ Katip: itemPayload :: Lens (Item a_am8C) (Item a_arBi) a_am8C a_arBi
- Katip: itemProcess :: Lens' (Item a_agn7) ProcessID
+ Katip: itemProcess :: Lens' (Item a_am8C) ProcessID
- Katip: itemSeverity :: Lens' (Item a_agn7) Severity
+ Katip: itemSeverity :: Lens' (Item a_am8C) Severity
- Katip: itemThread :: Lens' (Item a_agn7) ThreadIdText
+ Katip: itemThread :: Lens' (Item a_am8C) ThreadIdText
- Katip: itemTime :: Lens' (Item a_agn7) UTCTime
+ Katip: itemTime :: Lens' (Item a_am8C) UTCTime
- Katip.Core: itemApp :: Lens' (Item a_agn7) Namespace
+ Katip.Core: itemApp :: Lens' (Item a_am8C) Namespace
- Katip.Core: itemEnv :: Lens' (Item a_agn7) Environment
+ Katip.Core: itemEnv :: Lens' (Item a_am8C) Environment
- Katip.Core: itemHost :: Lens' (Item a_agn7) HostName
+ Katip.Core: itemHost :: Lens' (Item a_am8C) HostName
- Katip.Core: itemLoc :: Lens' (Item a_agn7) (Maybe Loc)
+ Katip.Core: itemLoc :: Lens' (Item a_am8C) (Maybe Loc)
- Katip.Core: itemMessage :: Lens' (Item a_agn7) LogStr
+ Katip.Core: itemMessage :: Lens' (Item a_am8C) LogStr
- Katip.Core: itemNamespace :: Lens' (Item a_agn7) Namespace
+ Katip.Core: itemNamespace :: Lens' (Item a_am8C) Namespace
- Katip.Core: itemPayload :: Lens (Item a_agn7) (Item a_amOE) a_agn7 a_amOE
+ Katip.Core: itemPayload :: Lens (Item a_am8C) (Item a_arBi) a_am8C a_arBi
- Katip.Core: itemProcess :: Lens' (Item a_agn7) ProcessID
+ Katip.Core: itemProcess :: Lens' (Item a_am8C) ProcessID
- Katip.Core: itemSeverity :: Lens' (Item a_agn7) Severity
+ Katip.Core: itemSeverity :: Lens' (Item a_am8C) Severity
- Katip.Core: itemThread :: Lens' (Item a_agn7) ThreadIdText
+ Katip.Core: itemThread :: Lens' (Item a_am8C) ThreadIdText
- Katip.Core: itemTime :: Lens' (Item a_agn7) UTCTime
+ Katip.Core: itemTime :: Lens' (Item a_am8C) UTCTime

Files

README.md view
@@ -81,3 +81,4 @@ * [Ozgun Ataman](https://github.com/ozataman) * [Michael Xavier](https://github.com/MichaelXavier) * [Doug Beardsley](https://github.com/mightybyte)+* [Leonid Onokhov](https://github.com/sopvop)
changelog.md view
@@ -1,3 +1,8 @@+0.3.1.0+=======+* Add support for aeson 1.0.x+* Add Katip.Format.Time module and use much more efficient time formatting code in the Handle scribe.+ 0.3.0.0 ======= * Switch from `regex-tdfa-rc` to `regex-tdfa`.
katip.cabal view
@@ -1,5 +1,5 @@ name:                katip-version:             0.3.0.0+version:             0.3.1.0 synopsis:            A structured logging framework. description:   Katip is a structured logging framework. See README.md for more details.@@ -37,6 +37,7 @@   exposed-modules:     Katip     Katip.Core+    Katip.Format.Time     Katip.Monadic     Katip.Scribes.Handle @@ -51,7 +52,7 @@     OverloadedStrings    build-depends: base >=4.5 && <5-               , aeson >=0.6 && <0.12+               , aeson >=0.6 && <1.1                , auto-update >= 0.1 && < 0.2                , bytestring >= 0.9 && < 0.11                , containers >=0.4 && <0.6@@ -63,7 +64,6 @@                , template-haskell >= 2.8 && < 2.12                , text >= 0.11 && <1.3                , time >= 1 && < 1.7-               , time-locale-compat >= 0.1.0.1 && < 0.2                , transformers >= 0.3 && < 0.6                , transformers-compat                , unix >= 2.5 && < 2.8@@ -90,18 +90,21 @@   build-depends: base                , katip                , aeson+               , bytestring                , tasty >= 0.10.1.2+               , tasty-golden                , tasty-hunit                , tasty-quickcheck                , quickcheck-instances                , template-haskell                , text                , time+               , time-locale-compat >= 0.1.0.1 && < 0.2                , temporary                , directory                , regex-tdfa                , unordered-containers-+               , microlens  benchmark bench   type: exitcode-stdio-1.0
+ src/Katip/Format/Time.hs view
@@ -0,0 +1,213 @@+-- | Time and memory efficient time encoding helper functions.+--+module Katip.Format.Time+    ( formatAsLogTime+    , formatAsIso8601+    ) where++import           Control.Monad.ST        (ST)++import           Data.Int                (Int64)+import qualified Data.Text.Array         as TA+import           Data.Text               (Text)+import           Data.Text.Internal      (Text(..))+import           Data.Time               (UTCTime(..), toGregorian, Day, DiffTime)+import           Data.Word               (Word16)+import           Unsafe.Coerce           (unsafeCoerce)++-- Note: All functions here are optimized to never allocate anything+-- on heap. At least on ghc 8.0.1 no extra strictness annotations are+-- seem to be needed.+--+-- Exported functions are INLINEABLE+++-- | Format 'UTCTime' into a short human readable format.+--+-- >>> formatAsLogTime $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012+-- "2016-01-23 01:23:45"+--+formatAsLogTime :: UTCTime -> Text+formatAsLogTime (UTCTime day time) = toText $ TA.run2 $ do+     buf <- TA.new 19 -- length "2016-10-20 12:34:56"+     _ <- writeDay buf 0 day+     TA.unsafeWrite buf 10 0x20 -- space+     _ <- writeTimeOfDay False buf 11 (diffTimeOfDay64 time)+     return (buf, 19)+  where+     toText (arr, len) = Text arr 0 len++{-# INLINEABLE formatAsLogTime #-}++-- | Format 'UTCTime' into a Iso8601 format.+--+--  Note that this function may overcommit up to 12*2 bytes, depending+--  on sub-second precision. If this is an issue, make a copy with a+--  'Data.Text.copy'.+--+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123456789012+-- "2016-11-23T01:23:45.123456789012Z"+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025.123+-- "2016-01-23T01:23:45.123Z"+-- >>> formatAsIso8601 $ UTCTime (fromGregorian 2016 1 23) 5025+-- "2016-01-23T01:23:45Z"++--+formatAsIso8601 :: UTCTime -> Text+formatAsIso8601 (UTCTime day time) = toText $ TA.run2 $ do+   buf <- TA.new 33 -- length "2016-10-20 12:34:56.123456789012Z"+   _ <- writeDay buf 0 day+   TA.unsafeWrite buf  10  0x54 -- T+   next <- writeTimeOfDay True buf 11 (diffTimeOfDay64 time)+   TA.unsafeWrite buf next 0x5A -- Z+   return (buf, next+1)+  where+     toText (arr, len) = Text arr 0 len++{-# INLINEABLE formatAsIso8601 #-}++-- | Writes the @YYYY-MM-DD@ part of timestamp+writeDay :: TA.MArray s -> Int -> Day -> ST s Int+writeDay buf off day =+  do+    TA.unsafeWrite buf (off + 0) (digit y1)+    TA.unsafeWrite buf (off + 1) (digit y2)+    TA.unsafeWrite buf (off + 2) (digit y3)+    TA.unsafeWrite buf (off + 3) (digit y4)+    TA.unsafeWrite buf (off + 4) 0x2d -- dash+    TA.unsafeWrite buf (off + 5) m1+    TA.unsafeWrite buf (off + 6) m2+    TA.unsafeWrite buf (off + 7) 0x2d -- dash+    TA.unsafeWrite buf (off + 8) d1+    TA.unsafeWrite buf (off + 9) d2+    return (off + 10)+  where+    (yr,m,d) = toGregorian day+    (y1, ya) = fromIntegral (abs yr) `quotRem` 1000+    (y2, yb) = ya `quotRem` 100+    (y3, y4) = yb `quotRem` 10+    T m1 m2  = twoDigits m+    T d1 d2  = twoDigits d+{-# INLINE writeDay #-}++-- | Write time of day, optionally with sub seconds+writeTimeOfDay :: Bool -> TA.MArray s -> Int -> TimeOfDay64 -> ST s Int+writeTimeOfDay doSubSeconds buf off (TOD hh mm ss) =+  do++    TA.unsafeWrite buf  off      h1+    TA.unsafeWrite buf (off + 1) h2+    TA.unsafeWrite buf (off + 2) 0x3A -- colon+    TA.unsafeWrite buf (off + 3) m1+    TA.unsafeWrite buf (off + 4) m2+    TA.unsafeWrite buf (off + 5) 0x3A -- colon+    TA.unsafeWrite buf (off + 6) s1+    TA.unsafeWrite buf (off + 7) s2+    if doSubSeconds && frac /= 0+    then writeFracSeconds buf (off + 8) frac+    else return (off + 8)+  where+   T h1 h2 = twoDigits hh+   T m1 m2 = twoDigits mm+   T s1 s2 = twoDigits (fromIntegral real)+   (real,frac) = ss `quotRem` pico+   pico       = 1000000000000 -- number of picoseconds  in 1 second+++writeFracSeconds :: TA.MArray s -> Int -> Int64 -> ST s Int+writeFracSeconds buf off frac =+  do+    TA.unsafeWrite buf off 0x2e -- period+    if mills == 0+    then do+      writeTrunc6 buf (off + 1) (fromIntegral mics)+    else do+      writeDigit6 buf (off + 1) (fromIntegral mics)+      writeTrunc6 buf (off + 7) (fromIntegral mills)++  where+    (mics, mills)  = frac `quotRem` micro+    micro          = 1000000 -- number of microseconds in 1 second+++writeDigit6 :: TA.MArray s -> Int -> Int -> ST s ()+writeDigit6 buf off i =+  do+    writeDigit3 buf off f1+    writeDigit3 buf (off+3) f2+  where+   (f1, f2) = i `quotRem` 1000++{-# INLINE writeDigit6 #-}++writeDigit3 :: TA.MArray s -> Int -> Int -> ST s ()+writeDigit3 buf off i =+  do+    TA.unsafeWrite buf off (digit d1)+    TA.unsafeWrite buf (off+1) (digit d2)+    TA.unsafeWrite buf (off+2) (digit d3)+  where+    (d1, d) = i `quotRem` 100+    (d2, d3) = d `quotRem` 10++{-# INLINE writeDigit3 #-}++writeTrunc6 :: TA.MArray s -> Int -> Int -> ST s Int+writeTrunc6 buf off i =+  if f2 == 0+    then writeTrunc3 buf off f1+    else do+      writeDigit3 buf off f1+      writeTrunc3 buf (off+3) f2+  where+   (f1, f2) = i `quotRem` 1000++{-# INLINE writeTrunc6 #-}+++writeTrunc3 :: TA.MArray s -> Int -> Int -> ST s Int+writeTrunc3 buf off i+    | d == 0 = do+        TA.unsafeWrite buf off (digit d1)+        return (off+1)+    | d3 == 0 = do+        TA.unsafeWrite buf off (digit d1)+        TA.unsafeWrite buf (off+1) (digit d2)+        return (off+2)++    | otherwise = do+        TA.unsafeWrite buf off (digit d1)+        TA.unsafeWrite buf (off+1) (digit d2)+        TA.unsafeWrite buf (off+2) (digit d3)+        return (off+3)+  where+    (d1, d) = i `quotRem` 100+    (d2, d3) = d `quotRem` 10++{-# INLINE writeTrunc3 #-}+++-- Following code was adapted from aeson package.+--+-- Copyright:   (c) 2015-2016 Bryan O'Sullivan+-- License:     BSD3++data T = T {-# UNPACK #-} !Word16 {-# UNPACK #-} !Word16++twoDigits :: Int -> T+twoDigits a     = T (digit hi) (digit lo)+  where (hi,lo) = a `quotRem` 10++digit :: Int -> Word16+digit x = fromIntegral (x + 48)+++data TimeOfDay64 = TOD {-# UNPACK #-} !Int+                       {-# UNPACK #-} !Int+                       {-# UNPACK #-} !Int64++diffTimeOfDay64 :: DiffTime -> TimeOfDay64+diffTimeOfDay64 t = TOD (fromIntegral h) (fromIntegral m) s+  where (h,mp) = fromIntegral pico `quotRem` 3600000000000000+        (m,s)  = mp `quotRem` 60000000000000+        pico   = unsafeCoerce t :: Integer
src/Katip/Monadic.hs view
@@ -315,7 +315,8 @@  ------------------------------------------------------------------------------- -- | Append a namespace segment to the current namespace for the given--- monadic action, then restore the previous state afterwards.+-- monadic action, then restore the previous state+-- afterwards. katipAddNamespace     :: (Monad m)     => Namespace@@ -327,7 +328,15 @@  ------------------------------------------------------------------------------- -- | Append some context to the current context for the given monadic--- action, then restore the previous state afterwards.+-- action, then restore the previous state afterwards. Important note:+-- be careful using this in a loop. If you're using something like+-- 'forever' or 'replicateM_' that does explicit sharing to avoid a+-- memory leak, youll be fine as it will *sequence* calls to+-- 'katipAddNamespace', so each loop will get the same context+-- added. If you instead roll your own recursion and you're recursing+-- in the action you provide, you'll instead accumulate tons of+-- redundant contexts and even if they all merge on log, they are+-- stored in a sequence and will leak memory. katipAddContext     :: ( LogItem i        , Monad m
src/Katip/Scribes/Handle.hs view
@@ -13,12 +13,11 @@ import           Data.Text               (Text) import           Data.Text.Lazy.Builder import           Data.Text.Lazy.IO       as T-import           Data.Time-import qualified Data.Time.Locale.Compat as LC import           System.IO import           System.IO.Unsafe        (unsafePerformIO) ------------------------------------------------------------------------------- import           Katip.Core+import           Katip.Format.Time       (formatAsLogTime) -------------------------------------------------------------------------------  @@ -83,7 +82,7 @@     maybe mempty (brackets . fromString . locationToString) _itemLoc <>     fromText " " <> (unLogStr _itemMessage)   where-    nowStr = fromString $ formatTime LC.defaultTimeLocale "%Y-%m-%d %H:%M:%S" _itemTime+    nowStr = fromText (formatAsLogTime _itemTime)     ks = map brackets $ getKeys verb _itemPayload     renderSeverity' s = case s of       EmergencyS -> red $ renderSeverity s
test/Katip/Tests/Scribes/Handle.hs view
@@ -6,17 +6,26 @@ ------------------------------------------------------------------------------- import           Control.Monad import           Data.Aeson+import qualified Data.ByteString.Lazy       as BL import           Data.Monoid-import           Data.Text        (Text)-import qualified Data.Text.IO     as T+import           Data.Text                  (Text)+import qualified Data.Text                  as T+import qualified Data.Text.IO               as T+import qualified Data.Text.Lazy             as LT+import qualified Data.Text.Lazy.Builder     as LT+import           Data.Time+import           Language.Haskell.TH.Syntax (Loc (..))+import           Lens.Micro                 ((.~)) import           System.Directory import           System.IO import           System.IO.Temp import           Test.Tasty+import           Test.Tasty.Golden import           Test.Tasty.HUnit import           Text.Regex.TDFA ------------------------------------------------------------------------------- import           Katip+import           Katip.Scribes.Handle -------------------------------------------------------------------------------  @@ -31,6 +40,10 @@        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        let matches = res =~ pat        assertBool (res <> " did not match") matches+  , withResource setupTempFile teardownTempFile $ \setup ->+       goldenVsString "Text-golden"+                      "test/Katip/Tests/Scribes/Handle-text.golden"+                      (setup >>= writeTextLog)   ]  @@ -74,3 +87,146 @@ teardown (_, h, _) = do   chk <- hIsOpen h   when chk $ hClose h++++-- Following code tests Handle scribe output against a golden file.+-- This test will fail on non utf8 locales because golden file is in utf-8.+-- It generates all meaningfull variations of Item, and also tests+-- writing of payload of different Aeson constructors+--+-- Note: currently Handle scribe does not write Array items at all+-------------------------------------------------------------------------------+data AllTypesLogItem = AllTypesLogItem+    { atlText  :: Text+    , atlNum   :: Int+    , atlFloat :: Float+    , atlList  :: [Text]+    , atlSub   :: Maybe DummyLogItem+    }+++instance ToJSON AllTypesLogItem where+  toJSON it = object+    [ "text"     .= atlText it+    , "num"      .= atlNum it+    , "float"    .= atlFloat it+    , "list"     .= atlList it+    , "sub"      .= atlSub it+    ]+++instance ToObject AllTypesLogItem+++instance LogItem AllTypesLogItem where+  payloadKeys _ _ = AllKeys+++-------------------------------------------------------------------------------+theItem :: Item DummyLogItem+theItem = Item (Namespace ["app"])+               (Environment "production")+               (InfoS)+               (ThreadIdText "1337")+               "example"+               7331+               dummyLogItem+               "message"+               (mkUTCTime 2016 6 12 12 34 56)+               (Namespace ["foo"])+               Nothing++genItems :: [Item DummyLogItem]+genItems = concat $+  [ [ itemSeverity .~ s $ theItem+          | s <- [minBound .. maxBound]+    ]+  , [ itemThread .~ (ThreadIdText . T.pack $ show t) $ theItem+          | t <- [0, 1, 1337, 2147483647]+    ]+  , [ itemHost .~ h $ theItem+          | h <- ["example", "www.example.com", "127.0.0.1"]+    ]+  , [ itemProcess .~ p $ theItem+          | p <- [0, 1, 1337, 2147483647]+    ]+  , [ itemMessage .~ LogStr m $ theItem+          | m <- [ "message"+                 , "message\nwith newline"+                 , LT.fromLazyText (LT.replicate 40 " a really long message")+                 , "сообщение"+                 , "哈囉世界"+                 ]+    ]+  , [ itemTime .~ t $ theItem+          | t <- genDates+    ]+  , [ itemNamespace .~ Namespace ns $ theItem+          | ns <- [ ["foo"]+                  , ["foo", "bar"]+                  , ["фу", "бар"]+                  , ["with\nnewline"] ]+    ]+  , [ itemLoc .~ l $ theItem | l <- genLocs+    ]+  ]++genDates :: [UTCTime]+genDates =+  [ mkUTCTime 2000 1   1 0   0  0.0+  , mkUTCTime 2123 12 31 23  59 59.999999999999+  , mkUTCTime 2016 10 10 1   1  5.0+  , mkUTCTime 2100 12 31 12  59 10.1+  , mkUTCTime 1982 1  1  12  30 0.000000000001+  ]++genLocs :: [Maybe Loc]+genLocs =+  [ Nothing+  , Just $ Loc "path/Some/Module.hs"+               "main"+               "Some.Module"+               (30,1) (30,14)+  , Just $ Loc "путь/Some/Module.hs"+               "main"+               "Some.Module"+               (3000,9000) (4000,1)+  ]++genTypedItems :: [Item AllTypesLogItem]+genTypedItems =+  [ itemPayload .~ p $ theItem+        | p <- [ AllTypesLogItem "" 0 0.0 [] Nothing+               , AllTypesLogItem "note" 10 5.5 ["one", "two", "three"]+                     (Just dummyLogItem)+               ]+  ]++mkUTCTime :: Integer -> Int -> Int -> DiffTime -> DiffTime -> DiffTime -> UTCTime+mkUTCTime y mt d h mn s = UTCTime day dt+  where+    day = fromGregorian y mt d+    dt = h * 60 * 60 + mn * 60 + s++-------------------------------------------------------------------------------+writeTextLog :: (FilePath, Handle) -> IO (BL.ByteString)+writeTextLog (path, h) = do+    mapM_ (T.hPutStrLn h . formatOne) genItems+    mapM_ (T.hPutStrLn h . formatOne) genTypedItems+    hClose h+    BL.readFile path+  where+    formatOne :: LogItem a => Item a -> Text+    formatOne = LT.toStrict . LT.toLazyText . formatItem False V3++setupTempFile :: IO (FilePath, Handle)+setupTempFile = do+    tempDir <- getTemporaryDirectory+    (fp, h) <- openBinaryTempFile tempDir "katip.log"+    return (fp, h)++teardownTempFile :: (FilePath, Handle) -> IO ()+teardownTempFile (_, h) = do+    chk <- hIsOpen h+    when chk $ hClose h
test/Main.hs view
@@ -4,6 +4,7 @@ import           Test.Tasty ------------------------------------------------------------------------------- import qualified Katip.Tests+import qualified Katip.Tests.Format.Time import qualified Katip.Tests.Scribes.Handle ------------------------------------------------------------------------------- @@ -15,5 +16,6 @@ testSuite = testGroup "katip"   [     Katip.Tests.tests+  , Katip.Tests.Format.Time.tests   , Katip.Tests.Scribes.Handle.tests   ]