packages feed

hs-opentelemetry-instrumentation-amazonka (empty) → 1.0.0.0

raw patch · 7 files changed

+387/−0 lines, 7 filesdep +amazonkadep +amazonka-coredep +basesetup-changed

Dependencies added: amazonka, amazonka-core, base, bytestring, case-insensitive, hs-opentelemetry-api, hs-opentelemetry-exporter-in-memory, hs-opentelemetry-instrumentation-amazonka, hs-opentelemetry-sdk, hs-opentelemetry-semantic-conventions, hspec, http-client, http-types, text, unordered-containers

Files

+ ChangeLog.md view
@@ -0,0 +1,14 @@+# Changelog for hs-opentelemetry-instrumentation-amazonka++## 1.0.0.0 - 2026-05-29++- Promoted to 1.0.0.0 for the hs-opentelemetry 1.0 release.++## 0.1.0.0++* Initial release+* Hooks-based automatic tracing for all Amazonka `send` calls+* `instrumentEnv` to add tracing hooks to an Amazonka `Env`+* AWS SDK semantic convention attributes (rpc.system, rpc.service, rpc.method, aws.request_id)+* HTTP response attributes (http.response.status_code)+* Error recording on failed AWS calls
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Ian Duncan (c) 2021-2026++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Ian Duncan nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,44 @@+# hs-opentelemetry-instrumentation-amazonka++[![hs-opentelemetry-instrumentation-amazonka](https://img.shields.io/hackage/v/hs-opentelemetry-instrumentation-amazonka?style=flat-square&logo=haskell&label=hs-opentelemetry-instrumentation-amazonka&labelColor=5D4F85)](https://hackage.haskell.org/package/hs-opentelemetry-instrumentation-amazonka)++OpenTelemetry instrumentation for the [Amazonka](https://hackage.haskell.org/package/amazonka) AWS SDK.++## GHC Compatibility++This package requires `amazonka >= 2.0`. As of April 2026, the published+`amazonka-2.0` on Hackage does not compile with GHC 9.10+ due to+`DuplicateRecordFields` changes in generated service packages (`amazonka-sts`,+`amazonka-sso`). You may need to use amazonka from its GitHub `main` branch+or wait for the next Hackage release.++## Usage++```haskell+import Amazonka+import OpenTelemetry.Instrumentation.Amazonka (instrumentEnv)+import OpenTelemetry.Trace (getTracerProvider, makeTracer, tracerOptions)++main :: IO ()+main = do+  tp <- getTracerProvider+  let tracer = makeTracer tp "my-app" tracerOptions+  env <- newEnv discover+  let tracedEnv = instrumentEnv tracer env+  -- All send calls through tracedEnv will create OTel spans+  runResourceT $ send tracedEnv someRequest+```++## Attributes++Per the [OTel AWS SDK semantic conventions](https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/):++| Attribute | Description |+|-----------|-------------|+| `rpc.system` | Always `"aws-api"` |+| `rpc.service` | AWS service abbreviation (e.g., `"S3"`, `"DynamoDB"`) |+| `rpc.method` | AWS operation name (e.g., `"GetObject"`, `"PutItem"`) |+| `aws.request_id` | From response headers when available |+| `http.response.status_code` | HTTP status code |+| `server.address` | AWS endpoint host |+| `cloud.region` | AWS region |
+ Setup.hs view
@@ -0,0 +1,4 @@+import Distribution.Simple+++main = defaultMain
+ hs-opentelemetry-instrumentation-amazonka.cabal view
@@ -0,0 +1,69 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.38.3.+--+-- see: https://github.com/sol/hpack++name:           hs-opentelemetry-instrumentation-amazonka+version:        1.0.0.0+synopsis:       OpenTelemetry instrumentation for the Amazonka AWS SDK+description:    Automatic tracing for Amazonka AWS SDK calls via the hooks API. Creates spans per AWS API call with semantic convention attributes (rpc.system, rpc.service, rpc.method, aws.request_id, etc.).+category:       OpenTelemetry, Telemetry, Monitoring, Observability, AWS+homepage:       https://github.com/iand675/hs-opentelemetry#readme+bug-reports:    https://github.com/iand675/hs-opentelemetry/issues+author:         Ian Duncan, Jade Lovelace+maintainer:     ian@iankduncan.com+copyright:      2024 Ian Duncan, Mercury Technologies+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/iand675/hs-opentelemetry++library+  exposed-modules:+      OpenTelemetry.Instrumentation.Amazonka+  other-modules:+      Paths_hs_opentelemetry_instrumentation_amazonka+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      amazonka ==2.0.*+    , amazonka-core ==2.0.*+    , base >=4.7 && <5+    , bytestring+    , case-insensitive+    , hs-opentelemetry-api ==1.0.*+    , hs-opentelemetry-semantic-conventions >=1.40 && <2+    , http-client+    , http-types+    , text+    , unordered-containers+  default-language: Haskell2010++test-suite hs-opentelemetry-instrumentation-amazonka-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Paths_hs_opentelemetry_instrumentation_amazonka+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      amazonka+    , amazonka-core+    , base >=4.7 && <5+    , hs-opentelemetry-api ==1.0.*+    , hs-opentelemetry-exporter-in-memory ==1.0.*+    , hs-opentelemetry-instrumentation-amazonka ==1.0.*+    , hs-opentelemetry-sdk ==1.0.*+    , hspec+    , text+    , unordered-containers+  default-language: Haskell2010
+ src/OpenTelemetry/Instrumentation/Amazonka.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++{- |+Module      :  OpenTelemetry.Instrumentation.Amazonka+Copyright   :  (c) Ian Duncan, 2024+License     :  BSD-3+Description :  OpenTelemetry instrumentation for the Amazonka AWS SDK+Maintainer  :  Ian Duncan+Stability   :  experimental+Portability :  non-portable (GHC extensions)++Provides automatic tracing for all AWS API calls made through the Amazonka+SDK. Installs hooks on an Amazonka 'Env' that create OTel spans per+@send@\/@sendEither@ call, following the+<https://opentelemetry.io/docs/specs/semconv/cloud-providers/aws-sdk/ OTel AWS SDK semantic conventions>.++@+import Amazonka+import OpenTelemetry.Instrumentation.Amazonka ('instrumentEnv')++main :: IO ()+main = 'OpenTelemetry.Trace.withTracerProvider' $ \\tp -> do+  let tracer = 'OpenTelemetry.Trace.Core.makeTracer' tp "my-app" 'OpenTelemetry.Trace.Core.tracerOptions'+  env <- newEnv discover+  let tracedEnv = 'instrumentEnv' tracer env+  runResourceT $ send tracedEnv someRequest+@++Each @send@ call produces a span named @\"Service.Operation\"@ (e.g.,+@\"S3.GetObject\"@, @\"DynamoDB.PutItem\"@) with standard RPC and AWS+attributes.++@since 0.1.0.0+-}+module OpenTelemetry.Instrumentation.Amazonka (+  instrumentEnv,+  instrumentHooks,+) where++import Amazonka.Data.Text (toText)+import Amazonka.Env (Env, Env' (..))+import Amazonka.Env.Hooks (Finality (..), Hook, Hook_, Hooks (..))+import qualified Amazonka.Types as AWS+import Control.Applicative ((<|>))+import Control.Exception (onException)+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Typeable (Proxy (..), Typeable, tyConName, typeRep, typeRepTyCon)+import qualified Network.HTTP.Client as HTTP+import Network.HTTP.Types (Header)+import qualified Network.HTTP.Types.Status as HTTP+import OpenTelemetry.Attributes (toAttribute)+import OpenTelemetry.Attributes.Key (unkey)+import OpenTelemetry.Context (insertSpan, lookupSpan)+import OpenTelemetry.Context.ThreadLocal (getAndAdjustContext, getContext)+import qualified OpenTelemetry.SemanticConventions as SC+import OpenTelemetry.Trace.Core (+  Span,+  SpanArguments (..),+  SpanKind (Client),+  SpanStatus (Error),+  Tracer,+  addAttributes,+  createSpanWithoutCallStack,+  defaultSpanArguments,+  endSpan,+  setStatus,+ )+import Prelude hiding (error)+++{- | Add OpenTelemetry tracing hooks to an Amazonka 'Env'.++All subsequent @send@ and @sendEither@ calls through the returned 'Env'+will automatically create spans with AWS SDK semantic convention attributes.++The tracing hooks compose with any existing hooks on the 'Env' — they do+not replace them.++@since 0.1.0.0+-}+instrumentEnv :: Tracer -> Env -> Env+instrumentEnv tracer env =+  env {hooks = instrumentHooks tracer (hooks env)}+++{- | Install tracing hooks on an Amazonka 'Hooks' value.++Lower-level than 'instrumentEnv' — use this if you need fine-grained+control over hook composition.++@since 0.1.0.0+-}+instrumentHooks :: Tracer -> Hooks -> Hooks+instrumentHooks tracer baseHooks =+  baseHooks+    { configuredRequest = tracingConfiguredRequest tracer (configuredRequest baseHooks)+    , clientResponse = tracingClientResponse (clientResponse baseHooks)+    , response = tracingResponse (response baseHooks)+    , error = tracingError (error baseHooks)+    }+++tracingConfiguredRequest+  :: forall a+   . (AWS.AWSRequest a, Typeable a)+  => Tracer+  -> Hook (AWS.Request a)+  -> Hook (AWS.Request a)+tracingConfiguredRequest tracer baseHook env req = do+  let svc = (AWS.service req :: AWS.Service)+      svcAbbrev = toText svc.abbrev+      opName = T.pack $ tyConName $ typeRepTyCon $ typeRep (Proxy @a)+      spanName = svcAbbrev <> "." <> opName+      region = AWS.fromRegion (Amazonka.Env.region env)+      endpoint_ = AWS.endpoint (AWS.service req) (Amazonka.Env.region env)+      host = TE.decodeUtf8 (AWS.host endpoint_)++  ctx <- getContext+  span <-+    createSpanWithoutCallStack tracer ctx spanName $+      defaultSpanArguments+        { kind = Client+        , attributes =+            HM.fromList+              [ (unkey SC.rpc_system, toAttribute ("aws-api" :: T.Text))+              , (unkey SC.rpc_service, toAttribute svcAbbrev)+              , (unkey SC.rpc_method, toAttribute opName)+              , (unkey SC.cloud_region, toAttribute region)+              , (unkey SC.server_address, toAttribute host)+              ]+        }++  _ <- getAndAdjustContext (insertSpan span)+  baseHook env req `onException` endSpan span Nothing+++tracingClientResponse+  :: forall a+   . (AWS.AWSRequest a, Typeable a)+  => Hook_ (AWS.Request a, AWS.ClientResponse ())+  -> Hook_ (AWS.Request a, AWS.ClientResponse ())+tracingClientResponse baseHook env arg@(_req, resp) = do+  ctx <- getContext+  case lookupSpan ctx of+    Just span -> do+      let sc = HTTP.statusCode (HTTP.responseStatus resp)+          mReqId = requestIdFromHeaders (HTTP.responseHeaders resp)+          attrs =+            HM.fromList $+              (unkey SC.http_response_statusCode, toAttribute sc)+                : case mReqId of+                  Just reqId -> [(unkey SC.aws_requestId, toAttribute reqId)]+                  Nothing -> []+      addAttributes span attrs+    Nothing -> pure ()+  baseHook env arg+++tracingResponse+  :: forall a+   . (AWS.AWSRequest a, Typeable a)+  => Hook_ (AWS.Request a, AWS.ClientResponse (AWS.AWSResponse a))+  -> Hook_ (AWS.Request a, AWS.ClientResponse (AWS.AWSResponse a))+tracingResponse baseHook env arg = do+  result <- baseHook env arg+  ctx <- getContext+  case lookupSpan ctx of+    Just span -> endSpan span Nothing+    Nothing -> pure ()+  pure result+++tracingError+  :: forall a+   . (AWS.AWSRequest a, Typeable a)+  => Hook_ (Finality, AWS.Request a, AWS.Error)+  -> Hook_ (Finality, AWS.Request a, AWS.Error)+tracingError baseHook env arg@(finality, _req, err) = do+  case finality of+    Final -> do+      ctx <- getContext+      case lookupSpan ctx of+        Just span -> do+          let errDesc = describeError err+              attrs =+                HM.fromList $+                  (unkey SC.error_type, toAttribute errDesc)+                    : case extractServiceRequestId err of+                      Just reqId -> [(unkey SC.aws_requestId, toAttribute reqId)]+                      Nothing -> []+          addAttributes span attrs+          setStatus span (Error errDesc)+          endSpan span Nothing+        Nothing -> pure ()+    NotFinal -> pure ()+  baseHook env arg+++requestIdFromHeaders :: [Header] -> Maybe T.Text+requestIdFromHeaders headers =+  fmap TE.decodeUtf8 (lookup "x-amzn-requestid" headers)+    <|> fmap TE.decodeUtf8 (lookup "x-amzn-request-id" headers)+    <|> fmap TE.decodeUtf8 (lookup "x-amz-request-id" headers)+++extractServiceRequestId :: AWS.Error -> Maybe T.Text+extractServiceRequestId (AWS.ServiceError svcErr) =+  AWS.fromRequestId <$> AWS.requestId svcErr+extractServiceRequestId _ = Nothing+++describeError :: AWS.Error -> T.Text+describeError (AWS.TransportError _) = "transport_error"+describeError (AWS.SerializeError serr) =+  T.pack (serr :: AWS.SerializeError).message+describeError (AWS.ServiceError svcErr) =+  let AWS.ErrorCode code = AWS.code svcErr+  in code
+ test/Spec.hs view
@@ -0,0 +1,2 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+