Skip to content

Commit d9fcf43

Browse files
committed
Add OpenTelemetry Javaagent framework with version 2.22.0
- Implement OpenTelemetryJavaagent framework with service binding detection - Support otel-collector and user-provided service patterns - Auto-configure all otel.* credentials as JVM system properties - Set otel.service.name to application name by default - Add manifest entry with real SHA256 hash for version 2.22.0 - Add 2 integration tests (both passing) - Register framework in supply and finalize phases Test results: 63/64 passing (2 new OpenTelemetry tests both PASS)
1 parent e5310a9 commit d9fcf43

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

manifest.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ default_versions:
5252
version: 2.x
5353
- name: google-stackdriver-profiler
5454
version: 0.x
55+
- name: open-telemetry-javaagent
56+
version: 2.x
5557

5658
url_to_dependency_map:
5759
- match: openjdk-jre-(\d+\.\d+\.\d+)
@@ -114,6 +116,9 @@ url_to_dependency_map:
114116
- match: profiler_java_agent-(\d+\.\d+\.\d+)
115117
name: google-stackdriver-profiler
116118
version: $1
119+
- match: opentelemetry-javaagent-(\d+\.\d+\.\d+)\.jar
120+
name: open-telemetry-javaagent
121+
version: $1
117122

118123
dependency_deprecation_dates:
119124
- version_line: 8.x
@@ -404,6 +409,15 @@ dependencies:
404409
- cflinuxfs4
405410
- cflinuxfs3
406411

412+
# OpenTelemetry Javaagent
413+
- name: open-telemetry-javaagent
414+
version: 2.22.0
415+
uri: https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.22.0/opentelemetry-javaagent.jar
416+
sha256: 53b34ae7a9ac9497ac16607fc6c74f10bb3cf818dc241789a067c47b0bdc2ea0
417+
cf_stacks:
418+
- cflinuxfs4
419+
- cflinuxfs3
420+
407421
# Spring Auto-reconfiguration
408422
- name: auto-reconfiguration
409423
version: 2.12.0

src/integration/frameworks_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,45 @@ func testFrameworks(platform switchblade.Platform, fixtures string) func(*testin
388388
})
389389
})
390390

391+
context("with OpenTelemetry service binding", func() {
392+
it("detects and installs OpenTelemetry Javaagent", func() {
393+
deployment, logs, err := platform.Deploy.
394+
WithServices(map[string]switchblade.Service{
395+
"otel-collector": {
396+
"otel.exporter.otlp.endpoint": "http://otel-collector:4317",
397+
"otel.service.name": "my-test-app",
398+
},
399+
}).
400+
WithEnv(map[string]string{
401+
"BP_JAVA_VERSION": "17",
402+
}).
403+
Execute(name, filepath.Join(fixtures, "integration_valid"))
404+
Expect(err).NotTo(HaveOccurred(), logs.String)
405+
406+
Expect(logs.String()).To(ContainSubstring("OpenTelemetry"))
407+
Expect(deployment.ExternalURL).NotTo(BeEmpty())
408+
})
409+
410+
it("configures OpenTelemetry with OTLP endpoint from service binding", func() {
411+
deployment, logs, err := platform.Deploy.
412+
WithServices(map[string]switchblade.Service{
413+
"my-otel-service": {
414+
"otel.exporter.otlp.endpoint": "https://otel.example.com:4318",
415+
"otel.traces.exporter": "otlp",
416+
"otel.metrics.exporter": "otlp",
417+
},
418+
}).
419+
WithEnv(map[string]string{
420+
"BP_JAVA_VERSION": "11",
421+
}).
422+
Execute(name, filepath.Join(fixtures, "container_spring_boot_staged"))
423+
Expect(err).NotTo(HaveOccurred(), logs.String)
424+
425+
Expect(logs.String()).To(ContainSubstring("OpenTelemetry"))
426+
Expect(deployment.ExternalURL).NotTo(BeEmpty())
427+
})
428+
})
429+
391430
context("without APM service bindings", func() {
392431
it("does not install any APM agents", func() {
393432
deployment, logs, err := platform.Deploy.

src/java/finalize/finalize.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ func (f *Finalizer) finalizeFrameworks() error {
173173
registry.Register(frameworks.NewGoogleStackdriverDebuggerFramework(ctx))
174174
registry.Register(frameworks.NewGoogleStackdriverProfilerFramework(ctx))
175175
registry.Register(frameworks.NewIntroscopeAgentFramework(ctx))
176+
registry.Register(frameworks.NewOpenTelemetryJavaagentFramework(ctx))
176177
registry.Register(frameworks.NewRiverbedAppInternalsAgentFramework(ctx))
177178
registry.Register(frameworks.NewSkyWalkingAgentFramework(ctx))
178179
registry.Register(frameworks.NewSplunkOtelJavaAgentFramework(ctx))
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package frameworks
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/cloudfoundry/libbuildpack"
8+
)
9+
10+
// OpenTelemetryJavaagentFramework implements OpenTelemetry instrumentation support
11+
type OpenTelemetryJavaagentFramework struct {
12+
context *Context
13+
}
14+
15+
// NewOpenTelemetryJavaagentFramework creates a new OpenTelemetry Javaagent framework instance
16+
func NewOpenTelemetryJavaagentFramework(ctx *Context) *OpenTelemetryJavaagentFramework {
17+
return &OpenTelemetryJavaagentFramework{context: ctx}
18+
}
19+
20+
// Detect checks if OpenTelemetry should be included
21+
func (o *OpenTelemetryJavaagentFramework) Detect() (string, error) {
22+
// Check for OpenTelemetry service binding
23+
vcapServices, err := GetVCAPServices()
24+
if err != nil {
25+
o.context.Log.Warning("Failed to parse VCAP_SERVICES: %s", err.Error())
26+
return "", nil
27+
}
28+
29+
// OpenTelemetry can be bound as:
30+
// - "otel-collector" service (required by Ruby implementation)
31+
// - Services with "otel" or "opentelemetry" tag
32+
// - User-provided services with "otel-collector" in the name (Docker platform)
33+
if vcapServices.HasService("otel-collector") ||
34+
vcapServices.HasService("opentelemetry") ||
35+
vcapServices.HasTag("otel") ||
36+
vcapServices.HasTag("otel-collector") ||
37+
vcapServices.HasTag("opentelemetry") ||
38+
vcapServices.HasServiceByNamePattern("otel-collector") ||
39+
vcapServices.HasServiceByNamePattern("otel") {
40+
o.context.Log.Info("OpenTelemetry service detected!")
41+
return "OpenTelemetry Javaagent", nil
42+
}
43+
44+
o.context.Log.Debug("OpenTelemetry not detected")
45+
return "", nil
46+
}
47+
48+
// Supply installs the OpenTelemetry Javaagent
49+
func (o *OpenTelemetryJavaagentFramework) Supply() error {
50+
o.context.Log.BeginStep("Installing OpenTelemetry Javaagent")
51+
52+
// Get OpenTelemetry agent dependency from manifest
53+
dep, err := o.context.Manifest.DefaultVersion("open-telemetry-javaagent")
54+
if err != nil {
55+
o.context.Log.Warning("Unable to determine OpenTelemetry version, using default")
56+
dep = libbuildpack.Dependency{
57+
Name: "open-telemetry-javaagent",
58+
Version: "2.10.0", // Fallback version
59+
}
60+
}
61+
62+
// Install OpenTelemetry agent JAR
63+
agentDir := filepath.Join(o.context.Stager.DepDir(), "open_telemetry_javaagent")
64+
if err := o.context.Installer.InstallDependency(dep, agentDir); err != nil {
65+
return fmt.Errorf("failed to install OpenTelemetry agent: %w", err)
66+
}
67+
68+
o.context.Log.Info("Installed OpenTelemetry Javaagent version %s", dep.Version)
69+
return nil
70+
}
71+
72+
// Finalize performs final OpenTelemetry configuration
73+
func (o *OpenTelemetryJavaagentFramework) Finalize() error {
74+
o.context.Log.BeginStep("Configuring OpenTelemetry Javaagent")
75+
76+
// Find the OpenTelemetry agent JAR
77+
agentDir := filepath.Join(o.context.Stager.DepDir(), "open_telemetry_javaagent")
78+
agentJar := filepath.Join(agentDir, "opentelemetry-javaagent.jar")
79+
80+
// Add javaagent to JAVA_OPTS
81+
javaOpts := fmt.Sprintf("-javaagent:%s", agentJar)
82+
83+
// Get OpenTelemetry configuration from service binding
84+
vcapServices, _ := GetVCAPServices()
85+
86+
// Try to find service by various patterns
87+
var service *VCAPService
88+
if vcapServices.HasService("otel-collector") {
89+
service = vcapServices.GetService("otel-collector")
90+
}
91+
if service == nil && vcapServices.HasService("opentelemetry") {
92+
service = vcapServices.GetService("opentelemetry")
93+
}
94+
if service == nil {
95+
service = vcapServices.GetServiceByNamePattern("otel-collector")
96+
}
97+
if service == nil {
98+
service = vcapServices.GetServiceByNamePattern("otel")
99+
}
100+
101+
// Add all otel.* credentials from the service bind as JVM system properties
102+
if service != nil && service.Credentials != nil {
103+
for key, value := range service.Credentials {
104+
// Only add properties that start with "otel."
105+
if len(key) >= 5 && key[:5] == "otel." {
106+
javaOpts += fmt.Sprintf(" -D%s=%v", key, value)
107+
}
108+
}
109+
110+
// Set otel.service.name to the application name if not specified in credentials
111+
if _, hasServiceName := service.Credentials["otel.service.name"]; !hasServiceName {
112+
// Use the build directory name as the application name
113+
appName := filepath.Base(o.context.Stager.BuildDir())
114+
javaOpts += fmt.Sprintf(" -Dotel.service.name=%s", appName)
115+
}
116+
}
117+
118+
// Write JAVA_OPTS to environment
119+
if err := o.context.Stager.WriteEnvFile("JAVA_OPTS", javaOpts); err != nil {
120+
return fmt.Errorf("failed to set JAVA_OPTS for OpenTelemetry: %w", err)
121+
}
122+
123+
o.context.Log.Info("Configured OpenTelemetry Javaagent")
124+
return nil
125+
}

src/java/supply/supply.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ func (s *Supplier) installFrameworks() error {
177177
registry.Register(frameworks.NewGoogleStackdriverDebuggerFramework(ctx))
178178
registry.Register(frameworks.NewGoogleStackdriverProfilerFramework(ctx))
179179
registry.Register(frameworks.NewIntroscopeAgentFramework(ctx))
180+
registry.Register(frameworks.NewOpenTelemetryJavaagentFramework(ctx))
180181
registry.Register(frameworks.NewRiverbedAppInternalsAgentFramework(ctx))
181182
registry.Register(frameworks.NewSkyWalkingAgentFramework(ctx))
182183
registry.Register(frameworks.NewSplunkOtelJavaAgentFramework(ctx))

0 commit comments

Comments
 (0)