packages feed

ekg-log 0.1.0.2 → 0.1.0.3

raw patch · 3 files changed

+181/−192 lines, 3 filesdep +directorydep +unixPVP ok

version bump matches the API change (PVP)

Dependencies added: directory, unix

API changes (from Hackage documentation)

Files

− System/Local/Monitoring/Log.hs
@@ -1,191 +0,0 @@-{-# LANGUAGE BangPatterns        #-}-{-# LANGUAGE CPP                 #-}-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE RecordWildCards     #-}-{-# LANGUAGE ScopedTypeVariables #-}---- | This module lets you periodically flush metrics to a log file--- that automatically rotates itself.------ > main = do--- >     store <- newStore--- >     forkEkgLog defaultLogOptions store------ You probably want to include some of the predefined metrics defined--- in the ekg-core package, by calling e.g. the 'registerGcStats'--- function defined in that package.-module System.Local.Monitoring.Log-    (-      -- * The statsd syncer-      Log-    , logThreadId-    , forkEkgLog-    , LogOptions(..)-    , defaultLogOptions-    ) where--import           Control.Concurrent    (ThreadId, myThreadId, threadDelay,-                                        throwTo)-import           Control.Exception     ()-import           Control.Monad         (forM_, when)-import           Data.Aeson            as A-import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Lazy  as L-import qualified Data.HashMap.Strict   as M-import           Data.Int              (Int64)-import           Data.Monoid           ((<>))-import qualified Data.Text             as T-import qualified Data.Text.IO          ()-import           Data.Time-import           Data.Time.Clock.POSIX (getPOSIXTime)-import qualified System.FilePath       as FP-import           System.IO             (stderr)-import           System.Log.FastLogger-import qualified System.Metrics        as Metrics--#if __GLASGOW_HASKELL__ >= 706-import           Control.Concurrent    (forkFinally)-#else-import           Control.Concurrent    (forkIO)-import           Control.Exception     (SomeException, mask, try)-import           Prelude               hiding (catch)-#endif---- | A handle that can be used to control the log sync thread.--- Created by 'forkEkgLog'.-data Log = Log-    { threadId :: {-# UNPACK #-} !ThreadId-    }---- | The thread ID of the statsd sync thread. You can stop the sync by--- killing this thread (i.e. by throwing it an asynchronous--- exception.)-logThreadId :: Log -> ThreadId-logThreadId = threadId---- | Options to control whate log file to append to, when to rotate,--- and how often to flush metrics. The flush interval should be--- shorter than the flush interval.-data LogOptions = LogOptions-    { -- | Server hostname or IP address-      logfile       :: !FP.FilePath--      -- | Data push interval, in ms.-    , flushInterval :: !Int--      -- | Print debug output to stderr.-    , debug         :: !Bool--      -- | Prefix to add to all metric names.-    , prefix        :: !T.Text--      -- | Suffix to add to all metric names. This is particularly-      -- useful for sending per application stats.-    , suffix        :: !T.Text-    }---- | Defaults:------ * @logfile@ = @\"./ekg.log\"@------ * @flushInterval@ = @5000@------ * @debug@ = @False@-defaultLogOptions :: LogOptions-defaultLogOptions = LogOptions-    { logfile       = "./ekg.log"-    , flushInterval = 5000-    , debug         = False-    , prefix        = ""-    , suffix        = ""-    }---- | Create a thread that periodically flushes the metrics in the--- store to a log file.-forkEkgLog :: LogOptions    -- ^ Options-           -> Metrics.Store -- ^ Metric store-           -> IO Log        -- ^ Statsd sync handle-forkEkgLog opts store = do-    logset <- newFileLoggerSet defaultBufSize (logfile opts)-    me     <- myThreadId-    tid    <- forkFinally (loop store emptySample logset opts) $ \ r -> do-        rmLoggerSet logset-        case r of-            Left e  -> throwTo me e-            Right _ -> return ()-    return $ Log tid-  where-    emptySample = M.empty--loop :: Metrics.Store   -- ^ Metric store-     -> Metrics.Sample  -- ^ Last sampled metrics-     -> LoggerSet       -- ^ FastLogger set-     -> LogOptions      -- ^ Options-     -> IO ()-loop store lastSample logset opts = do-    start <- time-    sample <- Metrics.sampleAll store-    let !diff = diffSamples lastSample sample-    flushSample diff logset opts-    end <- time-    threadDelay (flushInterval opts * 1000 - fromIntegral (end - start))-    loop store sample logset opts---- | Microseconds since epoch.-time :: IO Int64-time = (round . (* 1000000.0) . toDouble) `fmap` getPOSIXTime-  where toDouble = realToFrac :: Real a => a -> Double--diffSamples :: Metrics.Sample -> Metrics.Sample -> Metrics.Sample-diffSamples prev curr = M.foldlWithKey' combine M.empty curr-  where-    combine m name new = case M.lookup name prev of-        Just old -> case diffMetric old new of-            Just val -> M.insert name val m-            Nothing  -> m-        _        -> M.insert name new m--    diffMetric :: Metrics.Value -> Metrics.Value -> Maybe Metrics.Value-    diffMetric (Metrics.Counter n1) (Metrics.Counter n2)-        | n1 == n2  = Nothing-        | otherwise = Just $! Metrics.Counter $ n2 - n1-    diffMetric (Metrics.Gauge n1) (Metrics.Gauge n2)-        | n1 == n2  = Nothing-        | otherwise = Just $ Metrics.Gauge n2-    diffMetric (Metrics.Label n1) (Metrics.Label n2)-        | n1 == n2  = Nothing-        | otherwise = Just $ Metrics.Label n2-    -- Distributions are assumed to be non-equal.-    diffMetric _ _  = Nothing--flushSample :: Metrics.Sample -> LoggerSet -> LogOptions -> IO ()-flushSample sample logset opts = do-    forM_ (M.toList sample) $ \(name, val) -> do-        time' <- getCurrentTime-        let newName = dottedPrefix <> name <> dottedSuffix-            newObj  = case val of-                (Metrics.Counter v)      -> object [ "timestamp" .= time', newName .= show v ]-                (Metrics.Gauge   v)      -> object [ "timestamp" .= time', newName .= show v ]-                (Metrics.Label   v)      -> object [ "timestamp" .= time', newName .= show v ]-                (Metrics.Distribution v) -> object [ "timestamp" .= time', newName .= show v ]-        flushMetric newObj-  where-    isDebug = debug opts--    dottedPrefix = if T.null (prefix opts) then "" else prefix opts <> "."-    dottedSuffix = if T.null (suffix opts) then "" else "." <> suffix opts--    flushMetric metricObj = do-        let !msg = L.toStrict $ A.encode metricObj-        when isDebug $ B8.hPutStrLn stderr $ B8.concat [ "DEBUG: ", msg]-        pushLogStr logset . toLogStr $ B8.append "\n" msg----------------------------------------------------------------------------- Backwards compatibility shims--#if __GLASGOW_HASKELL__ < 706-forkFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId-forkFinally action and_then =-  mask $ \restore ->-    forkIO $ try (restore action) >>= and_then-#endif
ekg-log.cabal view
@@ -1,5 +1,5 @@ name:                ekg-log-version:             0.1.0.2+version:             0.1.0.3 synopsis:            Push metrics to a log file. description:   This library lets you push system metrics to a log file.@@ -14,6 +14,7 @@ cabal-version:       >=1.10  library+  HS-Source-Dirs:       src   exposed-modules:          System.Local.Monitoring.Log @@ -21,9 +22,11 @@     base                      >= 4.4     && < 5,     aeson                     >= 0.7     && < 0.9,     bytestring                              < 1.0,+    directory                 >= 1.0     && < 2.0,     ekg-core                  >= 0.1     && < 1.0,     text                                    < 1.3,     fast-logger               >= 2.0     && < 3.0,+    unix                      >= 2.0     && < 3.0,     filepath                  >= 1.3     && < 1.4,     time                                    < 1.5,     unordered-containers                    < 0.3
+ src/System/Local/Monitoring/Log.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE BangPatterns        #-}+{-# LANGUAGE CPP                 #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | This module lets you periodically flush metrics to a log file+-- that automatically rotates itself.+--+-- > main = do+-- >     store <- newStore+-- >     forkEkgLog defaultLogOptions store+--+-- You probably want to include some of the predefined metrics defined+-- in the ekg-core package, by calling e.g. the 'registerGcStats'+-- function defined in that package.+module System.Local.Monitoring.Log+    (+      -- * The log syncer+      Log+    , logThreadId+    , forkEkgLog+    , LogOptions(..)+    , defaultLogOptions+    ) where++import           Control.Concurrent    (ThreadId, myThreadId, threadDelay,+                                        throwTo)+import           Control.Exception     ()+import           Control.Monad         (forM_, when)+import           Data.Aeson            as A+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy  as L+import qualified Data.HashMap.Strict   as M+import           Data.Int              (Int64)+import           Data.Monoid           ((<>))+import qualified Data.Text             as T+import qualified Data.Text.IO          ()+import           Data.Time+import           Data.Time.Clock.POSIX (getPOSIXTime)+import qualified System.FilePath       as FP+import           System.IO             (stderr)+import           System.Log.FastLogger+import qualified System.Metrics        as Metrics+import           System.Posix.Files    (fileSize, getFileStatus)++#if __GLASGOW_HASKELL__ >= 706+import           Control.Concurrent    (forkFinally)+#else+import           Control.Concurrent    (forkIO)+import           Control.Exception     (SomeException, mask, try)+import           Prelude               hiding (catch)+#endif++-- | A handle that can be used to control the log sync thread.+-- Created by 'forkEkgLog'.+data Log = Log+    { threadId :: {-# UNPACK #-} !ThreadId+    }++-- | The thread ID of the log sync thread. You can stop the sync by+-- killing this thread (i.e. by throwing it an asynchronous+-- exception.)+logThreadId :: Log -> ThreadId+logThreadId = threadId++-- | Options to control whate log file to append to, when to rotate,+-- and how often to flush metrics. The flush interval should be+-- shorter than the flush interval.+data LogOptions = LogOptions+    { -- | Server hostname or IP address+      logfile       :: !FP.FilePath++      -- | Data push interval, in ms.+    , flushInterval :: !Int++      -- | Print debug output to stderr.+    , debug         :: !Bool++      -- | Prefix to add to all metric names.+    , prefix        :: !T.Text++      -- | Suffix to add to all metric names. This is particularly+      -- useful for sending per application stats.+    , suffix        :: !T.Text+    }++-- | Defaults:+--+-- * @logfile@ = @\"./ekg.log\"@+--+-- * @flushInterval@ = @5000@+--+-- * @debug@ = @False@+defaultLogOptions :: LogOptions+defaultLogOptions = LogOptions+    { logfile       = "./ekg.log"+    , flushInterval = 10000+    , debug         = False+    , prefix        = ""+    , suffix        = ""+    }++-- | Create a thread that periodically flushes the metrics in the+-- store to a log file.+forkEkgLog :: LogOptions    -- ^ Options+           -> Metrics.Store -- ^ Metric store+           -> IO Log        -- ^ Log sync handle+forkEkgLog opts store = do+    logset <- newFileLoggerSet defaultBufSize (logfile opts)+    me     <- myThreadId+    tid    <- forkFinally (loop store logset opts) $ \ r -> do+        case r of+            Left e  -> throwTo me e+            Right _ -> return ()+    return $ Log tid++loop :: Metrics.Store   -- ^ Metric store+     -> LoggerSet       -- ^ FastLogger set+     -> LogOptions      -- ^ Options+     -> IO ()+loop store logset opts = do+    start <- time+    sample <- Metrics.sampleAll store++    logstat <- getFileStatus (logfile opts)+    let logsize = fileSize logstat++    rmLoggerSet logset+    logset' <- newFileLoggerSet defaultBufSize (logfile opts)++    flushOnSize logsize sample logset'++    end <- time+    threadDelay (flushInterval opts * 1000 - fromIntegral (end - start))+    loop store logset' opts++  where+    flushOnSize sz diff logset' | sz == 0   = flushSample diff logset' opts+                                | otherwise = return ()++-- | Microseconds since epoch.+time :: IO Int64+time = (round . (* 1000000.0) . toDouble) `fmap` getPOSIXTime+  where toDouble = realToFrac :: Real a => a -> Double++flushSample :: Metrics.Sample -> LoggerSet -> LogOptions -> IO ()+flushSample sample logset opts = do+    forM_ (M.toList sample) $ \(name, val) -> do+        time' <- getCurrentTime+        let newName = dottedPrefix <> name <> dottedSuffix+            newObj  = case val of+                (Metrics.Counter v)      -> object [ "timestamp" .= time', newName .= show v ]+                (Metrics.Gauge   v)      -> object [ "timestamp" .= time', newName .= show v ]+                (Metrics.Label   v)      -> object [ "timestamp" .= time', newName .= show v ]+                (Metrics.Distribution v) -> object [ "timestamp" .= time', newName .= show v ]+        flushMetric newObj+  where+    isDebug = debug opts++    dottedPrefix = if T.null (prefix opts) then "" else prefix opts <> "."+    dottedSuffix = if T.null (suffix opts) then "" else "." <> suffix opts++    flushMetric metricObj = do+        let !msg = L.toStrict $ A.encode metricObj+        when isDebug $ B8.hPutStrLn stderr $ B8.concat [ "DEBUG: ", msg]+        pushLogStr logset . toLogStr $ B8.append "\n" msg++------------------------------------------------------------------------+-- Backwards compatibility shims++#if __GLASGOW_HASKELL__ < 706+forkFinally :: IO a -> (Either SomeException a -> IO ()) -> IO ThreadId+forkFinally action and_then =+  mask $ \restore ->+    forkIO $ try (restore action) >>= and_then+#endif