hs-opentelemetry-exporter-handle 0.0.1.3 → 1.0.0.0
raw patch · 9 files changed
+297/−17 lines, 9 filesdep +hs-opentelemetry-exporter-handledep +hspecdep +unordered-containersdep ~hs-opentelemetry-apiPVP ok
version bump matches the API change (PVP)
Dependencies added: hs-opentelemetry-exporter-handle, hspec, unordered-containers, vector
Dependency ranges changed: hs-opentelemetry-api
API changes (from Hackage documentation)
+ OpenTelemetry.Exporter.Handle.LogRecord: defaultLogRecordFormatter :: ReadableLogRecord -> IO Text
+ OpenTelemetry.Exporter.Handle.LogRecord: makeHandleLogRecordExporter :: Handle -> (ReadableLogRecord -> IO Text) -> IO LogRecordExporter
+ OpenTelemetry.Exporter.Handle.LogRecord: stderrLogRecordExporter :: IO LogRecordExporter
+ OpenTelemetry.Exporter.Handle.LogRecord: stdoutLogRecordExporter :: IO LogRecordExporter
+ OpenTelemetry.Exporter.Handle.Metric: makeHandleMetricExporter :: Handle -> MetricExporter
+ OpenTelemetry.Exporter.Handle.Metric: stderrMetricExporter :: MonadIO m => m MetricExporter
+ OpenTelemetry.Exporter.Handle.Metric: stdoutMetricExporter :: MonadIO m => m MetricExporter
- OpenTelemetry.Exporter.Handle.Span: defaultFormatter :: ImmutableSpan -> Text
+ OpenTelemetry.Exporter.Handle.Span: defaultFormatter :: ImmutableSpan -> IO Text
Files
- ChangeLog.md +8/−0
- LICENSE +1/−1
- README.md +2/−0
- hs-opentelemetry-exporter-handle.cabal +24/−3
- src/OpenTelemetry/Exporter/Handle.hs +10/−2
- src/OpenTelemetry/Exporter/Handle/LogRecord.hs +93/−1
- src/OpenTelemetry/Exporter/Handle/Metric.hs +53/−0
- src/OpenTelemetry/Exporter/Handle/Span.hs +39/−10
- test/Spec.hs +67/−0
ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for hs-opentelemetry-exporter-handle +## Unreleased++## 1.0.0.0 - 2026-05-29++- `OpenTelemetry.Exporter.Handle` barrel now re-exports all three signals (Span, Metric, LogRecord); deprecated annotation removed+- New `OpenTelemetry.Exporter.Handle.LogRecord` module (console log exporter with default formatter)+- New `OpenTelemetry.Exporter.Handle.Metric` module (console metric exporter)+ ## 0.0.1.3 - Relax `hs-opentelemetry-api` bounds to support 0.3.x
LICENSE view
@@ -1,4 +1,4 @@-Copyright Ian Duncan (c) 2021+Copyright Ian Duncan (c) 2021-2026 All rights reserved.
README.md view
@@ -1,1 +1,3 @@ # hs-opentelemetry-exporter-handle++[](https://hackage.haskell.org/package/hs-opentelemetry-exporter-handle)
hs-opentelemetry-exporter-handle.cabal view
@@ -1,12 +1,14 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.38.3. -- -- see: https://github.com/sol/hpack name: hs-opentelemetry-exporter-handle-version: 0.0.1.3+version: 1.0.0.0+synopsis: Handle-based exporter for OpenTelemetry (e.g. stdout) description: Please see the README on GitHub at <https://github.com/iand675/hs-opentelemetry/tree/main/exporters/handle#readme>+category: OpenTelemetry, Telemetry, Monitoring homepage: https://github.com/iand675/hs-opentelemetry#readme bug-reports: https://github.com/iand675/hs-opentelemetry/issues author: Ian Duncan@@ -27,6 +29,7 @@ exposed-modules: OpenTelemetry.Exporter.Handle OpenTelemetry.Exporter.Handle.LogRecord+ OpenTelemetry.Exporter.Handle.Metric OpenTelemetry.Exporter.Handle.Span other-modules: Paths_hs_opentelemetry_exporter_handle@@ -34,6 +37,24 @@ src build-depends: base >=4.7 && <5- , hs-opentelemetry-api ==0.3.*+ , hs-opentelemetry-api ==1.0.* , text+ , vector+ default-language: Haskell2010++test-suite hs-opentelemetry-exporter-handle-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_hs_opentelemetry_exporter_handle+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , hs-opentelemetry-api ==1.0.*+ , hs-opentelemetry-exporter-handle ==1.0.*+ , hspec+ , unordered-containers+ , vector default-language: Haskell2010
src/OpenTelemetry/Exporter/Handle.hs view
@@ -1,7 +1,15 @@-module OpenTelemetry.Exporter.Handle- {-# DEPRECATED "use OpenTelemetry.Exporter.Handle.Span instead" #-} (+{- |+Module : OpenTelemetry.Exporter.Handle+Description : Re-exports for handle-based (stdout/stderr) exporters.+Stability : experimental+-}+module OpenTelemetry.Exporter.Handle ( module OpenTelemetry.Exporter.Handle.Span,+ module OpenTelemetry.Exporter.Handle.Metric,+ module OpenTelemetry.Exporter.Handle.LogRecord, ) where +import OpenTelemetry.Exporter.Handle.LogRecord+import OpenTelemetry.Exporter.Handle.Metric import OpenTelemetry.Exporter.Handle.Span
src/OpenTelemetry/Exporter/Handle/LogRecord.hs view
@@ -1,2 +1,94 @@-module OpenTelemetry.Exporter.Handle.LogRecord () where+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} +module OpenTelemetry.Exporter.Handle.LogRecord (+ makeHandleLogRecordExporter,+ stdoutLogRecordExporter,+ stderrLogRecordExporter,+ defaultLogRecordFormatter,+) where++import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Data.Text.Lazy as LT+import qualified Data.Text.Lazy.Builder as LB+import Data.Text.Lazy.Builder.Int (decimal)+import Data.Text.Lazy.Builder.RealFloat (realFloat)+import qualified Data.Vector as V+import OpenTelemetry.Internal.Common.Types (ExportResult (..), FlushResult (..), InstrumentationLibrary (..))+import OpenTelemetry.Internal.Log.Types+import OpenTelemetry.LogAttributes (AnyValue (..), LogAttributes (..))+import System.IO (Handle, hFlush, stderr, stdout)+++builderText :: LB.Builder -> T.Text+builderText = LT.toStrict . LB.toLazyText+++textIntegral :: (Integral n) => n -> T.Text+textIntegral = builderText . decimal+++textDouble :: Double -> T.Text+textDouble = builderText . realFloat+++makeHandleLogRecordExporter :: Handle -> (ReadableLogRecord -> IO T.Text) -> IO LogRecordExporter+makeHandleLogRecordExporter h formatter =+ mkLogRecordExporter+ LogRecordExporterArguments+ { logRecordExporterArgumentsExport = \lrs -> do+ V.mapM_ (\lr -> formatter lr >>= T.hPutStrLn h >> hFlush h) lrs+ pure Success+ , logRecordExporterArgumentsForceFlush = hFlush h >> pure FlushSuccess+ , logRecordExporterArgumentsShutdown = hFlush h+ }+++stdoutLogRecordExporter :: IO LogRecordExporter+stdoutLogRecordExporter = makeHandleLogRecordExporter stdout defaultLogRecordFormatter+++stderrLogRecordExporter :: IO LogRecordExporter+stderrLogRecordExporter = makeHandleLogRecordExporter stderr defaultLogRecordFormatter+++defaultLogRecordFormatter :: ReadableLogRecord -> IO T.Text+defaultLogRecordFormatter lr = do+ ImmutableLogRecord {..} <- readLogRecord lr+ let scope_ = readLogRecordInstrumentationScope lr+ let sevText = case logRecordSeverityText of+ UJust s -> s+ UNothing -> "UNSET"+ let bodyText = case logRecordBody of+ TextValue t -> t+ IntValue i -> textIntegral i+ DoubleValue d -> textDouble d+ BoolValue b -> if b then "true" else "false"+ NullValue -> ""+ _ -> T.pack (show logRecordBody)+ let traceInfo = case logRecordTracingDetails of+ TracingDetails tid sid _flags -> " trace=" <> T.pack (show tid) <> " span=" <> T.pack (show sid)+ NoTracingDetails -> ""+ let LogAttributes {attributesCount = attrCount, attributesDropped = droppedCount} = logRecordAttributes+ let attrInfo =+ if attrCount > 0+ then " attrs=" <> textIntegral attrCount <> if droppedCount > 0 then " dropped=" <> textIntegral droppedCount else ""+ else ""+ let eventInfo = case logRecordEventName of+ UJust en -> " event=" <> en+ UNothing -> ""+ pure $+ T.concat+ [ T.pack (show logRecordObservedTimestamp)+ , " "+ , sevText+ , " ["+ , libraryName scope_+ , "]"+ , eventInfo+ , " "+ , bodyText+ , traceInfo+ , attrInfo+ ]
+ src/OpenTelemetry/Exporter/Handle/Metric.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++{- |+Module : OpenTelemetry.Exporter.Handle.Metric+Copyright : (c) Ian Duncan, 2024-2026+License : BSD-3+Description : Console \/ handle metric exporter (@OTEL_METRICS_EXPORTER=console@).+Stability : experimental++Renders each export batch using 'OpenTelemetry.Debug.MetricExport.renderResourceMetricsExportDebug'+and writes the text to the given 'Handle'.+-}+module OpenTelemetry.Exporter.Handle.Metric (+ makeHandleMetricExporter,+ stdoutMetricExporter,+ stderrMetricExporter,+) where++import Control.Monad.IO.Class (MonadIO)+import qualified Data.Text.IO as TIO+import qualified Data.Vector as V+import OpenTelemetry.Debug.MetricExport (renderResourceMetricsExportDebug)+import OpenTelemetry.Exporter.Metric (+ MetricExporter (..),+ )+import OpenTelemetry.Internal.Common.Types (ExportResult (..), FlushResult (..), ShutdownResult (..))+import System.IO (Handle, hFlush, hPutChar, stderr, stdout)+++makeHandleMetricExporter :: Handle -> MetricExporter+makeHandleMetricExporter h =+ MetricExporter+ { metricExporterExport = \batches -> do+ TIO.hPutStr h (renderResourceMetricsExportDebug (V.toList batches))+ hPutChar h '\n'+ hFlush h+ pure Success+ , metricExporterShutdown = do+ hFlush h+ pure ShutdownSuccess+ , metricExporterForceFlush = do+ hFlush h+ pure FlushSuccess+ }+++stdoutMetricExporter :: (MonadIO m) => m MetricExporter+stdoutMetricExporter = pure (makeHandleMetricExporter stdout)+++stderrMetricExporter :: (MonadIO m) => m MetricExporter+stderrMetricExporter = pure (makeHandleMetricExporter stderr)
src/OpenTelemetry/Exporter/Handle/Span.hs view
@@ -2,6 +2,27 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} +{- |+Module : OpenTelemetry.Exporter.Handle.Span+Copyright : (c) Ian Duncan, 2021-2026+License : BSD-3+Description : Export spans as text to a file handle (stdout/stderr)+Stability : experimental++= Overview++Writes spans as human-readable text to a file handle. Useful for local+development and debugging.++= Quick example++@+import OpenTelemetry.Exporter.Handle.Span (stdoutExporter')++exporter <- stdoutExporter'+-- Spans will be printed to stdout as they complete+@+-} module OpenTelemetry.Exporter.Handle.Span ( makeHandleExporter, -- $@@ -13,9 +34,12 @@ import Data.IORef import qualified Data.Text.Lazy as L+import Data.Text.Lazy.Builder (fromString, fromText, toLazyText) import qualified Data.Text.Lazy.IO as L import OpenTelemetry.Exporter.Span+import OpenTelemetry.Internal.Common.Types (FlushResult (..), ShutdownResult (..)) import OpenTelemetry.Trace.Core+import OpenTelemetry.Trace.Id (Base (..), spanIdBaseEncodedText, traceIdBaseEncodedText) import System.IO (Handle, hFlush, stderr, stdout) @@ -25,7 +49,8 @@ { spanExporterExport = \fs -> do mapM_ (mapM_ (\s -> f s >>= L.hPutStrLn h >> hFlush h)) fs pure Success- , spanExporterShutdown = hFlush h+ , spanExporterShutdown = hFlush h >> pure ShutdownSuccess+ , spanExporterForceFlush = hFlush h >> pure FlushSuccess } @@ -37,12 +62,16 @@ stderrExporter' = makeHandleExporter stderr -defaultFormatter :: ImmutableSpan -> L.Text-defaultFormatter ImmutableSpan {..} =- L.intercalate- " "- [ L.pack $ show $ traceId spanContext- , L.pack $ show $ spanId spanContext- , L.pack $ show spanStart- , L.fromStrict spanName- ]+defaultFormatter :: ImmutableSpan -> IO L.Text+defaultFormatter imm = do+ hot <- readIORef (spanHot imm)+ let ctx = spanContext imm+ pure $!+ toLazyText $+ fromText (traceIdBaseEncodedText Base16 (traceId ctx))+ <> " "+ <> fromText (spanIdBaseEncodedText Base16 (spanId ctx))+ <> " "+ <> fromString (show (spanStart imm))+ <> " "+ <> fromText (hotName hot)
+ test/Spec.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import qualified Data.HashMap.Strict as HM+import qualified Data.Vector as V+import OpenTelemetry.Exporter.Handle.LogRecord (makeHandleLogRecordExporter)+import OpenTelemetry.Exporter.Handle.Span (makeHandleExporter)+import OpenTelemetry.Exporter.LogRecord (+ logRecordExporterExport,+ logRecordExporterForceFlush,+ logRecordExporterShutdown,+ )+import OpenTelemetry.Exporter.Span (SpanExporter (..))+import OpenTelemetry.Internal.Common.Types (ExportResult (..))+import System.IO (stdout)+import Test.Hspec+++main :: IO ()+main = hspec spec+++spec :: Spec+spec =+ -- Handle-based exporters (implementation-specific; SpanExporter / LogRecordExporter shape)+ -- https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter+ -- https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter+ do+ describe "Handle Span exporter" $ do+ -- SpanExporter.Export must accept empty batches (SDK contract)+ -- https://opentelemetry.io/docs/specs/otel/trace/sdk/#export-batch+ it "returns Success for empty batch" $ do+ let formatter _span = pure "test"+ exporter = makeHandleExporter stdout formatter+ result <- spanExporterExport exporter HM.empty+ case result of+ Success -> pure ()+ Failure _ -> expectationFailure "expected Success"++ it "forceFlush does not throw" $ do+ let exporter = makeHandleExporter stdout (\_ -> pure "test")+ _ <- spanExporterForceFlush exporter+ pure ()++ it "shutdown does not throw" $ do+ let exporter = makeHandleExporter stdout (\_ -> pure "test")+ _ <- spanExporterShutdown exporter+ pure ()++ describe "Handle LogRecord exporter" $ do+ it "returns Success for empty batch" $ do+ exporter <- makeHandleLogRecordExporter stdout (\_ -> pure "test")+ result <- logRecordExporterExport exporter V.empty+ case result of+ Success -> pure ()+ Failure _ -> expectationFailure "expected Success"++ it "forceFlush does not throw" $ do+ exporter <- makeHandleLogRecordExporter stdout (\_ -> pure "test")+ _ <- logRecordExporterForceFlush exporter+ pure ()++ it "shutdown does not throw" $ do+ exporter <- makeHandleLogRecordExporter stdout (\_ -> pure "test")+ _ <- logRecordExporterShutdown exporter+ pure ()