{-# LANGUAGE CPP, OverloadedStrings #-}
module Katip.Scribes.Handle where
import Control.Concurrent
import Control.Exception (bracket_, finally)
import Data.Aeson
#if MIN_VERSION_aeson(2, 0, 0)
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as KM
import Data.Bifunctor (Bifunctor (..))
#else
import qualified Data.HashMap.Strict as HM
#endif
import Data.Monoid as M
import Data.Scientific as S
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Lazy (toStrict)
import Data.Text.Lazy.Builder
import Data.Text.Lazy.Encoding (decodeUtf8)
import Data.Text.Lazy.IO as T
import qualified Data.Vector as V
import Katip.Core
import Katip.Format.Time (formatAsLogTime)
import System.IO
brackets :: Builder -> Builder
brackets :: Builder -> Builder
brackets Builder
m = Builder
"[" Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
M.<> Builder
m Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
"]"
{-# INLINE brackets #-}
getKeys :: LogItem s => Verbosity -> s -> [Builder]
getKeys :: forall s. LogItem s => Verbosity -> s -> [Builder]
getKeys Verbosity
verb s
a = [[Builder]] -> [Builder]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (KeyMap Value -> [[Builder]]
toBuilders (Verbosity -> s -> KeyMap Value
forall a. LogItem a => Verbosity -> a -> KeyMap Value
payloadObject Verbosity
verb s
a))
{-# INLINE getKeys #-}
#if MIN_VERSION_aeson(2, 0, 0)
toBuilders :: KM.KeyMap Value -> [[Builder]]
toBuilders :: KeyMap Value -> [[Builder]]
toBuilders = ((Key, Value) -> [Builder]) -> [(Key, Value)] -> [[Builder]]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Text, Value) -> [Builder]
renderPair ((Text, Value) -> [Builder])
-> ((Key, Value) -> (Text, Value)) -> (Key, Value) -> [Builder]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Key -> Text) -> (Key, Value) -> (Text, Value)
forall a b c. (a -> b) -> (a, c) -> (b, c)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first Key -> Text
K.toText) ([(Key, Value)] -> [[Builder]])
-> (KeyMap Value -> [(Key, Value)]) -> KeyMap Value -> [[Builder]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. KeyMap Value -> [(Key, Value)]
forall v. KeyMap v -> [(Key, v)]
KM.toList
{-# INLINE toBuilders #-}
toTxtKeyList :: KM.KeyMap v -> [(Text, v)]
toTxtKeyList :: forall v. KeyMap v -> [(Text, v)]
toTxtKeyList KeyMap v
mp = (Key -> Text) -> (Key, v) -> (Text, v)
forall a b c. (a -> b) -> (a, c) -> (b, c)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first Key -> Text
K.toText ((Key, v) -> (Text, v)) -> [(Key, v)] -> [(Text, v)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> KeyMap v -> [(Key, v)]
forall v. KeyMap v -> [(Key, v)]
KM.toList KeyMap v
mp
{-# INLINE toTxtKeyList #-}
#else
toBuilders :: HM.HashMap Text Value -> [[Builder]]
toBuilders = fmap renderPair . HM.toList
{-# INLINE toBuilders #-}
toTxtKeyList :: HM.HashMap Text v -> [(Text, v)]
toTxtKeyList = HM.toList
{-# INLINE toTxtKeyList #-}
#endif
renderPair :: (Text, Value) -> [Builder]
renderPair :: (Text, Value) -> [Builder]
renderPair (Text
k, Value
v) =
case Value
v of
Object KeyMap Value
o -> [[Builder]] -> [Builder]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [(Text, Value) -> [Builder]
renderPair (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
k', Value
v') | (Text
k', Value
v') <- KeyMap Value -> [(Text, Value)]
forall v. KeyMap v -> [(Text, v)]
toTxtKeyList KeyMap Value
o]
Array Array
a -> [[Builder]] -> [Builder]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [(Text, Value) -> [Builder]
renderPair (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Int -> String
forall a. Show a => a -> String
show Int
k'), Value
v') | (Int
k', Value
v') <- Vector (Int, Value) -> [(Int, Value)]
forall a. Vector a -> [a]
V.toList (Array -> Vector (Int, Value)
forall a. Vector a -> Vector (Int, a)
V.indexed Array
a)]
String Text
t -> [Text -> Builder
fromText (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
t)]
Number Scientific
n -> [Text -> Builder
fromText (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":") Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> String -> Builder
fromString (Scientific -> String
formatNumber Scientific
n)]
Bool Bool
b -> [Text -> Builder
fromText (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":") Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> String -> Builder
fromString (Bool -> String
forall a. Show a => a -> String
show Bool
b)]
Value
Null -> [Text -> Builder
fromText (Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":null")]
where
formatNumber :: Scientific -> String
formatNumber :: Scientific -> String
formatNumber Scientific
n =
FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Generic (if Scientific -> Bool
isFloating Scientific
n then Maybe Int
forall a. Maybe a
Nothing else Int -> Maybe Int
forall a. a -> Maybe a
Just Int
0) Scientific
n
{-# INLINE renderPair #-}
data ColorStrategy
=
ColorLog Bool
|
ColorIfTerminal
deriving (Int -> ColorStrategy -> ShowS
[ColorStrategy] -> ShowS
ColorStrategy -> String
(Int -> ColorStrategy -> ShowS)
-> (ColorStrategy -> String)
-> ([ColorStrategy] -> ShowS)
-> Show ColorStrategy
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ColorStrategy -> ShowS
showsPrec :: Int -> ColorStrategy -> ShowS
$cshow :: ColorStrategy -> String
show :: ColorStrategy -> String
$cshowList :: [ColorStrategy] -> ShowS
showList :: [ColorStrategy] -> ShowS
Show, ColorStrategy -> ColorStrategy -> Bool
(ColorStrategy -> ColorStrategy -> Bool)
-> (ColorStrategy -> ColorStrategy -> Bool) -> Eq ColorStrategy
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ColorStrategy -> ColorStrategy -> Bool
== :: ColorStrategy -> ColorStrategy -> Bool
$c/= :: ColorStrategy -> ColorStrategy -> Bool
/= :: ColorStrategy -> ColorStrategy -> Bool
Eq)
mkHandleScribe :: ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribe :: ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribe = (forall a. LogItem a => ItemFormatter a)
-> ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribeWithFormatter ItemFormatter a
forall a. LogItem a => ItemFormatter a
bracketFormat
{-# INLINE mkHandleScribe #-}
mkHandleScribeWithFormatter ::
(forall a. LogItem a => ItemFormatter a) ->
ColorStrategy ->
Handle ->
PermitFunc ->
Verbosity ->
IO Scribe
mkHandleScribeWithFormatter :: (forall a. LogItem a => ItemFormatter a)
-> ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribeWithFormatter forall a. LogItem a => ItemFormatter a
itemFormatter ColorStrategy
cs Handle
h PermitFunc
permitF Verbosity
verb = do
Handle -> BufferMode -> IO ()
hSetBuffering Handle
h BufferMode
LineBuffering
Bool
colorize <- case ColorStrategy
cs of
ColorStrategy
ColorIfTerminal -> Handle -> IO Bool
hIsTerminalDevice Handle
h
ColorLog Bool
b -> Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
b
MVar ()
lock <- () -> IO (MVar ())
forall a. a -> IO (MVar a)
newMVar ()
let logger :: Item a -> IO ()
logger i :: Item a
i@Item {} = do
IO () -> IO () -> IO () -> IO ()
forall a b c. IO a -> IO b -> IO c -> IO c
bracket_ (MVar () -> IO ()
forall a. MVar a -> IO a
takeMVar MVar ()
lock) (MVar () -> () -> IO ()
forall a. MVar a -> a -> IO ()
putMVar MVar ()
lock ()) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
Handle -> Text -> IO ()
T.hPutStrLn Handle
h (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Builder -> Text
toLazyText (Builder -> Text) -> Builder -> Text
forall a b. (a -> b) -> a -> b
$ ItemFormatter a
forall a. LogItem a => ItemFormatter a
itemFormatter Bool
colorize Verbosity
verb Item a
i
Scribe -> IO Scribe
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Scribe -> IO Scribe) -> Scribe -> IO Scribe
forall a b. (a -> b) -> a -> b
$ (forall a. LogItem a => Item a -> IO ())
-> IO () -> PermitFunc -> Scribe
Scribe Item a -> IO ()
forall a. LogItem a => Item a -> IO ()
logger (Handle -> IO ()
hFlush Handle
h) Item a -> IO Bool
PermitFunc
permitF
{-# INLINE mkHandleScribeWithFormatter #-}
mkFileScribe :: FilePath -> PermitFunc -> Verbosity -> IO Scribe
mkFileScribe :: String -> PermitFunc -> Verbosity -> IO Scribe
mkFileScribe String
f PermitFunc
permitF Verbosity
verb = do
Handle
h <- String -> IOMode -> IO Handle
openFile String
f IOMode
AppendMode
Scribe forall a. LogItem a => Item a -> IO ()
logger IO ()
finalizer PermitFunc
permit <- ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribe (Bool -> ColorStrategy
ColorLog Bool
False) Handle
h Item a -> IO Bool
PermitFunc
permitF Verbosity
verb
Scribe -> IO Scribe
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ((forall a. LogItem a => Item a -> IO ())
-> IO () -> PermitFunc -> Scribe
Scribe Item a -> IO ()
forall a. LogItem a => Item a -> IO ()
logger (IO ()
finalizer IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO a
`finally` Handle -> IO ()
hClose Handle
h) Item a -> IO Bool
PermitFunc
permit)
{-# INLINE mkFileScribe #-}
type ItemFormatter a = Bool -> Verbosity -> Item a -> Builder
formatItem :: LogItem a => ItemFormatter a
formatItem :: forall a. LogItem a => ItemFormatter a
formatItem = ItemFormatter a
forall a. LogItem a => ItemFormatter a
bracketFormat
{-# DEPRECATED formatItem "Use bracketFormat instead" #-}
{-# INLINE formatItem #-}
bracketFormat :: LogItem a => ItemFormatter a
bracketFormat :: forall a. LogItem a => ItemFormatter a
bracketFormat Bool
withColor Verbosity
verb Item {a
String
Maybe Loc
UTCTime
ProcessID
ThreadIdText
LogStr
Severity
Environment
Namespace
_itemApp :: Namespace
_itemEnv :: Environment
_itemSeverity :: Severity
_itemThread :: ThreadIdText
_itemHost :: String
_itemProcess :: ProcessID
_itemPayload :: a
_itemMessage :: LogStr
_itemTime :: UTCTime
_itemNamespace :: Namespace
_itemLoc :: Maybe Loc
_itemApp :: forall a. Item a -> Namespace
_itemEnv :: forall a. Item a -> Environment
_itemSeverity :: forall a. Item a -> Severity
_itemThread :: forall a. Item a -> ThreadIdText
_itemHost :: forall a. Item a -> String
_itemProcess :: forall a. Item a -> ProcessID
_itemPayload :: forall a. Item a -> a
_itemMessage :: forall a. Item a -> LogStr
_itemTime :: forall a. Item a -> UTCTime
_itemNamespace :: forall a. Item a -> Namespace
_itemLoc :: forall a. Item a -> Maybe Loc
..} =
Builder -> Builder
brackets Builder
nowStr
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> Builder
brackets ([Builder] -> Builder
forall a. Monoid a => [a] -> a
mconcat ([Builder] -> Builder) -> [Builder] -> Builder
forall a b. (a -> b) -> a -> b
$ (Text -> Builder) -> [Text] -> [Builder]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Builder
fromText ([Text] -> [Builder]) -> [Text] -> [Builder]
forall a b. (a -> b) -> a -> b
$ Namespace -> [Text]
intercalateNs Namespace
_itemNamespace)
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> Builder
brackets (Text -> Builder
fromText (Severity -> Text
renderSeverity' Severity
_itemSeverity))
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> Builder
brackets (String -> Builder
fromString String
_itemHost)
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> Builder
brackets (Builder
"PID " Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> String -> Builder
fromString (ProcessID -> String
forall a. Show a => a -> String
show ProcessID
_itemProcess))
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> Builder
brackets (Builder
"ThreadId " Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Text -> Builder
fromText (ThreadIdText -> Text
getThreadIdText ThreadIdText
_itemThread))
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> [Builder] -> Builder
forall a. Monoid a => [a] -> a
mconcat [Builder]
ks
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder -> (Loc -> Builder) -> Maybe Loc -> Builder
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Builder
forall a. Monoid a => a
mempty (Builder -> Builder
brackets (Builder -> Builder) -> (Loc -> Builder) -> Loc -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Builder
fromString (String -> Builder) -> (Loc -> String) -> Loc -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Loc -> String
locationToString) Maybe Loc
_itemLoc
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Text -> Builder
fromText Text
" "
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (LogStr -> Builder
unLogStr LogStr
_itemMessage)
where
nowStr :: Builder
nowStr = Text -> Builder
fromText (UTCTime -> Text
formatAsLogTime UTCTime
_itemTime)
ks :: [Builder]
ks = (Builder -> Builder) -> [Builder] -> [Builder]
forall a b. (a -> b) -> [a] -> [b]
map Builder -> Builder
brackets ([Builder] -> [Builder]) -> [Builder] -> [Builder]
forall a b. (a -> b) -> a -> b
$ Verbosity -> a -> [Builder]
forall s. LogItem s => Verbosity -> s -> [Builder]
getKeys Verbosity
verb a
_itemPayload
renderSeverity' :: Severity -> Text
renderSeverity' Severity
severity =
Bool -> Severity -> Text -> Text
colorBySeverity Bool
withColor Severity
severity (Severity -> Text
renderSeverity Severity
severity)
{-# INLINE bracketFormat #-}
jsonFormat :: LogItem a => ItemFormatter a
jsonFormat :: forall a. LogItem a => ItemFormatter a
jsonFormat Bool
withColor Verbosity
verb Item a
i =
Text -> Builder
fromText (Text -> Builder) -> Text -> Builder
forall a b. (a -> b) -> a -> b
$
Bool -> Severity -> Text -> Text
colorBySeverity Bool
withColor (Item a -> Severity
forall a. Item a -> Severity
_itemSeverity Item a
i) (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$
Text -> Text
toStrict (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
decodeUtf8 (ByteString -> Text) -> ByteString -> Text
forall a b. (a -> b) -> a -> b
$ Value -> ByteString
forall a. ToJSON a => a -> ByteString
encode (Value -> ByteString) -> Value -> ByteString
forall a b. (a -> b) -> a -> b
$ Verbosity -> Item a -> Value
forall a. LogItem a => Verbosity -> Item a -> Value
itemJson Verbosity
verb Item a
i
{-# INLINE jsonFormat #-}
colorBySeverity :: Bool -> Severity -> Text -> Text
colorBySeverity :: Bool -> Severity -> Text -> Text
colorBySeverity Bool
withColor Severity
severity Text
msg = case Severity
severity of
Severity
EmergencyS -> Text -> Text
red Text
msg
Severity
AlertS -> Text -> Text
red Text
msg
Severity
CriticalS -> Text -> Text
red Text
msg
Severity
ErrorS -> Text -> Text
red Text
msg
Severity
WarningS -> Text -> Text
yellow Text
msg
Severity
_ -> Text
msg
where
red :: Text -> Text
red = Text -> Text -> Text
forall {a}. (Semigroup a, IsString a) => a -> a -> a
colorize Text
"31"
yellow :: Text -> Text
yellow = Text -> Text -> Text
forall {a}. (Semigroup a, IsString a) => a -> a -> a
colorize Text
"33"
colorize :: a -> a -> a
colorize a
c a
s
| Bool
withColor = a
"\ESC[" a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
c a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
"m" a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
s a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
"\ESC[0m"
| Bool
otherwise = a
s
{-# INLINE colorBySeverity #-}
ioLogEnv :: PermitFunc -> Verbosity -> IO LogEnv
ioLogEnv :: PermitFunc -> Verbosity -> IO LogEnv
ioLogEnv PermitFunc
permit Verbosity
verb = do
LogEnv
le <- Namespace -> Environment -> IO LogEnv
initLogEnv Namespace
"io" Environment
"io"
Scribe
lh <- ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribe ColorStrategy
ColorIfTerminal Handle
stdout Item a -> IO Bool
PermitFunc
permit Verbosity
verb
Text -> Scribe -> ScribeSettings -> LogEnv -> IO LogEnv
registerScribe Text
"stdout" Scribe
lh ScribeSettings
defaultScribeSettings LogEnv
le
{-# INLINE ioLogEnv #-}