Skip to content

Feature: Per-service/namespace control for Java and Node.js agent injection #1257

@gk-clode

Description

@gk-clode

Problem Statement

Currently, the Java agent (javaagent) and Node.js agent (nodejs) injectors can only be enabled/disabled globally via:

  • OTEL_EBPF_JAVAAGENT_ENABLED=false
  • OTEL_EBPF_NODEJS_ENABLED=false

This is problematic when:

  1. Some applications have library conflicts - e.g., Neo4j bundles caffeine cache which conflicts with OBI's shaded io.opentelemetry.obi.com.github.benmanes.caffeine.* classes, causing NoClassDefFoundError
  2. Users want eBPF traces but not agent injection for specific services
  3. Different namespaces have different requirements - production apps may need full instrumentation while third-party databases should be excluded

Current Workaround

Users must either:

  1. Disable agents globally (losing functionality for ALL Java/Node.js apps)
  2. Use exclude_instrument to exclude the namespace entirely (losing ALL eBPF traces for that service)

Neither option is ideal.

Proposed Solution

Add per-service agent injection control to the instrument discovery configuration, similar to existing per-service options like exports, sampler, and routes.

Option 1: Unified control

discovery:
  instrument:
    - k8s_namespace: juno
      k8s_statefulset_name: neo4j
      agent_injection: false  # Disable both Java + Node.js agents
      
    - k8s_namespace: production
      agent_injection: true   # Enable (default)

Option 2: Granular control

discovery:
  instrument:
    - k8s_namespace: juno
      javaagent_enabled: false
      nodejs_enabled: true
      
    - k8s_pod_labels:
        app: legacy-app
      javaagent_enabled: false

Option 3: Exclusion list (similar to exclude_instrument)

javaagent:
  enabled: true
  exclude:
    - k8s_namespace: juno
    - k8s_statefulset_name: neo4j

nodejs:
  enabled: true
  exclude:
    - k8s_namespace: kube-system

Implementation Notes

Based on code analysis:

  1. GlobAttributes struct (pkg/appolly/services/attr_glob.go) already supports per-service config for exports, sampler, routes, metrics. Adding javaagent_enabled and nodejs_enabled fields would follow the same pattern.

  2. attacher.go (pkg/appolly/discover/attacher.go:110-115) currently calls injectors unconditionally:

    ta.nodeInjector.NewExecutable(&instr.Obj)
    if ta.javaInjector != nil {
        if err := ta.javaInjector.NewExecutable(&instr.Obj); err != nil {
            // ...
        }
    }

    This would need to check the per-service config before calling the injectors.

  3. Namespace is already available in instr.Obj.FileInfo.Service.UID.Namespace and Metadata[attr.K8sNamespaceName] at injection time.

Use Case

We run Neo4j (a Java application) in Kubernetes. OBI's Java agent injection causes:

java.lang.NoClassDefFoundError: io/opentelemetry/obi/com/github/benmanes/caffeine/cache/BoundedBuffer$RingBuffer

This happens because Neo4j bundles caffeine cache, and OBI's shaded caffeine classes conflict with it.

We want to:

  • ✅ Keep eBPF network-level traces for Neo4j (HTTP, Bolt protocol)
  • ✅ Keep Java agent injection for our own Java microservices
  • ❌ Disable Java agent injection ONLY for Neo4j

Currently impossible without disabling Java agent globally.

Additional Context

  • OBI version: v0.4.1
  • Deployment: Kubernetes DaemonSet
  • Affected application: Neo4j 5.x (uses caffeine cache internally)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions