@@ -1,9 +1,12 @@
2+{-# LANGUAGE DefaultSignatures #-}
1{-# LANGUAGE DeriveFunctor #-}
3{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE ExistentialQuantification #-}
5{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9+{-# LANGUAGE ImplicitParams #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE RankNTypes #-}
12{-# LANGUAGE RankNTypes #-}
@@ -11,14 +14,17 @@
11{-# LANGUAGE TemplateHaskell #-}
14{-# LANGUAGE TemplateHaskell #-}
12{-# LANGUAGE TypeFamilies #-}
15{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE UndecidableInstances #-}
16{-# LANGUAGE UndecidableInstances #-}
17+#if MIN_VERSION_base(4, 9, 0)
18+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
20+-- | This module is not meant to be imported directly and may contain
21+-- internal mechanisms that will change without notice.
14module Katip.Core where
22module Katip.Core where
16-------------------------------------------------------------------------------
24-------------------------------------------------------------------------------
17import Control.Applicative as A
25import Control.Applicative as A
18import Control.AutoUpdate
26import Control.AutoUpdate
19import Control.Concurrent
27import Control.Concurrent
22import Control.Monad.Base
28import Control.Monad.Base
23import Control.Monad.Catch
29import Control.Monad.Catch
24import Control.Monad.IO.Class
30import Control.Monad.IO.Class
@@ -38,7 +44,7 @@ import Data.Foldable as FT
38import qualified Data.HashMap.Strict as HM
44import qualified Data.HashMap.Strict as HM
39import Data.List
45import Data.List
40import qualified Data.Map.Strict as M
46import qualified Data.Map.Strict as M
41−import Data.Monoid
47+import Data.Semigroup
42import Data.String
48import Data.String
43import Data.String.Conv
49import Data.String.Conv
44import Data.Text (Text)
50import Data.Text (Text)
@@ -46,13 +52,28 @@ import qualified Data.Text as T
46import qualified Data.Text.Lazy.Builder as B
52import qualified Data.Text.Lazy.Builder as B
47import Data.Time
53import Data.Time
48import GHC.Generics hiding (to)
54import GHC.Generics hiding (to)
55+#if MIN_VERSION_base(4, 8, 0)
56+#if !MIN_VERSION_base(4, 9, 0)
49import Language.Haskell.TH
61import Language.Haskell.TH
50import qualified Language.Haskell.TH.Syntax as TH
62import qualified Language.Haskell.TH.Syntax as TH
51import Network.HostName
65import Network.HostName
52import System.Posix
66import System.Posix
53-------------------------------------------------------------------------------
67-------------------------------------------------------------------------------
70+readMay :: Read a => String -> Maybe a
71+readMay s = case [x | (x,t) <- reads s, ("","") <- lex t] of
73+ [] -> Nothing -- no parse
74+ _ -> Nothing -- Ambiguous parse
56-------------------------------------------------------------------------------
77-------------------------------------------------------------------------------
57-- | Represents a heirarchy of namespaces going from general to
78-- | Represents a heirarchy of namespaces going from general to
58-- specific. For instance: ["processname", "subsystem"]. Note that
79-- specific. For instance: ["processname", "subsystem"]. Note that
@@ -60,7 +81,7 @@ import System.Posix
60-- IsString/OverloadedStrings, so "foo" will result in Namespace
81-- IsString/OverloadedStrings, so "foo" will result in Namespace
61-- ["foo"].
82-- ["foo"].
62newtype Namespace = Namespace { unNamespace :: [Text] }
83newtype 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)
65instance IsString Namespace where
86instance IsString Namespace where
66 fromString s = Namespace [fromString s]
87 fromString s = Namespace [fromString s]
@@ -117,27 +138,25 @@ renderSeverity s = case s of
119-------------------------------------------------------------------------------
140-------------------------------------------------------------------------------
120−severityText :: Prism' Text Severity
141+textToSeverity :: Text -> Maybe Severity
121−severityText = prism renderSeverity toSev
123− toSev "Debug" = Right DebugS
142+textToSeverity "Debug" = Just DebugS
124− toSev "Info" = Right InfoS
143+textToSeverity "Info" = Just InfoS
125− toSev "Notice" = Right NoticeS
144+textToSeverity "Notice" = Just NoticeS
126− toSev "Warning" = Right WarningS
145+textToSeverity "Warning" = Just WarningS
127− toSev "Error" = Right ErrorS
146+textToSeverity "Error" = Just ErrorS
128− toSev "Critical" = Right CriticalS
147+textToSeverity "Critical" = Just CriticalS
129− toSev "Alert" = Right AlertS
148+textToSeverity "Alert" = Just AlertS
130− toSev "Emergency" = Right EmergencyS
149+textToSeverity "Emergency" = Just EmergencyS
131− toSev x = Left x
150+textToSeverity _ = Nothing
133−instance ToJSON Severity where
134− toJSON s = A.String (s ^. re severityText)
153+instance ToJSON Severity where
154+ toJSON s = A.String (renderSeverity s)
137instance FromJSON Severity where
156instance FromJSON Severity where
138 parseJSON = A.withText "Severity" parseSeverity
157 parseJSON = A.withText "Severity" parseSeverity
140− parseSeverity t = case t ^? severityText of
159+ parseSeverity t = case textToSeverity t of
141 Just x -> return x
160 Just x -> return x
142 Nothing -> fail $ "Invalid Severity " ++ toS t
161 Nothing -> fail $ "Invalid Severity " ++ toS t
@@ -150,10 +169,16 @@ newtype LogStr = LogStr { unLogStr :: B.Builder }
150instance IsString LogStr where
169instance IsString LogStr where
151 fromString = LogStr . B.fromString
170 fromString = LogStr . B.fromString
173+instance Semigroup LogStr where
174+ (LogStr a) <> (LogStr b) = LogStr (a <> b)
153instance Monoid LogStr where
177instance Monoid LogStr where
154− mappend (LogStr a) (LogStr b) = LogStr (a `mappend` b)
178+ mappend = (<>)
155 mempty = LogStr mempty
179 mempty = LogStr mempty
157instance FromJSON LogStr where
182instance FromJSON LogStr where
158 parseJSON = A.withText "LogStr" parseLogStr
183 parseJSON = A.withText "LogStr" parseLogStr
@@ -290,13 +315,12 @@ instance FromJSON a => FromJSON (Item a) where
290 <*> (fmap getLocJs <$> o A..: "loc")
315 <*> (fmap getLocJs <$> o A..: "loc")
293−processIDText :: Prism' Text ProcessID
318+processIDToText :: ProcessID -> Text
294−processIDText = prism fromProcessID toProcessID
319+processIDToText = toS . show
296− fromProcessID = toS . show
321+
297− toProcessID t = case toS t ^? _Show of
322+textToProcessID :: Text -> Maybe ProcessID
298− Just i -> Right i
323+textToProcessID = readMay . toS
302newtype ProcessIDJs = ProcessIDJs {
326newtype ProcessIDJs = ProcessIDJs {
@@ -305,13 +329,13 @@ newtype ProcessIDJs = ProcessIDJs {
307instance ToJSON ProcessIDJs where
331instance ToJSON ProcessIDJs where
308− toJSON (ProcessIDJs p) = A.String (p ^. re processIDText)
332+ toJSON (ProcessIDJs p) = A.String (processIDToText p)
311instance FromJSON ProcessIDJs where
335instance FromJSON ProcessIDJs where
312 parseJSON = A.withText "ProcessID" parseProcessID
336 parseJSON = A.withText "ProcessID" parseProcessID
314− parseProcessID t = case t ^? processIDText of
338+ parseProcessID t = case textToProcessID t of
315 Just p -> return $ ProcessIDJs p
339 Just p -> return $ ProcessIDJs p
316 Nothing -> fail $ "Invalid ProcessIDJs " ++ toS t
340 Nothing -> fail $ "Invalid ProcessIDJs " ++ toS t
@@ -321,12 +345,17 @@ instance FromJSON ProcessIDJs where
321data PayloadSelection
345data PayloadSelection
322 = AllKeys
346 = AllKeys
323 | SomeKeys [Text]
347 | SomeKeys [Text]
350+instance Semigroup PayloadSelection where
351+ AllKeys <> _ = AllKeys
352+ _ <> AllKeys = AllKeys
353+ SomeKeys as <> SomeKeys bs = SomeKeys (as <> bs)
325instance Monoid PayloadSelection where
356instance Monoid PayloadSelection where
326 mempty = SomeKeys []
357 mempty = SomeKeys []
327− mappend AllKeys _ = AllKeys
358+ mappend = (<>)
328− mappend _ AllKeys = AllKeys
329− mappend (SomeKeys as) (SomeKeys bs) = SomeKeys (as++bs)
332-------------------------------------------------------------------------------
361-------------------------------------------------------------------------------
@@ -339,8 +368,9 @@ instance Monoid PayloadSelection where
339-- a ToJSON instance like:
368-- a ToJSON instance like:
341-- > instance ToObject Foo
370-- > instance ToObject Foo
342−class ToJSON a => ToObject a where
371+class ToObject a where
343 toObject :: a -> A.Object
372 toObject :: a -> A.Object
373+ default toObject :: ToJSON a => a -> A.Object
344 toObject v = case toJSON v of
374 toObject v = case toJSON v of
345 A.Object o -> o
375 A.Object o -> o
346 _ -> mempty
376 _ -> mempty
@@ -389,15 +419,22 @@ instance ToJSON SimpleLogPayload where
389 toJSON (SimpleLogPayload as) = object $ map go as
419 toJSON (SimpleLogPayload as) = object $ map go as
390 where go (k, AnyLogPayload v) = k A..= v
420 where go (k, AnyLogPayload v) = k A..= v
392instance ToObject SimpleLogPayload
423instance ToObject SimpleLogPayload
394instance LogItem SimpleLogPayload where
426instance LogItem SimpleLogPayload where
395 payloadKeys V0 _ = SomeKeys []
427 payloadKeys V0 _ = SomeKeys []
396 payloadKeys _ _ = AllKeys
428 payloadKeys _ _ = AllKeys
431+instance Semigroup SimpleLogPayload where
432+ SimpleLogPayload a <> SimpleLogPayload b = SimpleLogPayload (a <> b)
398instance Monoid SimpleLogPayload where
435instance Monoid SimpleLogPayload where
399 mempty = SimpleLogPayload []
436 mempty = SimpleLogPayload []
400− SimpleLogPayload a `mappend` SimpleLogPayload b = SimpleLogPayload (a `mappend` b)
437+ mappend = (<>)
403-------------------------------------------------------------------------------
440-------------------------------------------------------------------------------
@@ -412,7 +449,7 @@ sl a b = SimpleLogPayload [(a, AnyLogPayload b)]
412payloadObject :: LogItem a => Verbosity -> a -> A.Object
449payloadObject :: LogItem a => Verbosity -> a -> A.Object
413payloadObject verb a = case FT.foldMap (flip payloadKeys a) [(V0)..verb] of
450payloadObject verb a = case FT.foldMap (flip payloadKeys a) [(V0)..verb] of
414 AllKeys -> toObject a
451 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
418-------------------------------------------------------------------------------
455-------------------------------------------------------------------------------
@@ -459,11 +496,15 @@ data Scribe = Scribe {
499+instance Semigroup Scribe where
500+ (Scribe a) <> (Scribe b) = Scribe $ \ item -> do
462instance Monoid Scribe where
505instance Monoid Scribe where
463 mempty = Scribe $ const $ return ()
506 mempty = Scribe $ const $ return ()
464− mappend (Scribe a) (Scribe b) = Scribe $ \ item -> do
507+ mappend = (<>)
469-------------------------------------------------------------------------------
510-------------------------------------------------------------------------------
@@ -476,12 +517,18 @@ permitItem sev i = _itemSeverity i >= sev
476data LogEnv = LogEnv {
517data LogEnv = LogEnv {
477 _logEnvHost :: HostName
518 _logEnvHost :: HostName
478 , _logEnvPid :: ProcessID
519 , _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"
480 , _logEnvEnv :: Environment
526 , _logEnvEnv :: Environment
481 , _logEnvTimer :: IO UTCTime
527 , _logEnvTimer :: IO UTCTime
482 -- ^ Action to fetch the timestamp. You can use something like
528 -- ^ Action to fetch the timestamp. You can use something like
483 -- 'AutoUpdate' for high volume logs but note that this may cause
529 -- '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'.
485 , _logEnvScribes :: M.Map Text Scribe
532 , _logEnvScribes :: M.Map Text Scribe
487makeLenses ''LogEnv
534makeLenses ''LogEnv
@@ -516,7 +563,7 @@ registerScribe
516 -> Scribe
563 -> Scribe
517 -> LogEnv
564 -> LogEnv
518 -> LogEnv
565 -> LogEnv
519−registerScribe nm h = logEnvScribes . at nm .~ Just h
566+registerScribe nm h = logEnvScribes %~ M.insert nm h
522-------------------------------------------------------------------------------
569-------------------------------------------------------------------------------
@@ -527,12 +574,15 @@ unregisterScribe
527 -- ^ Name of the scribe
574 -- ^ Name of the scribe
528 -> LogEnv
575 -> LogEnv
529 -> LogEnv
576 -> LogEnv
530−unregisterScribe nm = logEnvScribes . at nm .~ Nothing
577+unregisterScribe nm = logEnvScribes %~ M.delete nm
533-------------------------------------------------------------------------------
580-------------------------------------------------------------------------------
534-- | Unregister *all* scribes. Logs will go off into space from this
581-- | 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
536clearScribes
586clearScribes
537 :: LogEnv
587 :: LogEnv
538 -> LogEnv
588 -> LogEnv
@@ -540,8 +590,16 @@ clearScribes = logEnvScribes .~ mempty
542-------------------------------------------------------------------------------
592-------------------------------------------------------------------------------
543−-- | Monads where katip logging actions can be performed
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.
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.
544−class MonadIO m => Katip m where
602+class MonadIO m => Katip m where
545 getLogEnv :: m LogEnv
603 getLogEnv :: m LogEnv
@@ -616,7 +674,7 @@ logItem a ns loc sev msg = do
616 LogEnv{..} <- getLogEnv
674 LogEnv{..} <- getLogEnv
617 liftIO $ do
675 liftIO $ do
618 item <- Item
676 item <- Item
619− <$> pure _logEnvNs
677+ <$> pure _logEnvApp
620 <*> pure _logEnvEnv
678 <*> pure _logEnvEnv
621 <*> pure sev
679 <*> pure sev
622 <*> (mkThreadIdText <$> myThreadId)
680 <*> (mkThreadIdText <$> myThreadId)
@@ -625,9 +683,9 @@ logItem a ns loc sev msg = do
625 <*> pure a
683 <*> pure a
626 <*> pure msg
684 <*> pure msg
627 <*> _logEnvTimer
685 <*> _logEnvTimer
628− <*> pure (_logEnvNs <> ns)
686+ <*> pure (_logEnvApp <> ns)
629 <*> pure loc
687 <*> pure loc
630− forM_ (M.elems _logEnvScribes) $ \ (Scribe h) -> h item
688+ FT.forM_ (M.elems _logEnvScribes) $ \ (Scribe h) -> h item
633-------------------------------------------------------------------------------
691-------------------------------------------------------------------------------
@@ -713,9 +771,33 @@ liftLoc (Loc a b c (d1, d2) (e1, e2)) = [|Loc
714-------------------------------------------------------------------------------
772-------------------------------------------------------------------------------
715-- | For use when you want to include location in your logs. This will
773-- | For use when you want to include location in your logs. This will
716−-- fill the 'Maybe Loc' gap in 'logF' of this module.
774+-- fill the 'Maybe Loc' gap in 'logF' of this module, and relies on implicit
717−getLoc :: Q Exp
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
780+ xs -> Just . toLoc . last $ xs
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)
796+-------------------------------------------------------------------------------
797+-- Like `getLoc`, but uses template-haskell and works with older versions of
798+-- the compiler (GHC 7.8 or older).
718−getLoc = [| $(location >>= liftLoc) |]
800+getLocTH = [| $(location >>= liftLoc) |]
721-------------------------------------------------------------------------------
803-------------------------------------------------------------------------------
@@ -723,7 +805,27 @@ getLoc = [| $(location >>= liftLoc) |]
724-- @$(logT) obj mempty InfoS "Hello world"@
806-- @$(logT) obj mempty InfoS "Hello world"@
725logT :: ExpQ
807logT :: 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 |]
811+-------------------------------------------------------------------------------
812+-- | 'Loc'-tagged logging using implicit-callstacks when available.
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.
821+-- @logLoc obj mempty InfoS "Hello world"@
822+logLoc :: (Applicative m, LogItem a, Katip m)
828+logLoc a ns = logItem a ns getLoc
729-- taken from the file-location package
831-- taken from the file-location package