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 +15/−0
- LICENSE +30/−0
- hs-opentelemetry-instrumentation-co-log.cabal +58/−0
- src/OpenTelemetry/Instrumentation/CoLog.hs +138/−0
- test/Spec.hs +71/−0
+ 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"