diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -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.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/hs-opentelemetry-instrumentation-co-log.cabal b/hs-opentelemetry-instrumentation-co-log.cabal
new file mode 100644
--- /dev/null
+++ b/hs-opentelemetry-instrumentation-co-log.cabal
@@ -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
diff --git a/src/OpenTelemetry/Instrumentation/CoLog.hs b/src/OpenTelemetry/Instrumentation/CoLog.hs
new file mode 100644
--- /dev/null
+++ b/src/OpenTelemetry/Instrumentation/CoLog.hs
@@ -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
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -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"
