Skip to content

[OpenTelementry] Fix circular reference#7308

Merged
martincostello merged 10 commits into
open-telemetry:mainfrom
martincostello:fix-circular-reference
Jun 9, 2026
Merged

[OpenTelementry] Fix circular reference#7308
martincostello merged 10 commits into
open-telemetry:mainfrom
martincostello:fix-circular-reference

Conversation

@martincostello

@martincostello martincostello commented May 18, 2026

Copy link
Copy Markdown
Member

Found in #6899.

Changes

Fix circular reference when LoggerProvider requires ILogger.

My only hesitation on this change as-is is the fact that I've had to update two tests outside of OpenTelemetry.Tests to account for this change and that the exception is only now thrown at the point ILogger is resolved and not ILoggerFactory. Thoughts?

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes
  • Changes in public API reviewed (if applicable)

Fix circular reference when `LoggerProvider` requires `ILogger`.
Add CHANGELOG entry.
Comment thread src/OpenTelemetry/CHANGELOG.md Outdated
@github-actions github-actions Bot added the pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package label May 18, 2026
@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.72%. Comparing base (2ea65f2) to head (8341dbf).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #7308      +/-   ##
==========================================
- Coverage   89.81%   89.72%   -0.10%     
==========================================
  Files         276      276              
  Lines       14822    14838      +16     
==========================================
  Hits        13313    13313              
- Misses       1509     1525      +16     
Flag Coverage Δ
unittests-Project-Experimental 89.61% <100.00%> (-0.15%) ⬇️
unittests-Project-Stable 89.61% <100.00%> (-0.20%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...emetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs 94.59% <100.00%> (+1.15%) ⬆️
...try/Logs/ILogger/OpenTelemetryLoggingExtensions.cs 100.00% <100.00%> (ø)

... and 5 files with indirect coverage changes

Resolve an ILogger to force resolution of dependencies.
@github-actions github-actions Bot added the pkg:OpenTelemetry.Exporter.OpenTelemetryProtocol Issues related to OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet package label May 18, 2026
Fix test that stopped throwing `NotSupportedException` to resolve the `ILoggerProvider` and `LoggerFactory` due to deferred creation.
@martincostello martincostello marked this pull request as ready for review May 19, 2026 09:27
@martincostello martincostello requested a review from a team as a code owner May 19, 2026 09:27
Copilot AI review requested due to automatic review settings May 19, 2026 09:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a circular dependency in the logging DI integration when LoggerProvider construction requires ILogger/ILoggerFactory (e.g., via dependencies like IHttpClientFactory). It does so by deferring LoggerProvider resolution until the first logger is actually created, avoiding a re-entrant LoggerProvider build.

Changes:

  • Update OpenTelemetryLoggingExtensions to pass a Func<LoggerProvider> into OpenTelemetryLoggerProvider, delaying LoggerProvider resolution to CreateLogger.
  • Make OpenTelemetryLoggerProvider lazily initialize and cache the LoggerProvider from the provided factory (thread-safe).
  • Update affected tests (and changelog) to trigger the new lazy behavior by creating a logger.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs Defers LoggerProvider resolution via a factory to avoid circular references during DI construction.
src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs Lazily resolves/caches LoggerProvider with synchronization to support deferred DI resolution.
test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs Adjusts assertions and ensures circular-reference scenario is exercised by creating a logger.
test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs Forces logger creation so configuration delegates are exercised under the new lazy-resolution behavior.
test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterHelperExtensionsTests.cs Updates the “throws on gRPC default factory” logging test to trigger failure via CreateLogger.
src/OpenTelemetry/CHANGELOG.md Notes the circular reference fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Update test to assert that creating a logger from the logger provider throws `NotSupportedException`.
@martincostello martincostello added the keep-open Prevents issues and pull requests being closed as stale label May 22, 2026
@martincostello martincostello added the review-priority Should be prioritized for review label Jun 8, 2026
@rajkumar-rangaraj

Copy link
Copy Markdown
Member

Asked Noah and Tarek to take a look at this PR.

@tarekgh

tarekgh commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Thanks for digging this one out, nice find. The overall approach looks right to me: deferring the LoggerProvider resolution out of the DI construction graph and into a lazily-invoked factory on first CreateLogger is a clean way to break the re-entrant build, and since ILoggerProvider is a singleton bound to the root provider, capturing sp/state in the closure is lifetime-safe. Disposal stays correct too (this.provider?.Dispose() only runs when the provider was actually created).

A few things worth considering before this goes in:

1. The deferred exception timing (your stated hesitation). I think this is the most important point to get explicit agreement on. Today a misconfiguration (like the OTLP gRPC default-factory NotSupportedException) fails at composition time, when ILoggerProvider/ILoggerFactory is resolved. After this change it only surfaces when the first log record is written, as the OtlpExporterHelperExtensionsTests updates show. That trades startup fail-fast for a failure that can now happen on a logging code path after the app already looked healthy. It is probably acceptable, but it feels like a behavior change that deserves a maintainer call and maybe a more explicit note in the changelog than "Fix circular reference".

2. Memory model on the lazy field. The Provider getter reads this.provider lock-free on the fast path, but the field is a plain LoggerProvider? (the write happens under the lock). That is the classic double-checked-locking shape that needs the field to be volatile (or accessed via Volatile.Read/Volatile.Write) to be correct on weaker memory models. Low probability in practice, but easy to make airtight.

3. Worth weighing Lazy<LoggerProvider>? It would give the correct barriers for free and trim the hand-rolled locking. The one semantic difference to be deliberate about: with LazyThreadSafetyMode.ExecutionAndPublication a thrown factory exception is cached and rethrown, whereas the current code leaves the factory in place and retries on the next CreateLogger. Given point 1 (the factory can throw), retry-versus-cache is a real choice. If retry is intended, keeping the manual version plus Volatile is perfectly fine.

4. Does CircularReferenceTest fail without the production change? CircularReferenceTest already passes on main, and with the pre-fix eager resolution the ILoggerFactory-first branch builds the provider fully during factory resolution, so the added CreateLogger call would hit an already-populated Provider. That makes me wonder whether the test actually reproduces the cycle on the currently-built target frameworks, or only under the specific environment where it was found. Could you confirm it fails on a reverted build, and on which target framework? If it only repros in that one spot, a short comment in the test saying so would make the guard clear.

None of these block the direction, which I think is correct. Mostly want to make sure the exception-timing change is a conscious decision. Thanks again!

@noahfalk

noahfalk commented Jun 8, 2026

Copy link
Copy Markdown

I don't think I have anything to add to what @tarekgh already called out :)

- Use `Volatile.Read/Write` to access provider.
- Do not cache logging provider resolution failures.
- Update CHANGELOG entry.
- Add explanatory comments.
@martincostello

Copy link
Copy Markdown
Member Author

Comments should now be addressed.

@martincostello martincostello enabled auto-merge June 9, 2026 17:50
@martincostello martincostello added this pull request to the merge queue Jun 9, 2026
Merged via the queue into open-telemetry:main with commit 71e4cb3 Jun 9, 2026
73 checks passed
vgmello added a commit to vgmello/momentum that referenced this pull request Jun 11, 2026
Updated
[OpenTelemetry.Exporter.OpenTelemetryProtocol](https://github.com/open-telemetry/opentelemetry-dotnet)
from 1.15.3 to 1.16.0.

<details>
<summary>Release notes</summary>

_Sourced from [OpenTelemetry.Exporter.OpenTelemetryProtocol's
releases](https://github.com/open-telemetry/opentelemetry-dotnet/releases)._

## 1.16.0

For highlights and announcements pertaining to this release see:
[Release Notes >
1.16.0](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/RELEASENOTES.md#​1160).

The following changes are from the previous release
[1.16.0-rc.1](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.16.0-rc.1).

* NuGet: [OpenTelemetry
v1.16.0](https://www.nuget.org/packages/OpenTelemetry/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Api
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Api/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Api/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Api.ProviderBuilderExtensions
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Api.ProviderBuilderExtensions/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Exporter.Console
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Exporter.Console/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Exporter.Console/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Exporter.InMemory
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Exporter.InMemory/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Exporter.InMemory/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Exporter.OpenTelemetryProtocol
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Exporter.OpenTelemetryProtocol/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Exporter.Zipkin
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Exporter.Zipkin/1.16.0)

  No notable changes.

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0/src/OpenTelemetry.Exporter.Zipkin/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Extensions.Hosting
v1.16.0](https://www.nuget.org/packages/OpenTelemetry.Extensions.Hosting/1.16.0)

  No notable changes.

 ... (truncated)

## 1.16.0-rc.1

The following changes are from the previous release
[1.15.3](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/core-1.15.3).

* NuGet: [OpenTelemetry
v1.16.0-rc.1](https://www.nuget.org/packages/OpenTelemetry/1.16.0-rc.1)

* Stop validating View-provided metric stream `Name` against the
instrument
    name syntax, per
[spec
clarification](open-telemetry/opentelemetry-specification#5094).

([#​7300](open-telemetry/opentelemetry-dotnet#7300))
  
* Fix incorrect validation of `OTEL_BSP_*` and `OTEL_BLRP_*` environment
    variables.

([#​7187](open-telemetry/opentelemetry-dotnet#7187))
  
* Fix observable instrument callbacks running once per reader instead of
    once per collection cycle.

([#​7188](open-telemetry/opentelemetry-dotnet#7188))
  
* Added exception safety for user-supplied `ExemplarReservoir`
implementations.
Exceptions thrown from `Offer` are now caught and logged rather than
propagating
    out of `Counter.Add`/`Histogram.Record`.

([#​7277](open-telemetry/opentelemetry-dotnet#7277))
  
* Update `OpenTelemetrySdkEventSource` to support the W3C randomness
flag.

([#​7301](open-telemetry/opentelemetry-dotnet#7301))
  
  * Added `ObservedTimestamp` property to `LogRecord`.

([#​6979](open-telemetry/opentelemetry-dotnet#6979))
  
* **Breaking Change** Explicit histogram boundaries no longer allow more
than
    10 million values.

([#​7165](open-telemetry/opentelemetry-dotnet#7165))
  
* Fixed a circular reference which could cause a `LoggerProvider` to
fail to
resolve when one of its dependencies depends on `ILogger` or
`ILoggerFactory`.
As part of this fix the `LoggerProvider` resolved from dependency
injection
is now created lazily when the first logger is created rather than when
`ILoggerProvider` or `ILoggerFactory` is resolved. A consequence is that
any
invalid configuration now surfaces when the first log record is written
instead
    of when the logging services are resolved.

([#​7308](open-telemetry/opentelemetry-dotnet#7308))

See
[CHANGELOG](https://github.com/open-telemetry/opentelemetry-dotnet/blob/core-1.16.0-rc.1/src/OpenTelemetry/CHANGELOG.md)
for details.

* NuGet: [OpenTelemetry.Api
v1.16.0-rc.1](https://www.nuget.org/packages/OpenTelemetry.Api/1.16.0-rc.1)

  * **Experimental (pre-release builds only):**
Add support for using environment variables as context propagation
carriers.

([#​7174](open-telemetry/opentelemetry-dotnet#7174))
  
* Fix `BaggagePropagator` to correctly follow Key and Value Encoding
rules as per
 ... (truncated)

## 1.16.0-beta.1

The following changes are from the previous release
[1.15.3-beta.1](https://github.com/open-telemetry/opentelemetry-dotnet/releases/tag/coreunstable-1.15.3-beta.1).

* NuGet: [OpenTelemetry.Exporter.Prometheus.AspNetCore
v1.16.0-beta.1](https://www.nuget.org/packages/OpenTelemetry.Exporter.Prometheus.AspNetCore/1.16.0-beta.1)

* Fixed scrape response cache freshness using monotonic time so it is
not
    affected by NTP system clock adjustments.

([#​7253](open-telemetry/opentelemetry-dotnet#7253))
  
  * **Breaking Change** Removed `DisableTimestamp` property from
    `PrometheusAspNetCoreOptions`.

([#​7176](open-telemetry/opentelemetry-dotnet#7176))
  
* Fixed the serialization of `NaN`, `PositiveInfinity`, and
`NegativeInfinity`
    values in Prometheus metrics to be compliant with the specification.

([#​7179](open-telemetry/opentelemetry-dotnet#7179))
  
* Fixed loss of precision when serializing `double` and `float` values
in
Prometheus metrics to be compliant with the specification by using 17
    significant digits to represent such values.

([#​7179](open-telemetry/opentelemetry-dotnet#7179))
  
* Fix non-ASCII characters in metric names and unit strings not being
sanitized
    correctly during Prometheus serialization.

([#​7184](open-telemetry/opentelemetry-dotnet#7184))
  
* Fix case where reader tracking could be reset while readers were still
active.

([#​7190](open-telemetry/opentelemetry-dotnet#7190))
  
* Improve `Accept` header handling for format negotiation so OpenMetrics
is
    selected correctly by considering whitespace and `q` weights.

([#​7208](open-telemetry/opentelemetry-dotnet#7208))
  
  * Emit OpenMetrics exemplars for counters and histogram buckets.

([#​7222](open-telemetry/opentelemetry-dotnet#7222))
  
* Fix incorrect handling of untyped metrics when using OpenMetrics
format.

([#​7219](open-telemetry/opentelemetry-dotnet#7219))
  
* Fix Prometheus/OpenMetrics serialization to emit metric and label
names
containing `_` instead of dropping them and prefixing leading digits.
    Invalid characters are replaced with `_` instead of being dropped.

([#​7209](open-telemetry/opentelemetry-dotnet#7209))
  
* Add `escaping=underscores` to the `Accept` header handling for content
    negotiation so OpenMetrics are handled correctly.

([#​7209](open-telemetry/opentelemetry-dotnet#7209))
  
* Omit histogram `_sum` and `_count` in OpenMetrics when negative bucket
    thresholds are present.

([#​7221](open-telemetry/opentelemetry-dotnet#7221))
 ... (truncated)

Commits viewable in [compare
view](open-telemetry/opentelemetry-dotnet@core-1.15.3...core-1.16.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=OpenTelemetry.Exporter.OpenTelemetryProtocol&package-manager=nuget&previous-version=1.15.3&new-version=1.16.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vitor M <4777793+vgmello@users.noreply.github.com>
This was referenced Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep-open Prevents issues and pull requests being closed as stale pkg:OpenTelemetry.Exporter.OpenTelemetryProtocol Issues related to OpenTelemetry.Exporter.OpenTelemetryProtocol NuGet package pkg:OpenTelemetry Issues related to OpenTelemetry NuGet package review-priority Should be prioritized for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants