co-log-json-0.0.1.0: src/Colog/Json/Internal/Structured.hs
{-# LANGUAGE UnliftedFFITypes #-}
{-# LANGUAGE MagicHash #-}
{-# OPTIONS_HADDOCK not-home #-}
-- |
--
-- Structure for tracing context.
--
-- We assume that each message ('Message') can be tagged with two types of values:
--
-- * @segment@ - path that tells where are we in the codebase.
-- * @attrbibutes@ - additional key-value tags, where value is an arbitrary json object.
--
-- Segment is needed in a case if we want to apply differrent logging rules to the differrent
-- parts of the codebase. For example we may way to log all the messages in the component one
-- but not all the rest.
--
-- In addition each @Message@ provides some common fields:
--
-- * "thread" - id of the thread that emits message
-- * "severity" - message severity
--
-- All messages in the same context share segment and attributes. So when exported to the log
-- analytics systems it's easy to load all the information associated with it.
--
-- **Compatibility note** internal structure of the message may be changed in the future in case
-- if it's proven that another implementation is faster or more memory efficient. However the
-- higher level API is likely to be stable.
module Colog.Json.Internal.Structured
( -- * Log datastructure.
Structured(..)
, Message(..)
, LogStr(..)
, PushContext(..)
-- * Internals.
, Severity(..)
, encodeSeverity
, showLS
, ls
, sl
, mkThreadId
) where
import Control.Concurrent
import Data.Aeson
import Data.Aeson.Encoding as Aeson
import Data.Sequence
import Data.String
import Data.String.Conv
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as TLB
import Foreign.C
import GHC.Conc
import GHC.Exts hiding (toList)
-- | Part of the structured message.
data Structured
= Segment T.Text -- ^ Part of the message that is associated this the context of code.
| Attr T.Text Encoding -- ^ Add attribute to the list.
-- | Log message.
data Message = Message
{ message_severity :: Severity -- ^ Message severity.
, thread_id :: Int -- ^ Thread that emitted message.
, attributes :: Seq Structured -- ^ List of attributes associated with the context.
, message :: LogStr -- ^ Message to log.
}
-- | Efficient message builder.
newtype LogStr = LogStr TLB.Builder
deriving newtype IsString
deriving newtype Semigroup
deriving newtype Monoid
-- | Logger severity.
data Severity
= DebugS -- ^ Debug level, intended for internal information
| InfoS -- ^ Info level, that may be interesting to the user
| NoticeS -- ^ Notice, information that
| WarningS -- ^ Warning, information possible problem problem of some sort
| ErrorS -- ^ Error, information about a problem
| CriticalS -- ^ Critical error, intended for error that may break the system
| AlertS -- ^ Critical error where immediate actions should be taken
| EmergencyS -- ^ System wide emergency
deriving (Show, Bounded, Eq, Ord, Enum)
-- | Convert severity into the one accepted by the loger.
encodeSeverity :: Severity -> Aeson.Encoding
{-# INLINE encodeSeverity #-}
encodeSeverity DebugS = Aeson.text "DEBUG"
encodeSeverity InfoS = Aeson.text "INFO"
encodeSeverity NoticeS = Aeson.text "NOTICE"
encodeSeverity WarningS = Aeson.text "WARNING"
encodeSeverity ErrorS = Aeson.text "ERROR"
encodeSeverity CriticalS = Aeson.text "CRITICAL"
encodeSeverity AlertS = Aeson.text "ALERT"
encodeSeverity EmergencyS = Aeson.text "EMERGENCY"
-- | Wrapper over the structured message builder.
newtype PushContext = PushContext (Seq Structured -> Seq Structured)
-- | "Simple logger" adds a key value to the context:
--
-- @sl "foo" 123@
--
-- Will add @"foo":123@ key pair to the current list of the attributes.
-- Submitted value is stored with json encoding.
sl :: ToJSON a => T.Text -> a -> PushContext
sl label msg = PushContext \x ->
x |> Attr label (toEncoding msg)
-- | Log any message.
logStr :: StringConv a T.Text => a -> LogStr
logStr t = LogStr (TLB.fromText $ toS t)
-- | Convert message can be converted.
ls :: StringConv a T.Text => a -> LogStr
ls = logStr
-- | Convert loggable value from any message that has show instance.
showLS :: Show a => a -> LogStr
showLS = ls . show
-- | Helper function to get id of the thread.
mkThreadId :: ThreadId -> Int
{-# NOINLINE mkThreadId #-}
mkThreadId (ThreadId tid) = fromIntegral (getThreadId tid)
foreign import ccall unsafe "rts_getThreadId" getThreadId :: ThreadId# -> CInt