opentracing-0.1.0.0: OpenTracing/Log.hs
{-|
Module: OpenTracing.Log
Logs are structured data that occur over the lifetime of a span.
-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
module OpenTracing.Log
( LogRecord(..)
, logTime
, logFields
, LogField(..)
, logFieldLabel
, logFieldEncoding
, logFieldValue
, LogFieldsFormatter
, jsonAssoc
, jsonMap
)
where
import Control.Exception
import Control.Lens hiding ((.=))
import Data.Aeson
import qualified Data.Aeson.Encoding as Encoding
import Data.ByteString.Builder (Builder)
import Data.Foldable
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
import Data.Time.Clock
import GHC.Stack
import qualified Data.Map.Strict as Map
-- | A single entry into a `Spans` logs. Occurs at a single time and contains multiple
-- (one or more) entries.
--
-- @since 0.1.0.0
data LogRecord = LogRecord
{ _logTime :: UTCTime
, _logFields :: NonEmpty LogField
} deriving Show
-- | A piece of data in a `LogRecord`. Conceptually a key:value pair with a few
-- distinguished keys. More info about the distinguished keys in the [OpenTracing spec](https://github.com/opentracing/specification/blob/master/semantic_conventions.md#log-fields-table)
--
-- @since 0.1.0.0
data LogField where
-- | A generic key:value pair entry into a `LogRecord`
LogField :: Show a => Text -> a -> LogField
-- | A stable identifier for some notable moment in the lifetime of a Span.
Event :: Text -> LogField
-- | A concise, human-readable, one-line message explaining the event.
Message :: Text -> LogField
-- | A stack trace in platform-conventional format
Stack :: CallStack -> LogField
-- | The type or "kind" of an error (only for event="error" logs).
ErrKind :: Text -> LogField
-- | The actual error exception
ErrObj :: Exception e => e -> LogField
deriving instance (Show LogField)
type LogFieldsFormatter = forall t. Foldable t => t LogField -> Builder
-- | A log formatter that encodes each `LogField` as a single JSON object.
--
-- >>> BS.hPutBuilder stdout $ jsonAssoc [Event "e", LogField @Text "key" "value"]
-- [{"event":"\"e\""},{"key":"\"value\""}]
--
-- @since 0.1.0.0
jsonAssoc :: LogFieldsFormatter
jsonAssoc = Encoding.fromEncoding . Encoding.list go . toList
where
go lf = Encoding.pairs $
Encoding.pair (logFieldLabel lf) (logFieldEncoding lf)
-- | A log formatter that encodes each `LogField` as an entry in a shared JSON object
--
-- >>> BS.hPutBuilder stdout $ jsonMap [Event "e", LogField @Text "key" "value"]
-- {"event":"e","key":"\"value\""}
--
-- @since 0.1.0.0
jsonMap :: LogFieldsFormatter
jsonMap
= Encoding.fromEncoding
. Encoding.dict Encoding.text id Map.foldrWithKey'
. foldr' merge mempty
where
merge lf = Map.insert (logFieldLabel lf) (logFieldEncoding lf)
-- | Retrieve the label of a log field. Distinguished `LogField`s have predefined keys.
--
-- @since 0.1.0.0
logFieldLabel :: LogField -> Text
logFieldLabel (LogField x _) = x
logFieldLabel (Event _) = "event"
logFieldLabel (Message _) = "message"
logFieldLabel (Stack _) = "stack"
logFieldLabel (ErrKind _) = "error.kind"
logFieldLabel (ErrObj _) = "error.object"
logFieldEncoding :: LogField -> Encoding
logFieldEncoding (LogField _ v) = Encoding.string $ show v
logFieldEncoding (Event v) = Encoding.text v
logFieldEncoding (Message v) = Encoding.text v
logFieldEncoding (Stack v) = Encoding.string $ prettyCallStack v
logFieldEncoding (ErrKind v) = Encoding.text v
logFieldEncoding (ErrObj v) = Encoding.string $ show v
logFieldValue :: LogField -> Value
logFieldValue (LogField _ v) = toJSON $ show v
logFieldValue (Event v) = toJSON v
logFieldValue (Message v) = toJSON v
logFieldValue (Stack v) = toJSON $ prettyCallStack v
logFieldValue (ErrKind v) = toJSON v
logFieldValue (ErrObj v) = toJSON $ show v
makeLenses ''LogRecord