Skip to content

Add opt-in OSGi bundle metadata for selected instrumentation, API, and SDK extension artifacts#18995

Open
royteeuwen wants to merge 11 commits into
open-telemetry:mainfrom
royteeuwen:osgi-support
Open

Add opt-in OSGi bundle metadata for selected instrumentation, API, and SDK extension artifacts#18995
royteeuwen wants to merge 11 commits into
open-telemetry:mainfrom
royteeuwen:osgi-support

Conversation

@royteeuwen

@royteeuwen royteeuwen commented Jun 14, 2026

Copy link
Copy Markdown

Resolves #18884.

With open-telemetry/opentelemetry-java#8417 the core modules gained OSGi support. This adds the same, opt-in, for a selected set of artifacts published by this repo.

What this does

Generates OSGi bundle metadata (via the bnd Gradle plugin) so the artifacts can be consumed directly in OSGi runtimes (e.g. Apache Felix / Eclipse Equinox, AEM/Sling) without external wrapper bundles.

OSGi support is opt-in per module: applying the new otel.osgi-conventions plugin is the switch, and it is applied only to modules that are verified to work as bundles by the :smoke-tests-osgi suites:

  • instrumentation-api
  • instrumentation-api-incubator
  • instrumentation-annotations
  • instrumentation-annotations-support
  • instrumentation:apache-httpclient:apache-httpclient-4.3:library
  • instrumentation:logback:logback-appender-1.0:library
  • instrumentation:resources:library

Other library instrumentations and SDK extensions are intentionally left as plain jars for now — most of the value is in the API/SDK artifacts, and library bundles are only useful when the instrumented library itself ships OSGi metadata. Library instrumentations that add classes to the instrumented library's own package (e.g. grpc, lettuce, elasticsearch-rest, nats, rxjava) can't work across separate OSGi bundle class loaders and so are deliberately excluded. Javaagent and shadowed artifacts are untouched.

How

  • Added the bnd Gradle plugin (biz.aQute.bnd:biz.aQute.bnd.gradle:7.3.0) to the convention build, mirroring opentelemetry-java.
  • New otel.osgi-conventions convention plugin configures the jar task's bnd bundle block: -exportcontents: io.opentelemetry.*, an Import-Package that makes javax.annotation (and per-module osgiOptionalPackages) optional, and -metainf-services: auto so SPI providers in META-INF/services (including AutoService-generated ones like the resources ResourceProviders) become OSGi Provide-Capability headers.
  • The plugin is applied explicitly by each opted-in module; OtelJavaExtension.osgiOptionalPackages lets a module mark extra imports optional. Because plugin application is the opt-in switch, there is no osgiEnabled flag and no per-module opt-out.

Tests

New smoke-tests-osgi module that boots a real OSGi container (Apache Felix + Aries SPI Fly) and runs JUnit 5 tests inside it, via bnd's BundleResolveTestOSGi tasks (modeled on opentelemetry-java's integration-tests/osgi). Suites:

  • apiinstrumentation-api + -api-incubator + -annotations + -annotations-support: context propagation, builds an Instrumenter, references @WithSpan. The incubator and annotations-support bundles are rooted in -runrequires so their generated wiring is resolved even though no test class imports them.
  • apacheHttpClientApacheHttpClientTelemetry resolved against the stock Apache httpclient-osgi/httpcore-osgi bundles.
  • logbackAppender — the appender resolves without the optional Logstash encoder present (validating the osgiOptionalPackages tuning).
  • resources — asserts the AutoService ResourceProviders are mediated by SPI Fly (validating -metainf-services: auto).

All four suites pass in-container, and each opted-in module is verified to produce a valid bundle (Bundle-SymbolicName + Export-Package).

Notes

  • osgiOptionalPackages currently tunes the logback appender's optional Logstash import; a future opted-in module may need similar per-module tuning, which surfaces as a clear OSGi resolution error (never silent).
  • The apacheHttpClient suite pulls httpclient-osgi/httpcore-osgi non-transitively because their POMs also drag in the plain (non-OSGi) httpclient/httpcore jars, which otherwise shadow the bundles in the resolver.

Copilot AI review requested due to automatic review settings June 14, 2026 17:49
@royteeuwen
royteeuwen requested a review from a team as a code owner June 14, 2026 17:49
royteeuwen added a commit to royteeuwen/opentelemetry-java-instrumentation that referenced this pull request Jun 14, 2026

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

Note

Copilot was unable to run its full agentic suite in this review.

Adds OSGi bundle metadata generation for published library artifacts and introduces a dedicated :osgi-test module to verify that the produced bundles resolve and run in an Apache Felix OSGi container (including SPI Fly ServiceLoader mediation).

Changes:

  • Introduce otel.osgi-conventions + new otelJava extension knobs (osgiEnabled, osgiOptionalPackages) to generate OSGi manifest metadata via Bnd.
  • Add :osgi-test integration test module with multiple OSGi suites (api, apacheHttpClient, logbackAppender, resources).
  • Adjust specific instrumentation metadata (e.g., logback appender optional imports) and centralize OSGi test dependency versions.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
settings.gradle.kts Registers the new :osgi-test module in the build.
osgi-test/build.gradle.kts Adds Gradle+Bnd wiring to generate/resolve/run OSGi test suites under Felix/SPI Fly.
osgi-test/src/testApi/.../ApiOsgiTest.java Verifies core API/annotations are usable inside OSGi.
osgi-test/src/testApacheHttpClient/.../ApacheHttpClientOsgiTest.java Verifies Apache HttpClient telemetry bundle works in OSGi.
osgi-test/src/testLogbackAppender/.../LogbackAppenderOsgiTest.java Verifies logback appender resolves without optional Logstash packages.
osgi-test/src/testResources/.../ResourcesOsgiTest.java Verifies AutoService/SPI metadata is bridged to OSGi services via SPI Fly.
conventions/src/main/kotlin/otel.osgi-conventions.gradle.kts New convention plugin applying Bnd bundle generation to published libraries.
conventions/src/main/kotlin/io/.../OtelJavaExtension.kt Adds OSGi-related extension properties.
conventions/build.gradle.kts Adds the Bnd Gradle plugin dependency used by the new convention.
dependencyManagement/build.gradle.kts Pins versions for OSGi test runtime + JUnit/Bnd tester.
conventions/src/main/kotlin/otel.library-instrumentation.gradle.kts Applies otel.osgi-conventions to library instrumentation modules.
conventions/src/main/kotlin/otel.sdk-extension.gradle.kts Applies otel.osgi-conventions to SDK extension modules.
instrumentation-api/build.gradle.kts Applies otel.osgi-conventions to instrumentation API.
instrumentation-api-incubator/build.gradle.kts Applies otel.osgi-conventions to incubator API.
instrumentation-annotations/build.gradle.kts Applies otel.osgi-conventions to annotations module.
instrumentation-annotations-support/build.gradle.kts Applies otel.osgi-conventions to annotations support module.
instrumentation/logback/.../library/build.gradle.kts Marks Logstash packages as optional OSGi imports.

Comment thread conventions/src/main/kotlin/otel.osgi-conventions.gradle.kts Outdated
Comment thread settings.gradle.kts Outdated
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Jun 30, 2026

Copy link
Copy Markdown

Pull request dashboard status

Status last refreshed: 2026-07-23 23:33:51 UTC.

  • Waiting on: Maintainers
  • Next step: Merge when ready.

This automated status or its linked feedback items may be incorrect. If something looks wrong, please report it with the result you expected.


init {
minJavaVersionSupported.convention(JavaVersion.VERSION_1_8)
osgiEnabled.convention(true)

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.

Some library instrumentations add classes to the same package as instrumented library. These probably won't work even with the osgi manifests. Should we disable osgi manifest for these instrumentations? Maybe it isn't worth the effort.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. Five library instrumentations put classes in the instrumented library's own package to reach package-private members or class-loader hooks, and those can't work across separate OSGi bundle class loaders:

  • grpc-1.6io.grpc.override.ContextStorageOverride (gRPC loads it by name from its own class loader)
  • lettuce-5.1io.lettuce.core.protocol.OtelCommandArgsUtil (package-private field access)
  • elasticsearch-rest-7.0org.elasticsearch.client.RestClientPackageAccess (package-private Lookup for the proxy)
  • nats-2.17io.nats.client.impl.OpenTelemetryDispatcherFactory (extends package-private DispatcherFactory)
  • rxjava-1.0rx.OpenTelemetryTracingUtil (package-private onSubscribe field)

I've set osgiEnabled = false for all five so they publish a plain manifest rather than a bundle that resolves but then fails at runtime. lettuce and rxjava could be reworked to use reflection and re-enabled later; grpc/nats/elasticsearch would need upstream changes to the instrumented libraries. Done in 3d89b3d.

(Also fixed the plugin so osgiEnabled = false actually removes bnd's buildBundle action from the jar task — previously it only skipped adding the bnd config.)

@laurit laurit added this to the v2.30.0 milestone Jun 30, 2026
@royteeuwen

Copy link
Copy Markdown
Author

@laurit anything else I can do to make this mergeable?

@trask

trask commented Jul 13, 2026

Copy link
Copy Markdown
Member

hi @royteeuwen!

how many of the modules are you (or people you have heard from) using in osgi?

It might be better to make it opt-in instead of opt-out, and only opt-in to the ones actually tested via the smoke test, currently I think these?

:instrumentation-api
:instrumentation-api-incubator
:instrumentation-annotations
:instrumentation:apache-httpclient:apache-httpclient-4.3:library
:instrumentation:logback:logback-appender-1.0:library
:instrumentation:resources:library

@laurit

laurit commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

how many of the modules are you (or people you have heard from) using in osgi?
It might be better to make it opt-in instead of opt-out, and only opt-in to the ones actually tested via the smoke test, currently I think these?

Probably too much of a hassle. It is likely that many of the library instrumentations will work and even if the don't it is not a big deal. I guess the biggest argument for going opt in would be all libraries don't have osgi metadata so us adding it to the corresponding instrumentation library won't help much.

Generate OSGi bundle metadata (via the bnd gradle plugin) for every published
io.opentelemetry.instrumentation library: the instrumentation-api/annotations
modules, all library instrumentations, and SDK extensions. This lets them be
consumed directly in OSGi runtimes, replacing external wrapper bundles.

- conventions: add bnd dependency, osgiEnabled/osgiOptionalPackages on
  OtelJavaExtension, and a new otel.osgi-conventions plugin applied via
  otel.library-instrumentation, otel.sdk-extension and the four API modules.
  Javaagent/shadowed artifacts are intentionally left untouched.
- SPI capabilities are auto-generated from META-INF/services (-metainf-services: auto).
- osgi-test: new module that boots Apache Felix + Aries SPI Fly and runs JUnit 5
  tests inside the container (api, logbackAppender, resources suites).
…che OSGi bundles

The apache-httpclient-4.3 suite now boots Felix and runs ApacheHttpClientTelemetry
against the stock org.apache.httpcomponents.httpclient/httpcore OSGi bundles - the
same bundles AEM/Sling provide. They are pulled non-transitively because their POMs
also drag in the plain (non-OSGi) httpclient/httpcore jars, which otherwise shadow
the bundles in the resolver and leave org.apache.http unexported.

All four suites (api, apacheHttpClient, logbackAppender, resources) now pass fully
in-container.
…assert concrete class resource in logback test
… for split-package modules

Rename the :osgi-test module to :smoke-tests-osgi to match the existing
smoke-tests and smoke-tests-otel-starter naming, and move it under the
smoke tests section in settings.gradle.kts.

Disable OSGi bundle metadata (osgiEnabled = false) for the five library
instrumentations that place classes in the instrumented library's own
package (grpc-1.6, lettuce-5.1, elasticsearch-rest-7.0, nats-2.17,
rxjava-1.0). These rely on package-private access or class-loader hooks
that cannot work across separate OSGi bundle class loaders, so advertising
OSGi support would produce bundles that resolve but fail at runtime.

Make the osgi-conventions plugin remove the bnd buildBundle action when
osgiEnabled is false, so opted-out modules get a plain manifest.
Instead of generating OSGi bundle metadata for every published library,
API and SDK extension artifact (opt-out), apply otel.osgi-conventions
explicitly only to modules verified to work as OSGi bundles by the
:smoke-tests-osgi suites:

- instrumentation-api
- instrumentation-api-incubator
- instrumentation-annotations
- instrumentation-annotations-support
- apache-httpclient-4.3:library
- logback-appender-1.0:library
- resources:library

Plugin application is now the opt-in switch, so the osgiEnabled toggle
and the per-module osgiEnabled=false overrides for split-package
libraries (grpc, lettuce, elasticsearch-rest, nats, rxjava) are removed;
those modules simply don't apply the convention.
@royteeuwen

Copy link
Copy Markdown
Author

@trask @laurit thanks both. I went with the opt-in approach.

OSGi is now opt-in per module: a module is only published as a bundle if it explicitly applies otel.osgi-conventions. I've enabled it for exactly the set that's exercised by the :smoke-tests-osgi suites:

  • :instrumentation-api
  • :instrumentation-api-incubator
  • :instrumentation-annotations
  • :instrumentation-annotations-support
  • :instrumentation:apache-httpclient:apache-httpclient-4.3:library
  • :instrumentation:logback:logback-appender-1.0:library
  • :instrumentation:resources:library

This also covers the real-world usage I have (my AEM/Sling deployment consumes the api, api-incubator and apache-httpclient bundles), so the tested set and the actually-used set line up.

Because plugin application is now the opt-in switch, the osgiEnabled toggle and the per-module osgiEnabled = false overrides are gone — the split-package instrumentations (grpc, lettuce, elasticsearch-rest, nats, rxjava) that laurit flagged simply don't apply the convention, which is a cleaner answer to that concern than a manifest that resolves but fails at runtime.

Verified locally: the 7 modules above build with Bundle-SymbolicName set (and logback keeps net.logstash.logback.*;resolution:=optional), while opted-out modules like grpc build a plain manifest.

Also rebased onto main to clear the merge conflicts. Done in 50968cb.

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

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread conventions/src/main/kotlin/otel.osgi-conventions.gradle.kts
Comment thread smoke-tests-osgi/build.gradle.kts Outdated
Comment thread instrumentation-annotations-support/build.gradle.kts
Comment thread CHANGELOG.md Outdated
@trask trask removed this from the v2.30.0 milestone Jul 20, 2026
@royteeuwen royteeuwen changed the title Add OSGi support for library instrumentation, API, and SDK extension artifacts Add opt-in OSGi bundle metadata for selected instrumentation, API, and SDK extension artifacts Jul 20, 2026
- Root instrumentation-api-incubator and instrumentation-annotations-support
  in the api OSGi suite's -runrequires so they are actually resolved in the
  container (previously available to the resolver but omitted, since no test
  imports their packages). Also add annotations-support to the suite so its
  bundle is exercised.
- Fix the smoke-test -runsystempackages/-runpath matching to match the
  artifact name exactly instead of by prefix: 'opentelemetry-semconv' was
  also matching 'opentelemetry-semconv-incubating' (pulled in by
  annotations-support), which emitted a duplicate io.opentelemetry.semconv
  system package and an ambiguous -runpath entry that broke the framework
  launch.
- Close the CloseableHttpClient in ApacheHttpClientOsgiTest with
  try-with-resources.
- Remove the hand-written CHANGELOG entry: per CONTRIBUTING.md the changelog
  is generated from merged PRs and entries are reserved for deprecations and
  breaking changes.

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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Comment thread smoke-tests-osgi/build.gradle.kts
- Broaden the logback-appender OSGi import ranges to match the versions the
  library actually supports at runtime: the bundle is compiled against
  Logback 1.3 / SLF4J 2.0 but works on Logback 1.0 / SLF4J 1.6.4 (newer APIs
  guarded via reflection). Widen ch.qos.logback.* to [1.0,2) and org.slf4j to
  [1.6,3), and make org.slf4j.event (SLF4J 2.0+ only) optional so its absence
  on older SLF4J does not block resolution. Adds a new osgiImportPackages
  extension knob for explicit per-module Import-Package clauses.
- Add a resolve-only logbackAppenderLegacy smoke suite proving the appender
  bundle resolves against Logback 1.2.13 / SLF4J 1.7.36 (where org.slf4j.event
  2.0 is absent) - would fail if that import were still mandatory.
- Skip the bnd Resolve/TestOSGi tasks under -PskipTests=true. The root build
  only disables Test-typed tasks, so previously the CI build-without-tests
  phase still booted all OSGi containers.

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

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

Comment thread instrumentation/resources/library/build.gradle.kts
ResourceComponentProvider references io.opentelemetry.api.incubator.config
(DeclarativeConfigProperties), but opentelemetry-api-incubator is a compileOnly
optional dependency. bnd emitted a mandatory import for that package, so the
entire resources bundle - including its plain ResourceProviders - failed to
resolve in an OSGi runtime without the optional declarative-config stack. Mark
io.opentelemetry.api.incubator optional, mirroring how the core SDK handles the
same optional dependency.

Add a resolve-only resourcesNoIncubator smoke suite that excludes
opentelemetry-api-incubator and proves the bundle still resolves - it would fail
if that import were still mandatory. The existing resources suite (with the
incubator present) is unchanged.

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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@trask trask left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Produced from a local AI review...

Comment thread instrumentation-api/build.gradle.kts
Comment thread smoke-tests-osgi/build.gradle.kts
Comment thread conventions/src/main/kotlin/otel.osgi-conventions.gradle.kts
…SGi bundle

InternalInstrumenterCustomizerUtil looks up an instrumentation-api-incubator
class via Class.forName and tolerates its absence, but bnd emitted a mandatory
import for io.opentelemetry.instrumentation.api.incubator.instrumenter.internal
(it fell through to the * clause). The stable instrumentation-api bundle could
therefore not resolve without instrumentation-api-incubator. Mark that package
optional. The upstream io.opentelemetry.api.incubator.* imports stay mandatory -
instrumentation-api genuinely requires opentelemetry-api-incubator.

Add a resolve-only apiNoIncubator smoke suite that resolves instrumentation-api
with instrumentation-api-incubator absent - it would fail if that import were
still mandatory. The api suite always roots the incubator bundle and masked it.
@royteeuwen

Copy link
Copy Markdown
Author

Thanks for the detailed review @trask! Went through all three:

1. instrumentation-api mandatory incubator import — good catch, fixed in c26496d6b2. io.opentelemetry.instrumentation.api.incubator.instrumenter.internal (looked up via Class.forName in InternalInstrumenterCustomizerUtil, which tolerates its absence) was falling through to the * clause and becoming mandatory, so the stable bundle couldn't resolve without instrumentation-api-incubator. It's now resolution:=optional; the upstream io.opentelemetry.api.incubator.* imports stay mandatory since instrumentation-api genuinely requires opentelemetry-api-incubator. Added a resolve-only apiNoIncubator smoke suite that resolves instrumentation-api with the incubator bundle absent (the api suite always rooted it and masked this). The same class of issue was also fixed for resources' io.opentelemetry.api.incubator.config import in b6d99955c6, with a matching resourcesNoIncubator suite.

2. semconv system-package masks a deployment prerequisite — correct, and this is the real gate. opentelemetry-semconv isn't an OSGi bundle (no Bundle-SymbolicName/Export-Package), so every bundle here that imports io.opentelemetry.semconv can't resolve in a stock container without it being wrapped or exposed as a system package (what the smoke suite does). The proper fix is upstream — I have open-telemetry/semantic-conventions-java#498 open to add native OSGi metadata to the published semconv jars; once it lands these bundles resolve against a real semconv bundle with no workaround. Would appreciate reviews there. Until then I'm happy to document the interim requirement explicitly rather than have it only in the test harness.

3. -metainf-services: auto doesn't wire ServiceLoader consumer call sites — correct: it only sets up the provider side, and ServiceLoaderUtil's call site can't be woven by SPI Fly anyway because the load goes through a generic Function<Class<?>, Iterable<?>> (no static service type at the call site). I checked whether this affects the bundles this PR ships and don't believe it does: none of the opt-in bundles consume another's SPI via ServiceLoader (only resources provides services, and its consumer is the upstream autoconfigure bundle), and the two consumer SPIs in instrumentation-api-incubator have no library-side providers at all — the only implementations in the repo are javaagent examples/tooling. So in a library/OSGi deployment ServiceLoader.load just returns empty (no customization), the same as a classpath app with none present — no error, no regression. The gap is real for a user shipping their own customizer in a separate bundle. The intended integration point already exists (ServiceLoaderUtil.setLoadFunction(...), which the agent uses), so a small OSGi bridge bundle backing it with the service registry could close it — I'd propose that as a follow-up rather than expanding this PR, since it's a runtime feature with global-static/lifecycle concerns worth their own review. Happy to open it if there's interest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OSGi support

4 participants