packages feed

eventuo11y-otel (empty) → 0.1.0.0

raw patch · 4 files changed

+160/−0 lines, 4 filesdep +basedep +eventuo11ydep +hs-opentelemetry-api

Dependencies added: base, eventuo11y, hs-opentelemetry-api, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for eventuo11y-otel++## 0.1.0.0 -- 2023-01-13++Initial release
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright 2023 Shea Levy.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this project except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
+ eventuo11y-otel.cabal view
@@ -0,0 +1,34 @@+cabal-version:      3.0+name:               eventuo11y-otel+version:            0.1.0.0+synopsis:           OpenTelemetry-based rendering for eventuo11y+description:+  Render [eventuo11y](https://hackage.haskell.org/package/eventuo11y) 'Observe.Event.Event's+  to OpenTelemetry traces.++bug-reports:        https://github.com/shlevy/eventuo11y/issues+license:            Apache-2.0+license-file:       LICENSE+author:             Shea Levy+maintainer:         shea@shealevy.com+copyright:          Copyright 2023 Shea Levy.+category:           Observability+extra-source-files: CHANGELOG.md+tested-with:        GHC == { 8.10.7, 9.2.4 }++source-repository head+  type:     git+  location: https://github.com/shlevy/eventuo11y++library+  exposed-modules:+    Observe.Event.Render.OpenTelemetry++  build-depends:+    , base                 ^>= { 4.14, 4.16, 4.17 }+    , eventuo11y           ^>= { 0.7 }+    , hs-opentelemetry-api ^>= { 0.0.3 }+    , text                 ^>= { 1.2, 2.0 }++  hs-source-dirs:   src+  default-language: Haskell2010
+ src/Observe/Event/Render/OpenTelemetry.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}++-- |+-- Description : EventBackend for rendering events as OpenTelemetry traces+-- Copyright   : Copyright 2023 Shea Levy.+-- License     : Apache-2.0+-- Maintainer  : shea@shealevy.com+module Observe.Event.Render.OpenTelemetry where++import Control.Monad.IO.Class+import Data.Text (Text, pack)+import Observe.Event.Backend+import OpenTelemetry.Context+import OpenTelemetry.Context.ThreadLocal+import OpenTelemetry.Trace.Core hiding (Event)+import OpenTelemetry.Trace.Id++-- | An 'EventBackend' built on a 'Tracer'.+--+-- When no explicit parent is set, the backend will try to find a parent in the "OpenTelemetry.Context.ThreadLocal" 'Context'.+-- However, it will never update that 'Context', as the primitive 'EventBackend' API has no way to determine if it's being+-- consumed in a scoped context or one allowing for general interleaving.+--+-- When possible, events created with 'emitImmediateEvent' will use the span event API. However, this requires a parent event+-- (explicitly specified or found in the thread-local 'Context'), so the backend will fallback to creating and 'finalize'ing a new+-- 'Span'. If a span event is created, the resulting 'reference' will be to its parent, as span events cannot be parents/links. Span+-- events do not allow for non-parent links, so any `newEventCauses` are dropped; in the future, we may either add them as custom+-- 'Attribute's or fall back to a full span if any are specified.+--+-- Event 'Link's are currently not given any attributes. In the future, arbitrary link metadata could be added to the core 'EventBackend'+-- API, in which case we could add a renderer for the link metadata type.+--+-- Currently the backend lets the underlying 'Tracer' set all timestamps. In the future, 'RenderSelectorOTel' could be allowed to run in+-- @m@ and have a timestamp field.+--+-- Exceptions passed to 'finalize' are 'recordException'ed without any custom attributes. In the future, an @Exception -> [Text, Attribute]@+-- argument could be added, or arbitrary exception metadata added to 'finalize'.+tracerEventBackend :: (MonadIO m) => Tracer -> RenderSelectorOTel s -> EventBackend m Span s+tracerEventBackend tracer render = backend+  where+    backend =+      EventBackend+        { newEvent = \args@(NewEventArgs {..}) -> do+            ctx <- maybe empty id <$> lookupContext+            let ctx' = case newEventParent of+                  Just s -> insertSpan s ctx+                  Nothing -> ctx+                OTelRendered {..} = render newEventSelector+            links <- traverse (fmap (flip NewLink []) . getSpanContext) newEventCauses+            s <-+              createSpanWithoutCallStack tracer ctx' eventName $+                SpanArguments+                  { kind = eventKind,+                    attributes = concatMap renderField newEventInitialFields,+                    links = links,+                    startTime = Nothing+                  }+            pure $+              Event+                { reference = s,+                  addField = addAttributes s . renderField,+                  finalize = \me -> do+                    let recordError e = do+                          recordException s [("exception.escaped", toAttribute True)] Nothing e+                          setStatus s . Error . pack $ show e+                    maybe (setStatus s Ok) recordError me+                    endSpan s Nothing+                },+          emitImmediateEvent = \args@(NewEventArgs {..}) -> case newEventParent of+            Nothing -> do+              m_ctx <- lookupContext+              case m_ctx >>= lookupSpan of+                Just s ->+                  emitImmediateEvent backend $+                    args+                      { newEventParent = Just s+                      }+                Nothing -> do+                  ev <- newEvent backend args+                  finalize ev Nothing+                  pure $ reference ev+            Just s -> do+              let OTelRendered {..} = render newEventSelector+              addEvent s $+                NewEvent+                  { newEventName = eventName,+                    newEventAttributes = concatMap renderField newEventInitialFields,+                    newEventTimestamp = Nothing+                  }+              pure s+        }++-- | Render a given selector (and all of its fields) to OpenTelemetry+type RenderSelectorOTel s = forall f. s f -> OTelRendered f++-- | The result of rendering a specific selector with field type @f@+data OTelRendered f = OTelRendered+  { -- | The name of the event. See section on "span name" at <https://opentelemetry.io/docs/reference/specification/trace/api/#span>+    eventName :: !Text,+    -- | See the specification on [SpanKind](https://opentelemetry.io/docs/reference/specification/trace/api/#spankind)+    eventKind :: !SpanKind,+    -- | Render a field to a set of span [attributes](https://opentelemetry.io/docs/reference/specification/common/#attribute).+    --+    -- Note especially the [attribute naming guidelines](https://opentelemetry.io/docs/reference/specification/common/attribute-naming/)+    renderField :: !(f -> [(Text, Attribute)])+  }