on master help sign in
Initial fleshing out of the API
commit4b2ab3f0398001894c4a53a19d7961d51fc6951e authorOzgun Ataman <ozgun.ataman@soostone.com> dateSat 31 Jan 2015, 14:34 (12 years ago)
6 files changed+300
.gitignore+4 LICENSE+30 Setup.hs+2 soothsayer.cabal+48 src/Soothsayer/Backends/Handle.hs+56 src/Soothsayer/Core.hs+160
.gitignorenew file+4
@@ -0,0 +1,4 @@
1+.cabal-sandbox
2+dist
3+cabal.sandbox.config
4+TAGS
LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2015, Ozgun Ataman
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Ozgun Ataman nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
soothsayer.cabalnew file+48
@@ -0,0 +1,48 @@
1+-- Initial soothsayer.cabal generated by cabal init. For further
2+-- documentation, see http://haskell.org/cabal/users-guide/
3+
4+name: soothsayer
5+version: 0.1.0.0
6+-- synopsis:
7+-- description:
8+license: BSD3
9+license-file: LICENSE
10+author: Ozgun Ataman
11+maintainer: ozgun.ataman@soostone.com
12+copyright: Soostone Inc
13+category: Data
14+build-type: Simple
15+cabal-version: >=1.10
16+
17+library
18+ exposed-modules:
19+ Soothsayer.Core
20+ Soothsayer.Backends.Handle
21+
22+ -- other-modules:
23+ other-extensions:
24+ DeriveGeneric,
25+ FlexibleContexts,
26+ GeneralizedNewtypeDeriving,
27+ MultiParamTypeClasses,
28+ RankNTypes,
29+ RecordWildCards,
30+ TemplateHaskell
31+
32+ build-depends:
33+ base >=4.6 && <4.7
34+ , lens >=4.2 && <4.7
35+ , mtl >=2.1 && <2.2
36+ , aeson >=0.7 && <0.8
37+ , containers >=0.5 && <0.6
38+ , text >=1.2 && <1.3
39+ , hostname >=1.0 && <1.1
40+ , time >= 1.2 && < 1.5
41+ , bytestring >= 0.9 && < 0.11
42+ , blaze-builder >= 0.3 && < 0.4
43+ , auto-update >= 0.1 && < 0.2
44+ , old-locale >= 1.0 && < 1.1
45+ , lens-aeson >= 1.0 && < 1.1
46+
47+ hs-source-dirs: src
48+ default-language: Haskell2010
src/Soothsayer/Backends/Handle.hsnew file+56
@@ -0,0 +1,56 @@
1+{-# LANGUAGE RecordWildCards #-}
2+module Soothsayer.Backends.Handle where
3+
4+-------------------------------------------------------------------------------
5+import Blaze.ByteString.Builder
6+import Blaze.ByteString.Builder.Char.Utf8
7+import Control.Lens
8+import Data.Aeson (ToJSON (..))
9+import Data.Aeson.Lens
10+import qualified Data.ByteString.Char8 as B
11+import Data.Maybe
12+import Data.Monoid
13+import Data.Text (Text)
14+import Data.Time
15+import System.IO
16+import System.Locale
17+-------------------------------------------------------------------------------
18+import Soothsayer.Core
19+-------------------------------------------------------------------------------
20+
21+
22+-------------------------------------------------------------------------------
23+brackets :: Builder -> Builder
24+brackets m = fromByteString "[" <> m <> fromByteString "]"
25+
26+
27+-------------------------------------------------------------------------------
28+getKeys :: ToJSON s => s -> [Text] -> [Builder]
29+getKeys a keys = flip mapMaybe keys $ \ k ->
30+ a' ^? key k . _Primitive . to renderPrim
31+ where
32+ a' = toJSON a
33+
34+
35+-------------------------------------------------------------------------------
36+renderPrim (StringPrim t) = fromText t
37+renderPrim (NumberPrim s) = fromString (show s)
38+renderPrim (BoolPrim b) = fromString (show b)
39+renderPrim NullPrim = fromByteString "null"
40+
41+
42+-------------------------------------------------------------------------------
43+mkHandleHandler :: Handle -> [Text] -> IO LogHandler
44+mkHandleHandler h keys = do
45+ hSetBuffering h LineBuffering
46+ return $ LogHandler $ \ Item{..} -> do
47+ let nowStr = fromString $ formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" itemTime
48+ ks = map brackets $ getKeys itemPayload keys
49+ msg = brackets nowStr <>
50+ brackets (fromText (renderSeverity itemSeverity)) <>
51+ brackets (fromString itemHost) <>
52+ brackets (fromString (show itemThread)) <>
53+ mconcat ks <>
54+ fromText " " <> fromText itemMessage
55+ B.putStrLn $ toByteString msg
56+
src/Soothsayer/Core.hsnew file+160
@@ -0,0 +1,160 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE RankNTypes #-}
7+{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE TemplateHaskell #-}
9+
10+module Soothsayer.Core where
11+
12+-------------------------------------------------------------------------------
13+import Control.Applicative
14+import Control.AutoUpdate
15+import Control.Concurrent
16+import Control.Lens
17+import Control.Monad.Reader
18+
19+import Data.Aeson (ToJSON)
20+import qualified Data.Aeson as A
21+import qualified Data.Map.Strict as M
22+import Data.Monoid
23+import Data.Text (Text)
24+import Data.Time
25+import GHC.Generics hiding (to)
26+import Network.HostName
27+-------------------------------------------------------------------------------
28+
29+
30+newtype AppName = AppName { getAppName :: Text }
31+ deriving (Eq,Show,Read,Ord,Generic,ToJSON)
32+
33+newtype Environment = Environment { getEnvironment :: Text }
34+ deriving (Eq,Show,Read,Ord,Generic,ToJSON)
35+
36+
37+-------------------------------------------------------------------------------
38+data Severity
39+ = Debug -- ^ Debug messages
40+ | Info -- ^ Information
41+ | Notice -- ^ Normal runtime Conditions
42+ | Warning -- ^ General Warnings
43+ | Error -- ^ General Errors
44+ | Critical -- ^ Severe situations
45+ | Alert -- ^ Take immediate action
46+ | Emergency -- ^ System is unusable
47+ deriving (Eq, Ord, Show, Read, Generic)
48+
49+
50+-------------------------------------------------------------------------------
51+renderSeverity :: Severity -> Text
52+renderSeverity s = case s of
53+ Debug -> "Debug"
54+ Info -> "Debug"
55+ Notice -> "Notice"
56+ Warning -> "Warning"
57+ Error -> "Error"
58+ Critical -> "Critical"
59+ Alert -> "Alert"
60+ Emergency -> "Emergency"
61+
62+
63+-------------------------------------------------------------------------------
64+instance ToJSON Severity where
65+ toJSON s = A.String (renderSeverity s)
66+
67+-------------------------------------------------------------------------------
68+data Item a = Item {
69+ itemApp :: AppName
70+ , itemEnv :: Environment
71+ , itemSeverity :: Severity
72+ , itemThread :: ThreadId
73+ , itemHost :: HostName
74+ , itemPayload :: a
75+ , itemMessage :: Text
76+ , itemTime :: UTCTime
77+ } deriving (Show,Generic)
78+
79+
80+
81+instance ToJSON a => ToJSON (Item a) where
82+ toJSON Item{..} = A.object
83+ [ "app" A..= itemApp
84+ , "env" A..= itemEnv
85+ , "sev" A..= itemSeverity
86+ , "thread" A..= show itemThread
87+ , "host" A..= itemHost
88+ , "data" A..= itemPayload
89+ , "msg" A..= itemMessage
90+ , "at" A..= itemTime
91+ ]
92+
93+
94+-------------------------------------------------------------------------------
95+data LogHandler = LogHandler {
96+ lhPush :: forall a. ToJSON a => Item a -> IO ()
97+ }
98+
99+
100+-------------------------------------------------------------------------------
101+data LogEnv = LogEnv {
102+ _logEnvHost :: HostName
103+ , _logEnvApp :: AppName
104+ , _logEnvEnv :: Environment
105+ , _logEnvTimer :: IO UTCTime
106+ , _logEnvHandlers :: M.Map Text LogHandler
107+ }
108+makeLenses ''LogEnv
109+
110+
111+-------------------------------------------------------------------------------
112+initLogEnv :: AppName -> Environment -> IO LogEnv
113+initLogEnv an env = LogEnv
114+ <$> getHostName
115+ <*> pure an
116+ <*> pure env
117+ <*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
118+ <*> pure mempty
119+
120+
121+-------------------------------------------------------------------------------
122+registerHandler
123+ :: Text
124+ -- ^ Name the handler
125+ -> LogHandler
126+ -> LogEnv
127+ -> LogEnv
128+registerHandler nm h = logEnvHandlers . at nm .~ Just h
129+
130+
131+-------------------------------------------------------------------------------
132+unregisterHandler
133+ :: Text
134+ -- ^ Name of the handler
135+ -> LogEnv
136+ -> LogEnv
137+unregisterHandler nm = logEnvHandlers . at nm .~ Nothing
138+
139+
140+-------------------------------------------------------------------------------
141+logM
142+ :: (Applicative m, MonadIO m, ToJSON a, MonadReader LogEnv m)
143+ => Severity
144+ -> a
145+ -> Text
146+ -> m ()
147+logM sev a msg = do
148+ item <- Item
149+ <$> view logEnvApp
150+ <*> view logEnvEnv
151+ <*> pure sev
152+ <*> liftIO myThreadId
153+ <*> view logEnvHost
154+ <*> pure a
155+ <*> pure msg
156+ <*> (view logEnvTimer >>= liftIO)
157+ hs <- view $ logEnvHandlers . to M.elems
158+ liftIO $ forM_ hs $ \ (LogHandler h) -> h item
159+
160+