packages feed

core-telemetry 0.1.6.1 → 0.1.7.0

raw patch · 6 files changed

+192/−22 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Core.Telemetry.Structured: structuredExporter :: Exporter

Files

core-telemetry.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           core-telemetry-version:        0.1.6.1+version:        0.1.7.0 synopsis:       Advanced telemetry description:    This is part of a library to help build command-line programs, both tools and                 longer-running daemons.@@ -37,9 +37,10 @@ library   exposed-modules:       Core.Telemetry-      Core.Telemetry.Observability       Core.Telemetry.Console       Core.Telemetry.Honeycomb+      Core.Telemetry.Observability+      Core.Telemetry.Structured   hs-source-dirs:       lib   ghc-options: -Wall -Wwarn -fwarn-tabs@@ -48,8 +49,8 @@     , base >=4.11 && <5     , bytestring     , chronologique-    , core-data-    , core-program >=0.3.0.7+    , core-data >=0.2.1.11+    , core-program >=0.3.0.8     , core-text >=0.3.5     , exceptions     , http-streams
lib/Core/Telemetry.hs view
@@ -27,8 +27,10 @@     -- Processors to export telemetry to a backend.     module Core.Telemetry.Console,     module Core.Telemetry.Honeycomb,+    module Core.Telemetry.Structured ) where  import Core.Telemetry.Console import Core.Telemetry.Honeycomb import Core.Telemetry.Observability+import Core.Telemetry.Structured
lib/Core/Telemetry/Console.hs view
@@ -3,7 +3,19 @@ {-# LANGUAGE OverloadedStrings #-}  {- |-Implementations of different backends that telemetry can be exported to.+A simple exporter backend that prints your metrics to the terminal as they are+submitted.++Taking the example from 'Core.Telemetry.Observability.telemetry', the output+would be:++@+09:58:54Z (03.755) Process order:+  calories = 667.0+  flavour = true+  meal_name = "hamburger"+  precise = 45.0+@ -} module Core.Telemetry.Console (     consoleExporter,@@ -22,8 +34,11 @@ import Core.Text.Utilities import qualified Data.List as List --- TODO convert this into a Render instance-+{-|+Output metrics to the terminal. This is mostly useful for debugging, but it+can also be used as general output mechanism if your program is mostly+concerned with gathering metrics and displaying them.+-} consoleExporter :: Exporter consoleExporter =     Exporter@@ -40,12 +55,12 @@     let out = outputChannelFrom context     pure         ( Forwarder-            { telemetryHandlerFrom = process out+            { telemetryHandlerFrom = processConsoleOutput out             }         ) -process :: TQueue (Maybe Rope) -> [Datum] -> IO ()-process out datums = do+processConsoleOutput :: TQueue (Maybe Rope) -> [Datum] -> IO ()+processConsoleOutput out datums = do     mapM_ processOne datums   where     processOne :: Datum -> IO ()
lib/Core/Telemetry/Honeycomb.hs view
@@ -4,7 +4,7 @@ {-# OPTIONS_GHC -fno-warn-unused-imports #-}  {- |-A backend exporter that sends telemetry in the form of traces of your+A exporter backend that sends telemetry in the form of traces of your application's behaviour, or event data—accompanied either way by [conceivably very wide] additional metadata—to the Honeycomb observability service. @@ -187,8 +187,6 @@                     (JsonNumber (fromRational (toRational duration / 1e6)))                     meta5 -        -- start = show (fromRational (toRational (spanTimeFrom datum) / 1e9) :: Fixed E9)-        -- meta7 = insertKeyValue "timestamp" (JsonString (intoRope (show (spanTimeFrom datum)))) meta6         time = intoRope (show (spanTimeFrom datum))         point =             JsonObject
lib/Core/Telemetry/Observability.hs view
@@ -43,7 +43,7 @@ Then when you run your program you can pick the exporter:  @-\$ burgerservice --telemetry=console+\$ __burgerservice --telemetry=console__ @  to activate sending telemetry to, in this case, the console. Other exporters@@ -295,7 +295,7 @@ This will allow you to then select the appropriate backend at runtime:  @-\$ burgerservice --telemetry=console+\$ __burgerservice --telemetry=structured__ @  which will result in it spitting out metrics as it goes,@@ -521,11 +521,11 @@ Add measurements to the current span.  @-            telemetry-                [ metric "calories" (667 :: Int)-                , metric "precise" measurement-                , metric "meal_name" ("hamburger" :: Rope)-                , metric "flavour" True+            'telemetry'+                [ 'metric' \"calories\" (667 :: 'Int')+                , 'metric' \"precise\" measurement+                , 'metric' \"meal_name\" ("hamburger" :: 'Rope')+                , 'metric' \"flavour\" 'True'                 ] @ @@ -575,9 +575,9 @@ the in which it occured.  @-            sendEvent+            'sendEvent'                 "Make tea"-                [ metric "sugar" False+                [ 'metric' \"sugar\" 'False'                 ] @ -}
+ lib/Core/Telemetry/Structured.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+An exporter backend that outputs \"structured\" logs to your terminal as they are+submitted.++Most traditional programs' logs were textual, single lines, sometimes with a+reasonably well-known internal layout, but regardless very difficult to+actually perform analysis on. Engineers attempting to diagnose problems are+largely limited to doing text searches across masses of logs. It's hard to+correlate between diffent subsystems let alone perform any sort of statistical+analysis.++Other systems in the past gathered copious amounts of metrics but having done+so, left us with the hard problem of actually doing anything useful with them+other than providing fodder for pretty graphs.++Structured logging was a significant step forward for large-scale systems+administration; by combining metrics together with context in the form of+key/value pairs it allows us to perform more detailed investigation and+analysis that this was largely done by emitting copious amounts of enormously+wasteful JSON is astonishing and goes some way to explain why structured+logging took so long to catch on).++Taking the example from 'Core.Telemetry.Observability.telemetry', the output+would be:++@+\$ __burgerservice --telemetry=structured__+{"calories":667.0,"flavour":true,"meal_name":"hamburger","precise":45.0,"timestamp":"2021-10-22T11:12:53.674399531Z"}+...+@++which if pretty printed would have been more recognizable as++@+{+    "calories": 667.0,+    "flavour": true,+    "meal_name": "hamburger",+    "precise": 45.0,+    "timestamp": "2021-10-22T11:12:53.674399531Z",+}+@++but all that whitespace would be wasteful, right?++While more advanced observability systems will directly ingest this data and+assemble it into traces of nested spans, in other situations having+straight-forward metrics output as JSON may be sufficient for your needs. If+you /do/ use this exporter in a program embellished with traces and spans, the+relevant contextual information will be added to the output:++@+{+    "calories": 667.0,+    "flavour": true,+    "meal_name": "hamburger",+    "precise": 45.0,+    "timestamp": "2021-10-22T11:12:53.674399531Z",+    "duration": 3.756717001,+    "span_id": "o7ucNqCeSJBzeviL",+    "span_name": "Process order",+    "trace_id": "order-11430185",+    "service_name": "burger-service"+}+@+-}+module Core.Telemetry.Structured (+    structuredExporter,+) where++import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TQueue (TQueue, writeTQueue)+import Core.Data.Structures (insertKeyValue)+import Core.Encoding.Json+import Core.Program.Arguments+import Core.Program.Context+import Core.Text.Rope++{- |+Output metrics to @stdout@ in the form of a raw JSON object.+-}+structuredExporter :: Exporter+structuredExporter =+    Exporter+        { codenameFrom = "structured"+        , setupConfigFrom = setupStructuredConfig+        , setupActionFrom = setupStructuredAction+        }++setupStructuredConfig :: Config -> Config+setupStructuredConfig = id++setupStructuredAction :: Context τ -> IO Forwarder+setupStructuredAction context = do+    let out = outputChannelFrom context+    pure+        ( Forwarder+            { telemetryHandlerFrom = processStructuredOutput out+            }+        )++-- almost exact copy of what we use to send to Honeycomb+convertDatumToJson :: Datum -> JsonValue+convertDatumToJson datum =+    let spani = spanIdentifierFrom datum+        trace = traceIdentifierFrom datum+        parent = parentIdentifierFrom datum+        meta0 = attachedMetadataFrom datum++        meta1 = insertKeyValue "span_name" (JsonString (spanNameFrom datum)) meta0++        meta2 = case spani of+            Nothing -> meta1+            Just value -> insertKeyValue "span_id" (JsonString (unSpan value)) meta1++        meta3 = case parent of+            Nothing -> meta2+            Just value -> insertKeyValue "parent_id" (JsonString (unSpan value)) meta2++        meta4 = case trace of+            Nothing -> meta3+            Just value -> insertKeyValue "trace_id" (JsonString (unTrace value)) meta3++        meta5 = case serviceNameFrom datum of+            Nothing -> meta4+            Just service -> insertKeyValue "service_name" (JsonString service) meta4++        meta6 = case durationFrom datum of+            Nothing -> meta5+            Just duration ->+                insertKeyValue+                    "duration"+                    (JsonNumber (fromRational (toRational duration / 1e9)))+                    meta5++        time = intoRope (show (spanTimeFrom datum))+        meta7 = insertKeyValue "timestamp" (JsonString time) meta6+     in JsonObject meta7++processStructuredOutput :: TQueue (Maybe Rope) -> [Datum] -> IO ()+processStructuredOutput out datums = do+    mapM_ processOne datums+  where+    processOne :: Datum -> IO ()+    processOne datum = do+        let object = convertDatumToJson datum+            text = intoRope (encodeToUTF8 object)++        atomically $ do+            writeTQueue out (Just text)