module Ecluse.Log (
LogFormat (..),
parseLogFormat,
renderLogFormat,
newLogEnv,
newScribe,
formatterFor,
auditContext,
moduleField,
DdContext (..),
DdSpan (..),
ddField,
ddObject,
formatDdTraceId,
formatDdSpanId,
renderLogLine,
) where
import Data.Aeson (Value, object, (.=))
import Data.ByteString qualified as BS
import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Builder qualified as TB
import Katip (
ColorStrategy (ColorLog),
Environment,
Item,
LogEnv,
LogItem,
Namespace (Namespace),
Scribe,
Severity (DebugS),
SimpleLogPayload,
Verbosity (V2),
defaultScribeSettings,
initLogEnv,
permitItem,
registerScribe,
sl,
)
import Katip.Scribes.Handle (ItemFormatter, bracketFormat, jsonFormat, mkHandleScribeWithFormatter)
import Ecluse.Core.Wire (WireVocab (..), parseWire, renderWire)
data LogFormat
=
JsonLog
|
ConsoleLog
deriving stock (LogFormat -> LogFormat -> Bool
(LogFormat -> LogFormat -> Bool)
-> (LogFormat -> LogFormat -> Bool) -> Eq LogFormat
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: LogFormat -> LogFormat -> Bool
== :: LogFormat -> LogFormat -> Bool
$c/= :: LogFormat -> LogFormat -> Bool
/= :: LogFormat -> LogFormat -> Bool
Eq, Int -> LogFormat -> ShowS
[LogFormat] -> ShowS
LogFormat -> String
(Int -> LogFormat -> ShowS)
-> (LogFormat -> String)
-> ([LogFormat] -> ShowS)
-> Show LogFormat
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> LogFormat -> ShowS
showsPrec :: Int -> LogFormat -> ShowS
$cshow :: LogFormat -> String
show :: LogFormat -> String
$cshowList :: [LogFormat] -> ShowS
showList :: [LogFormat] -> ShowS
Show)
instance WireVocab LogFormat where
wireKind :: Text
wireKind = Text
"log format"
wireTable :: NonEmpty (LogFormat, Text)
wireTable =
(LogFormat
JsonLog, Text
"json")
(LogFormat, Text)
-> [(LogFormat, Text)] -> NonEmpty (LogFormat, Text)
forall a. a -> [a] -> NonEmpty a
:| [(LogFormat
ConsoleLog, Text
"console")]
parseLogFormat :: Text -> Either Text LogFormat
parseLogFormat :: Text -> Either Text LogFormat
parseLogFormat = Text -> Either Text LogFormat
forall a. WireVocab a => Text -> Either Text a
parseWire
renderLogFormat :: LogFormat -> Text
renderLogFormat :: LogFormat -> Text
renderLogFormat = LogFormat -> Text
forall a. (Eq a, WireVocab a) => a -> Text
renderWire
newLogEnv :: LogFormat -> Environment -> IO LogEnv
newLogEnv :: LogFormat -> Environment -> IO LogEnv
newLogEnv LogFormat
format Environment
environment = do
scribe <- LogFormat -> IO Scribe
newScribe LogFormat
format
base <- initLogEnv (Namespace ["ecluse"]) environment
registerScribe "stdout" scribe defaultScribeSettings base
newScribe :: LogFormat -> IO Scribe
newScribe :: LogFormat -> IO Scribe
newScribe LogFormat
format =
(forall a. LogItem a => ItemFormatter a)
-> ColorStrategy -> Handle -> PermitFunc -> Verbosity -> IO Scribe
mkHandleScribeWithFormatter
(LogFormat -> ItemFormatter a
forall a. LogItem a => LogFormat -> ItemFormatter a
formatterFor LogFormat
format)
(Bool -> ColorStrategy
ColorLog Bool
False)
Handle
stdout
(Severity -> Item a -> IO Bool
forall (m :: * -> *) a. Monad m => Severity -> Item a -> m Bool
permitItem Severity
DebugS)
Verbosity
V2
formatterFor :: (LogItem a) => LogFormat -> ItemFormatter a
formatterFor :: forall a. LogItem a => LogFormat -> ItemFormatter a
formatterFor = \case
LogFormat
JsonLog -> ItemFormatter a
forall a. LogItem a => ItemFormatter a
jsonFormat
LogFormat
ConsoleLog -> ItemFormatter a
forall a. LogItem a => ItemFormatter a
bracketFormat
auditContext ::
Text ->
Text ->
Text ->
SimpleLogPayload
auditContext :: Text -> Text -> Text -> SimpleLogPayload
auditContext Text
package Text
version Text
rule =
Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"package" Text
package SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"version" Text
version SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"rule" Text
rule
moduleField :: Text -> SimpleLogPayload
moduleField :: Text -> SimpleLogPayload
moduleField = Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"module"
data DdContext = DdContext
{ DdContext -> Text
ddService :: Text
, DdContext -> Maybe Text
ddEnv :: Maybe Text
, DdContext -> Maybe Text
ddVersion :: Maybe Text
, DdContext -> Maybe DdSpan
ddSpan :: Maybe DdSpan
}
deriving stock (DdContext -> DdContext -> Bool
(DdContext -> DdContext -> Bool)
-> (DdContext -> DdContext -> Bool) -> Eq DdContext
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DdContext -> DdContext -> Bool
== :: DdContext -> DdContext -> Bool
$c/= :: DdContext -> DdContext -> Bool
/= :: DdContext -> DdContext -> Bool
Eq, Int -> DdContext -> ShowS
[DdContext] -> ShowS
DdContext -> String
(Int -> DdContext -> ShowS)
-> (DdContext -> String)
-> ([DdContext] -> ShowS)
-> Show DdContext
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DdContext -> ShowS
showsPrec :: Int -> DdContext -> ShowS
$cshow :: DdContext -> String
show :: DdContext -> String
$cshowList :: [DdContext] -> ShowS
showList :: [DdContext] -> ShowS
Show)
data DdSpan = DdSpan
{ DdSpan -> Text
ddTraceId :: Text
, DdSpan -> Text
ddSpanId :: Text
}
deriving stock (DdSpan -> DdSpan -> Bool
(DdSpan -> DdSpan -> Bool)
-> (DdSpan -> DdSpan -> Bool) -> Eq DdSpan
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DdSpan -> DdSpan -> Bool
== :: DdSpan -> DdSpan -> Bool
$c/= :: DdSpan -> DdSpan -> Bool
/= :: DdSpan -> DdSpan -> Bool
Eq, Int -> DdSpan -> ShowS
[DdSpan] -> ShowS
DdSpan -> String
(Int -> DdSpan -> ShowS)
-> (DdSpan -> String) -> ([DdSpan] -> ShowS) -> Show DdSpan
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DdSpan -> ShowS
showsPrec :: Int -> DdSpan -> ShowS
$cshow :: DdSpan -> String
show :: DdSpan -> String
$cshowList :: [DdSpan] -> ShowS
showList :: [DdSpan] -> ShowS
Show)
ddObject :: DdContext -> Value
ddObject :: DdContext -> Value
ddObject DdContext
ctx =
[Pair] -> Value
object ([Pair] -> Value) -> [Pair] -> Value
forall a b. (a -> b) -> a -> b
$
[Maybe Pair] -> [Pair]
forall a. [Maybe a] -> [a]
catMaybes
[ Pair -> Maybe Pair
forall a. a -> Maybe a
Just (Key
"service" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= DdContext -> Text
ddService DdContext
ctx)
, (Key
"env" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.=) (Text -> Pair) -> Maybe Text -> Maybe Pair
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DdContext -> Maybe Text
ddEnv DdContext
ctx
, (Key
"version" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.=) (Text -> Pair) -> Maybe Text -> Maybe Pair
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DdContext -> Maybe Text
ddVersion DdContext
ctx
, (Key
"trace_id" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.=) (Text -> Pair) -> (DdSpan -> Text) -> DdSpan -> Pair
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DdSpan -> Text
ddTraceId (DdSpan -> Pair) -> Maybe DdSpan -> Maybe Pair
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DdContext -> Maybe DdSpan
ddSpan DdContext
ctx
, (Key
"span_id" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.=) (Text -> Pair) -> (DdSpan -> Text) -> DdSpan -> Pair
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DdSpan -> Text
ddSpanId (DdSpan -> Pair) -> Maybe DdSpan -> Maybe Pair
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DdContext -> Maybe DdSpan
ddSpan DdContext
ctx
]
ddField :: DdContext -> SimpleLogPayload
ddField :: DdContext -> SimpleLogPayload
ddField = Text -> Value -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"dd" (Value -> SimpleLogPayload)
-> (DdContext -> Value) -> DdContext -> SimpleLogPayload
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DdContext -> Value
ddObject
formatDdTraceId :: ByteString -> Text
formatDdTraceId :: ByteString -> Text
formatDdTraceId = Word64 -> Text
forall b a. (Show a, IsString b) => a -> b
show (Word64 -> Text) -> (ByteString -> Word64) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Word64
low64Bits
formatDdSpanId :: ByteString -> Text
formatDdSpanId :: ByteString -> Text
formatDdSpanId = Word64 -> Text
forall b a. (Show a, IsString b) => a -> b
show (Word64 -> Text) -> (ByteString -> Word64) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Word64
low64Bits
low64Bits :: ByteString -> Word64
low64Bits :: ByteString -> Word64
low64Bits = (Word64 -> Word8 -> Word64) -> Word64 -> ByteString -> Word64
forall a. (a -> Word8 -> a) -> a -> ByteString -> a
BS.foldl' (\Word64
acc Word8
byte -> Word64
acc Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
* Word64
256 Word64 -> Word64 -> Word64
forall a. Num a => a -> a -> a
+ Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
byte) Word64
0 (ByteString -> Word64)
-> (ByteString -> ByteString) -> ByteString -> Word64
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> ByteString -> ByteString
lastBytes Int
8
where
lastBytes :: Int -> ByteString -> ByteString
lastBytes :: Int -> ByteString -> ByteString
lastBytes Int
n ByteString
bytes = Int -> ByteString -> ByteString
BS.drop (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (ByteString -> Int
BS.length ByteString
bytes Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
n)) ByteString
bytes
renderLogLine :: (LogItem a) => LogFormat -> Item a -> Text
renderLogLine :: forall a. LogItem a => LogFormat -> Item a -> Text
renderLogLine LogFormat
format Item a
item =
LazyText -> Text
TL.toStrict (Builder -> LazyText
TB.toLazyText (LogFormat -> ItemFormatter a
forall a. LogItem a => LogFormat -> ItemFormatter a
formatterFor LogFormat
format Bool
False Verbosity
V2 Item a
item))