packages feed

hs-opentelemetry-instrumentation-co-log (empty) → 1.0.0.0

raw patch · 5 files changed

+312/−0 lines, 5 filesdep +basedep +co-logdep +co-log-core

Dependencies added: base, co-log, co-log-core, hs-opentelemetry-api, hs-opentelemetry-exporter-in-memory, hs-opentelemetry-instrumentation-co-log, hs-opentelemetry-sdk, hs-opentelemetry-semantic-conventions, hspec, text, unordered-containers, vector

Files

+ ChangeLog.md view
@@ -0,0 +1,15 @@+# Changelog for hs-opentelemetry-instrumentation-co-log++## 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.+- Provides `otelLogAction` for co-log's standard `Message` type and+  `otelLogActionWith` for arbitrary message types.+- Severity mapping: `Debug`→`Debug`, `Info`→`Info`, `Warning`→`Warn`,+  `Error`→`Error`.+- Call-stack source location emitted as `code.filepath`, `code.function.name`,+  and `code.lineno` attributes.
+ 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.
+ hs-opentelemetry-instrumentation-co-log.cabal view
@@ -0,0 +1,58 @@+cabal-version: 2.0+name:               hs-opentelemetry-instrumentation-co-log+version:            1.0.0.0+synopsis:           Bridge co-log to OpenTelemetry Logs+description:+  Provides LogAction values that forward co-log messages to the+  OpenTelemetry Logs pipeline with automatic trace correlation.+  Supports both the standard co-log Message type and arbitrary+  custom message types via a conversion function.+category:           OpenTelemetry, Logging+homepage:           https://github.com/iand675/hs-opentelemetry#readme+bug-reports:        https://github.com/iand675/hs-opentelemetry/issues+author:             Ian Duncan+maintainer:         ian@iankduncan.com+copyright:          2024-2026 Ian Duncan, Mercury Technologies+license:            BSD3+license-file:       LICENSE+build-type:         Simple+extra-doc-files:    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/iand675/hs-opentelemetry++library+  exposed-modules:+      OpenTelemetry.Instrumentation.CoLog+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      base >=4.7 && <5+    , co-log >=0.6 && <0.8+    , co-log-core >=0.3 && <0.4+    , hs-opentelemetry-api ^>= 1.0+    , hs-opentelemetry-semantic-conventions >= 1.40 && < 2+    , text+    , unordered-containers+  default-language: Haskell2010++test-suite hs-opentelemetry-instrumentation-co-log-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , co-log >=0.6 && <0.8+    , co-log-core >=0.3 && <0.4+    , hs-opentelemetry-api ^>= 1.0+    , hs-opentelemetry-exporter-in-memory ^>= 1.0+    , hs-opentelemetry-instrumentation-co-log ^>= 1.0+    , hs-opentelemetry-sdk ^>= 1.0+    , hspec+    , text+    , vector+  default-language: Haskell2010
+ src/OpenTelemetry/Instrumentation/CoLog.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE OverloadedStrings #-}++{- |+Module      : OpenTelemetry.Instrumentation.CoLog+Copyright   : (c) Ian Duncan, 2021-2026+License     : BSD-3+Description : Bridge co-log to OpenTelemetry Logs+Stability   : experimental++Provides 'LogAction' values that forward co-log messages to the+OpenTelemetry Logs pipeline.++* 'otelLogAction' for co-log's standard 'Message' type (from @co-log@),+  which carries severity, call stack, and text.++* 'otelLogActionWith' for arbitrary message types — supply your own+  conversion function.++Trace correlation is automatic: log records emitted inside an+'OpenTelemetry.Trace.Core.inSpan' block carry the active trace\/span IDs.++= Usage with co-log's @Message@++@+import Colog (logInfo)+import Colog.Core (LogAction)+import OpenTelemetry.Log.Core+import OpenTelemetry.Instrumentation.CoLog++main :: IO ()+main = do+  lp <- getGlobalLoggerProvider+  let logger = makeLogger lp (instrumentationLibrary \"my-app\" \"1.0.0\")+      action = otelLogAction logger+  -- use action with your co-log setup+@++= Usage with a custom message type++@+myBridge :: Logger -> LogAction IO Text+myBridge logger = otelLogActionWith logger $ \\txt ->+  emptyLogRecordArguments+    { severityNumber = Just Info+    , body = toValue txt+    }+@++@since 0.1.0.0+-}+module OpenTelemetry.Instrumentation.CoLog (+  otelLogAction,+  otelLogActionWith,+  coLogSeverity,+) where++import Colog.Core (LogAction (..))+import Colog.Core.Severity (Severity (..))+import qualified Colog.Core.Severity as CS+import Colog.Message (Message, Msg (..))+import Control.Monad (void)+import qualified Data.HashMap.Strict as H+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Stack (CallStack, getCallStack, srcLocFile, srcLocModule, srcLocPackage, srcLocStartLine)+import OpenTelemetry.Attributes.Key (unkey)+import OpenTelemetry.Internal.Common.Types (AnyValue (..), ToValue (..))+import OpenTelemetry.Internal.Log.Types (+  LogRecordArguments (..),+  SeverityNumber (..),+  emptyLogRecordArguments,+ )+import OpenTelemetry.Log.Core (Logger, emitLogRecord)+import qualified OpenTelemetry.SemanticConventions as SC+import OpenTelemetry.SemanticsConfig (StabilityOpt (..), codeOption, getSemanticsOptions)+++{- | A 'LogAction' for co-log's standard 'Message' type that forwards+to the OTel Logs pipeline.++@since 0.1.0.0+-}+otelLogAction :: Logger -> LogAction IO Message+otelLogAction logger = LogAction $ \msg -> do+  semOpts <- getSemanticsOptions+  let (sevNum, sevText) = coLogSeverity (msgSeverity msg)+      attrs = callStackAttributes (codeOption semOpts) (msgStack msg)+      args =+        emptyLogRecordArguments+          { severityText = Just sevText+          , severityNumber = Just sevNum+          , body = toValue (msgText msg)+          , attributes = attrs+          }+  void $ emitLogRecord logger args+++{- | A generic 'LogAction' for arbitrary message types. Supply a function+that converts your message to 'LogRecordArguments'.++@since 0.1.0.0+-}+otelLogActionWith :: Logger -> (msg -> LogRecordArguments) -> LogAction IO msg+otelLogActionWith logger toArgs = LogAction $ \msg ->+  void $ emitLogRecord logger (toArgs msg)+++{- | Map co-log 'Severity' to OTel 'SeverityNumber' and short text.++@since 0.1.0.0+-}+coLogSeverity :: Severity -> (SeverityNumber, Text)+coLogSeverity CS.Debug = (OpenTelemetry.Internal.Log.Types.Debug, "DEBUG")+coLogSeverity CS.Info = (OpenTelemetry.Internal.Log.Types.Info, "INFO")+coLogSeverity CS.Warning = (Warn, "WARN")+coLogSeverity CS.Error = (OpenTelemetry.Internal.Log.Types.Error, "ERROR")+++callStackAttributes :: StabilityOpt -> CallStack -> H.HashMap Text AnyValue+callStackAttributes opt cs = case getCallStack cs of+  [] -> H.empty+  ((_, loc) : _) ->+    let qualifiedName = T.pack (srcLocPackage loc) <> ":" <> T.pack (srcLocModule loc)+        stableAttrs =+          [ (unkey SC.code_function_name, toValue qualifiedName)+          , (unkey SC.code_file_path, toValue (T.pack (srcLocFile loc)))+          , (unkey SC.code_line_number, IntValue (fromIntegral (srcLocStartLine loc)))+          ]+        oldAttrs =+          [ (unkey SC.code_function, toValue (T.pack (srcLocModule loc)))+          , (unkey SC.code_namespace, toValue qualifiedName)+          , (unkey SC.code_filepath, toValue (T.pack (srcLocFile loc)))+          , (unkey SC.code_lineno, IntValue (fromIntegral (srcLocStartLine loc)))+          ]+    in H.fromList $ case opt of+         Stable -> stableAttrs+         Old -> oldAttrs+         StableAndOld -> stableAttrs <> oldAttrs
+ test/Spec.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Colog.Core (LogAction (..), (<&))+import Colog.Core.Severity (Severity (..))+import qualified Colog.Core.Severity as CS+import Colog.Message (Message, Msg (..))+import Data.IORef (readIORef)+import qualified Data.Text as T+import GHC.Stack (emptyCallStack)+import OpenTelemetry.Exporter.InMemory.LogRecord (getExportedLogRecords, inMemoryLogRecordExporter)+import OpenTelemetry.Instrumentation.CoLog (coLogSeverity, otelLogAction, otelLogActionWith)+import OpenTelemetry.Internal.Common.Types (AnyValue (..), ToValue (..))+import OpenTelemetry.Internal.Log.Types+import OpenTelemetry.Log.Core+import OpenTelemetry.Processor.Simple.LogRecord (SimpleLogRecordProcessorConfig (..), simpleLogRecordProcessor)+import Test.Hspec+++main :: IO ()+main = hspec spec+++spec :: Spec+spec = describe "co-log bridge" $ do+  describe "coLogSeverity" $ do+    it "maps Debug to Debug" $+      fst (coLogSeverity CS.Debug) `shouldBe` OpenTelemetry.Internal.Log.Types.Debug+    it "maps Info to Info" $+      fst (coLogSeverity CS.Info) `shouldBe` OpenTelemetry.Internal.Log.Types.Info+    it "maps Warning to Warn" $+      fst (coLogSeverity CS.Warning) `shouldBe` Warn+    it "maps Error to Error" $+      fst (coLogSeverity CS.Error) `shouldBe` OpenTelemetry.Internal.Log.Types.Error++  describe "otelLogAction" $ do+    it "emits log records to the OTel pipeline" $ do+      (exporter, ref) <- inMemoryLogRecordExporter+      proc <- simpleLogRecordProcessor (SimpleLogRecordProcessorConfig exporter 30000000)+      lp <- createLoggerProvider [proc] emptyLoggerProviderOptions+      let logger = makeLogger lp (instrumentationLibrary "test-co-log" "0.0.0")+          action = otelLogAction logger+          msg = Msg CS.Info emptyCallStack "hello from co-log"+      action <& msg+      _ <- forceFlushLoggerProvider lp Nothing+      records <- getExportedLogRecords ref+      length records `shouldBe` 1+      let r = head records+      ilr <- readLogRecord r+      toBaseMaybe (logRecordSeverityNumber ilr) `shouldBe` Just OpenTelemetry.Internal.Log.Types.Info+      logRecordBody ilr `shouldBe` TextValue "hello from co-log"++  describe "otelLogActionWith" $ do+    it "converts custom messages via the supplied function" $ do+      (exporter, ref) <- inMemoryLogRecordExporter+      proc <- simpleLogRecordProcessor (SimpleLogRecordProcessorConfig exporter 30000000)+      lp <- createLoggerProvider [proc] emptyLoggerProviderOptions+      let logger = makeLogger lp (instrumentationLibrary "test-co-log" "0.0.0")+          action = otelLogActionWith logger $ \txt ->+            emptyLogRecordArguments+              { severityNumber = Just Warn+              , body = toValue (txt :: T.Text)+              }+      action <& "custom message"+      _ <- forceFlushLoggerProvider lp Nothing+      records <- getExportedLogRecords ref+      length records `shouldBe` 1+      ilr <- readLogRecord (head records)+      toBaseMaybe (logRecordSeverityNumber ilr) `shouldBe` Just Warn+      logRecordBody ilr `shouldBe` TextValue "custom message"