@@ -49,6 +49,7 @@ import qualified Data.ByteString.Lazy as LBS
4949import qualified Data.ByteString.Lazy.Char8 as LBS8
5050import qualified Data.HashMap.Strict as HM
5151import Data.Int
52+import qualified Data.Scientific as Scientific
5253import Data.String (IsString)
5354import qualified Data.Text as T
5455import qualified Data.Text.Encoding as TE
@@ -472,7 +473,7 @@ fullItemObject verbosity item = HM.fromList
472473 , "thread" A..= K.getThreadIdText (K._itemThread item)
473474 , "host" A..= K._itemHost item
474475 , "pid" A..= pidInt
475− , "data" A..= K.payloadObject verbosity (K._itemPayload item)
476+ , "data" A..= annotateKeys (K.payloadObject verbosity (K._itemPayload item))
476477 -- Slight deviation, logz.io uses "message" instead of "msg"
477478 , "message" A..= TB.toLazyText (K.unLogStr (K._itemMessage item))
478479 -- Another slight deviation, logz.io uses "@timestamp" instead of
@@ -617,3 +618,53 @@ bufferItem' customMaxPayload customMaxItem maxItems verb item bulkBuffer =
617618-- will always return 'FlushNow' with an empty new buffer.
618619forceFlush :: (Monoid buf) => buf -> LogAction buf
619620forceFlush buf = FlushNow buf mempty
621+
622+
623+-------------------------------------------------------------------------------
624+-- Annotation borrowed from katip-elasticsearch. There are fixed
625+-- fields in katip logs which should stay the same type forever and
626+-- thus won't need annotation. However, any code in userland may
627+-- choose to add akey to their log's metadata with a type that's
628+-- incompatible with the mapping. If the first value that's picked up
629+-- restrictive (e.g. a long), subsequent values will be rejected. By
630+-- differentiating types by their name, this is guaranteed not to
631+-- happen.
632+
633+annotateValue :: A.Value -> A.Value
634+annotateValue (A.Object o) = A.Object (annotateKeys o)
635+annotateValue (A.Array a) = A.Array (annotateValue <$> a)
636+annotateValue x = x
637+
638+
639+annotateKeys :: A.Object -> A.Object
640+annotateKeys = HM.fromList . map go . HM.toList
641+ where
642+ go (k, A.Object o) = (k, A.Object (annotateKeys o))
643+ go (k, A.Array a) = (k, A.Array (annotateValue <$> a))
644+ go (k, s@(A.String _)) = (k <> stringAnn, s)
645+ go (k, n@(A.Number sci)) = if Scientific.isFloating sci
646+ then (k <> doubleAnn, n)
647+ else (k <> longAnn, n)
648+ go (k, b@(A.Bool _)) = (k <> booleanAnn, b)
649+ go (k, A.Null) = (k <> nullAnn, A.Null)
650+
651+
652+-------------------------------------------------------------------------------
653+-- Annotation Constants
654+-------------------------------------------------------------------------------
655+
656+
657+stringAnn :: T.Text
658+stringAnn = "::s"
659+
660+doubleAnn :: T.Text
661+doubleAnn = "::d"
662+
663+longAnn :: T.Text
664+longAnn = "::l"
665+
666+booleanAnn :: T.Text
667+booleanAnn = "::b"
668+
669+nullAnn :: T.Text
670+nullAnn = "::n"