packages feed

logsink (empty) → 0.1.0

raw patch · 7 files changed

+235/−0 lines, 7 filesdep +basedep +hspecdep +hsyslogsetup-changed

Dependencies added: base, hspec, hsyslog, logging-facade, logsink, time

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2015 Zalora South East Asia Pte. Ltd++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ logsink.cabal view
@@ -0,0 +1,47 @@+name:             logsink+version:          0.1.0+synopsis:         A logging framework for Haskell+description:      A logging framework for Haskell+license:          MIT+license-file:     LICENSE+copyright:        (c) Zalora South East Asia Pte. Ltd+maintainer:       Simon Hengel <sol@typeful.net>+build-type:       Simple+cabal-version:    >= 1.10+category:         System++source-repository head+  type: git+  location: https://github.com/zalora/logsink++library+  ghc-options:+      -Wall+  hs-source-dirs:+      src+  exposed-modules:+      System.Logging.LogSink.Config+      System.Logging.LogSink.Core+      System.Logging.LogSink.Format+  build-depends:+      base == 4.*+    , logging-facade+    , hsyslog+    , time+  default-language: Haskell2010++test-suite spec+  type:+      exitcode-stdio-1.0+  ghc-options:+      -Wall+  hs-source-dirs:+      test+  main-is:+      Spec.hs+  build-depends:+      base == 4.*+    , logsink+    , logging-facade+    , hspec == 2.*+  default-language: Haskell2010
+ src/System/Logging/LogSink/Config.hs view
@@ -0,0 +1,52 @@+module System.Logging.LogSink.Config (+  SinkConfig(..)+, defaultSinkConfig+, LogLevel(..)+, LogTarget(..)+, toLogSink+, setupLogging+) where++import           Control.Applicative+import           System.Exit (exitFailure)+import           System.IO+import           System.Logging.Facade.Sink+import           System.Logging.Facade.Types++import           System.Logging.LogSink.Core+import           System.Logging.LogSink.Format++data LogTarget = StdErr | SysLog+  deriving (Eq, Show)++data SinkConfig = SinkConfig {+  sinkConfigLevel :: LogLevel+, sinkConfigFormat :: String+, sinkConfigTarget :: LogTarget+} deriving (Eq, Show)++defaultSinkConfig :: SinkConfig+defaultSinkConfig = SinkConfig {+  sinkConfigLevel = minBound+, sinkConfigFormat = defaultFormatString+, sinkConfigTarget = StdErr+}++setupLogging :: [SinkConfig] -> IO ()+setupLogging sinks = either die (setLogSink . combine) (mapM toLogSink sinks)++die :: String -> IO a+die err = hPutStrLn stderr err >> exitFailure++toLogSink :: SinkConfig -> Either String LogSink+toLogSink sink = filterByLogLevel (sinkConfigLevel sink) . targetToSink sink <$> parseFormat_ (sinkConfigFormat sink)+  where+    parseFormat_ :: String -> Either String Format+    parseFormat_ fmt = case parseFormat fmt of+      Left err -> Left ("Invalid format " ++ show fmt ++ " (" ++ err ++ ")")+      Right f -> return f++targetToSink :: SinkConfig -> Format -> LogSink+targetToSink sink = case sinkConfigTarget sink of+  StdErr -> stdErrSink+  SysLog -> sysLogSink
+ src/System/Logging/LogSink/Core.hs view
@@ -0,0 +1,56 @@+module System.Logging.LogSink.Core (+  Format+, defaultFormat+, stdErrSink+, sysLogSink+, combine+, filterByLogLevel+) where++import           Control.Concurrent.MVar+import           Control.Monad+import           System.IO+import           System.IO.Unsafe+import           System.Logging.Facade.Sink+import           System.Logging.Facade.Types+import           System.Posix.Syslog++import           System.Logging.LogSink.Format++defaultFormat :: Format+defaultFormat =+  let Right format = parseFormat defaultFormatString+  in format++{-# NOINLINE stderrLock #-}+stderrLock :: MVar ()+stderrLock = unsafePerformIO $ newMVar ()++stdErrSink :: Format -> LogSink+stdErrSink format record = do+  s <- format record+  modifyMVar_ stderrLock $ \ () -> hPutStrLn stderr s++sysLogSink :: Format -> LogSink+sysLogSink format record = format record >>= syslog (toPriority $ logRecordLevel record)+  where+    toPriority :: LogLevel -> Priority+    toPriority l = case l of+      TRACE -> Debug+      DEBUG -> Debug+      INFO -> Info+      WARN -> Warning+      ERROR -> Error++combine :: [LogSink] -> LogSink+combine sinks record = do+  forM_ sinks $ \sink -> sink record++filterByLogLevel :: LogLevel -> LogSink -> LogSink+filterByLogLevel level sink+  | level == minBound = sink+  | otherwise = filteringSink+  where+    filteringSink record+      | logRecordLevel record < level = return ()+      | otherwise = sink record
+ src/System/Logging/LogSink/Format.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE RecordWildCards #-}+module System.Logging.LogSink.Format where++import           Control.Applicative+import           Control.Concurrent+import           Data.Char+import           Data.Time.Clock+import           Data.Time.LocalTime ()+import           System.Logging.Facade.Types++type Format = LogRecord -> IO String++defaultFormatString :: String+defaultFormatString = "{level}: {message}"++parseFormat :: String -> Either String Format+parseFormat format = formatNodes <$> parseNodes format++data Node = Level | Message | Timestamp | ThreadId | Literal String+  deriving (Eq, Show)++formatNodes :: [Node] -> LogRecord -> IO String+formatNodes nodes LogRecord{..} = concat <$> mapM evalNode nodes+  where+    evalNode :: Node -> IO String+    evalNode node = case node of+      Level -> return (show logRecordLevel)+      Message -> return logRecordMessage+      Timestamp -> show <$> getCurrentTime+      ThreadId -> show <$> myThreadId+      Literal s -> return s++parseNodes :: String -> Either String [Node]+parseNodes = fmap (filter $ not . isEmpty) . go ""+  where+    isIdChar :: Char -> Bool+    isIdChar c = isAlphaNum c || (c `elem` "-_")++    lookupNode :: String -> Maybe Node+    lookupNode key = lookup key [("level", Level), ("message", Message), ("timestamp", Timestamp), ("thread-id", ThreadId)]++    go :: String -> String -> Either String [Node]+    go acc input = case input of+      ""  -> return [lit acc]+      '{':xs | (key,'}':ys) <- span isIdChar xs -> case lookupNode key of+        Nothing -> Left ("invalid format directive " ++ show key)+        Just node -> do+          nodes <- go "" ys+          return (lit acc : node : nodes)+      x:xs -> go (x:acc) xs++    lit :: String -> Node+    lit acc = (Literal . reverse) acc++    isEmpty :: Node -> Bool+    isEmpty (Literal "") = True+    isEmpty _ = False
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}