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 +5/−4
- lib/Core/Telemetry.hs +2/−0
- lib/Core/Telemetry/Console.hs +21/−6
- lib/Core/Telemetry/Honeycomb.hs +1/−3
- lib/Core/Telemetry/Observability.hs +9/−9
- lib/Core/Telemetry/Structured.hs +154/−0
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)