Skip to content

Commit c9291b2

Browse files
authored
Add ability to auto-instrument with the OTel Java SDK (#1704)
1 parent 998ab56 commit c9291b2

32 files changed

+1834
-108
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1201,3 +1201,6 @@ test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/writerc.go
12011201
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yaml.go
12021202
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yamlh.go
12031203
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yamlprivateh.go
1204+
*_bpfel.go
1205+
*_bpfel.o
1206+
pkg/internal/otelsdk/grafana-opentelemetry-java.jar

Makefile

+12-5
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,19 @@ update-offsets: prereqs
183183

184184
# Prefer docker-generate instead, as it's able to perform a parallel build
185185
.PHONY: generate
186-
generate: export BPF_CLANG := $(CLANG)
187-
generate: export BPF_CFLAGS := $(CFLAGS)
188-
generate: export BPF2GO := $(BPF2GO)
189-
generate: bpf2go
186+
generate: bpf-generate javaagent-generate
187+
188+
.PHONY: bpf-generate
189+
bpf-generate: export BPF_CLANG := $(CLANG)
190+
bpf-generate: export BPF_CFLAGS := $(CFLAGS)
191+
bpf-generate: export BPF2GO := $(BPF2GO)
192+
bpf-generate: bpf2go
190193
@echo "### Generating BPF Go bindings"
191-
go generate ./pkg/...
194+
grep -rlI "BPF2GO" pkg/internal/ | xargs -P 0 -n 1 go generate
195+
196+
.PHONY: javaagent-generate
197+
javaagent-generate:
198+
@go generate pkg/internal/otelsdk/sdk_inject.go
192199

193200
.PHONY: docker-generate
194201
docker-generate:

generator.Dockerfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ FROM golang:alpine3.21 AS base
33
ARG EBPF_VER
44

55
# Installs dependencies that are required to compile eBPF programs
6-
RUN apk add clang llvm19
6+
RUN apk add clang llvm19 curl
77
RUN apk cache purge
88
RUN go install github.com/cilium/ebpf/cmd/bpf2go@$EBPF_VER
99

@@ -21,6 +21,8 @@ export BPF_CFLAGS="-O2 -g -Wall -Werror"
2121

2222
export GENFILES=\$1
2323

24+
go generate pkg/internal/otelsdk/sdk_inject.go
25+
2426
if [ -z "\$GENFILES" ]; then
2527
echo No genfiles specified - regenerating everything
2628
grep -rlI "BPF2GO" pkg/internal/ | xargs -P 0 -n 1 go generate

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/grafana/beyla/v2
22

3-
go 1.23.0
3+
go 1.23.2
44

55
require (
66
github.com/AlessandroPomponio/go-gibberish v0.0.0-20191004143433-a2d4156f0396
@@ -17,6 +17,7 @@ require (
1717
github.com/google/uuid v1.6.0
1818
github.com/gorilla/mux v1.8.1
1919
github.com/grafana/go-offsets-tracker v0.1.7
20+
github.com/grafana/jvmtools v0.0.3
2021
github.com/hashicorp/go-version v1.7.0
2122
github.com/hashicorp/golang-lru/v2 v2.0.7
2223
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
110110
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
111111
github.com/grafana/go-offsets-tracker v0.1.7 h1:2zBQ7iiGzvyXY7LA8kaaSiEqH/Yx82UcfRabbY5aOG4=
112112
github.com/grafana/go-offsets-tracker v0.1.7/go.mod h1:qcQdu7zlUKIFNUdBJlLyNHuJGW0SKWKjkrN6jtt+jds=
113+
github.com/grafana/jvmtools v0.0.3 h1:4uPquep5v+54Z04OQYOCldrv2SR42IRagHQXrNHsc4g=
114+
github.com/grafana/jvmtools v0.0.3/go.mod h1:b0tiPI5zNyIuPUz2DwvxUCI+5bFoG7A4i47F9sc8w98=
113115
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
114116
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
115117
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=

pkg/config/ebpf_tracer.go

+3
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,7 @@ type EBPFTracer struct {
5959

6060
// Enables debug printing of the protocol data
6161
ProtocolDebug bool `yaml:"protocol_debug_print" env:"BEYLA_PROTOCOL_DEBUG_PRINT"`
62+
63+
// Enables Java instrumentation with the OpenTelemetry JDK Agent
64+
UseOTelSDKForJava bool `yaml:"use_otel_sdk_for_java" env:"BEYLA_USE_OTEL_SDK_FOR_JAVA"`
6265
}

pkg/export/otel/common.go

+27-11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
envProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL"
4646
envHeaders = "OTEL_EXPORTER_OTLP_HEADERS"
4747
envTracesHeaders = "OTEL_EXPORTER_OTLP_TRACES_HEADERS"
48+
envMetricsHeaders = "OTEL_EXPORTER_OTLP_METRICS_HEADERS"
4849
envResourceAttrs = "OTEL_RESOURCE_ATTRIBUTES"
4950
)
5051

@@ -229,8 +230,7 @@ type otlpOptions struct {
229230
BaseURLPath string
230231
URLPath string
231232
SkipTLSVerify bool
232-
HTTPHeaders map[string]string
233-
GRPCHeaders map[string]string
233+
Headers map[string]string
234234
}
235235

236236
func (o *otlpOptions) AsMetricHTTP() []otlpmetrichttp.Option {
@@ -246,8 +246,8 @@ func (o *otlpOptions) AsMetricHTTP() []otlpmetrichttp.Option {
246246
if o.SkipTLSVerify {
247247
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))
248248
}
249-
if len(o.HTTPHeaders) > 0 {
250-
opts = append(opts, otlpmetrichttp.WithHeaders(o.HTTPHeaders))
249+
if len(o.Headers) > 0 {
250+
opts = append(opts, otlpmetrichttp.WithHeaders(o.Headers))
251251
}
252252
return opts
253253
}
@@ -262,8 +262,8 @@ func (o *otlpOptions) AsMetricGRPC() []otlpmetricgrpc.Option {
262262
if o.SkipTLSVerify {
263263
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
264264
}
265-
if len(o.GRPCHeaders) > 0 {
266-
opts = append(opts, otlpmetricgrpc.WithHeaders(o.GRPCHeaders))
265+
if len(o.Headers) > 0 {
266+
opts = append(opts, otlpmetricgrpc.WithHeaders(o.Headers))
267267
}
268268
return opts
269269
}
@@ -281,8 +281,8 @@ func (o *otlpOptions) AsTraceHTTP() []otlptracehttp.Option {
281281
if o.SkipTLSVerify {
282282
opts = append(opts, otlptracehttp.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))
283283
}
284-
if len(o.HTTPHeaders) > 0 {
285-
opts = append(opts, otlptracehttp.WithHeaders(o.HTTPHeaders))
284+
if len(o.Headers) > 0 {
285+
opts = append(opts, otlptracehttp.WithHeaders(o.Headers))
286286
}
287287
return opts
288288
}
@@ -297,8 +297,8 @@ func (o *otlpOptions) AsTraceGRPC() []otlptracegrpc.Option {
297297
if o.SkipTLSVerify {
298298
opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
299299
}
300-
if len(o.GRPCHeaders) > 0 {
301-
opts = append(opts, otlptracegrpc.WithHeaders(o.GRPCHeaders))
300+
if len(o.Headers) > 0 {
301+
opts = append(opts, otlptracegrpc.WithHeaders(o.Headers))
302302
}
303303
return opts
304304
}
@@ -359,7 +359,7 @@ func (l *LogrAdaptor) WithName(name string) logr.LogSink {
359359
return &LogrAdaptor{inner: l.inner.With("name", name)}
360360
}
361361

362-
func headersFromEnv(varName string) map[string]string {
362+
func HeadersFromEnv(varName string) map[string]string {
363363
headers := map[string]string{}
364364

365365
addToMap := func(k string, v string) {
@@ -404,3 +404,19 @@ func ResourceAttrsFromEnv(svc *svc.Attrs) []attribute.KeyValue {
404404
parseOTELEnvVar(svc, envResourceAttrs, apply)
405405
return otelResourceAttrs
406406
}
407+
408+
func ResolveOTLPEndpoint(endpoint, common string, grafana *GrafanaOTLP) (string, bool) {
409+
if endpoint != "" {
410+
return endpoint, false
411+
}
412+
413+
if common != "" {
414+
return common, true
415+
}
416+
417+
if grafana != nil && grafana.CloudZone != "" && grafana.Endpoint() != "" {
418+
return grafana.Endpoint(), true
419+
}
420+
421+
return "", false
422+
}

pkg/export/otel/common_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,45 @@ func TestParseOTELEnvVar_nil(t *testing.T) {
186186

187187
assert.True(t, reflect.DeepEqual(actual, map[string]string{}))
188188
}
189+
190+
func TestResolveOTLPEndpoint(t *testing.T) {
191+
grafana1 := GrafanaOTLP{
192+
CloudZone: "foo",
193+
}
194+
195+
const grafanaEndpoint = "https://otlp-gateway-foo.grafana.net/otlp"
196+
197+
grafana2 := GrafanaOTLP{}
198+
199+
type expected struct {
200+
e string
201+
common bool
202+
}
203+
204+
type testCase struct {
205+
endpoint string
206+
common string
207+
grafana *GrafanaOTLP
208+
expected expected
209+
}
210+
211+
testCases := []testCase{
212+
{endpoint: "e1", common: "c1", grafana: nil, expected: expected{e: "e1", common: false}},
213+
{endpoint: "e1", common: "", grafana: nil, expected: expected{e: "e1", common: false}},
214+
{endpoint: "", common: "c1", grafana: nil, expected: expected{e: "c1", common: true}},
215+
{endpoint: "", common: "", grafana: nil, expected: expected{e: "", common: false}},
216+
{endpoint: "e1", common: "c1", grafana: &grafana1, expected: expected{e: "e1", common: false}},
217+
{endpoint: "", common: "c1", grafana: &grafana1, expected: expected{e: "c1", common: true}},
218+
{endpoint: "", common: "", grafana: &grafana1, expected: expected{e: grafanaEndpoint, common: true}},
219+
{endpoint: "", common: "", grafana: &grafana2, expected: expected{e: "", common: false}},
220+
}
221+
222+
for _, tc := range testCases {
223+
t.Run(fmt.Sprint(tc), func(t *testing.T) {
224+
ep, common := ResolveOTLPEndpoint(tc.endpoint, tc.common, tc.grafana)
225+
226+
assert.Equal(t, ep, tc.expected.e)
227+
assert.Equal(t, common, tc.expected.common)
228+
})
229+
}
230+
}

pkg/export/otel/grafana.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,18 @@ func (cfg *GrafanaOTLP) AuthHeader() string {
7777
return "Basic " + base64.StdEncoding.EncodeToString([]byte(cfg.InstanceID+":"+cfg.APIKey))
7878
}
7979

80+
func (cfg *GrafanaOTLP) HasAuth() bool {
81+
return cfg.InstanceID != "" && cfg.APIKey != ""
82+
}
83+
8084
func (cfg *GrafanaOTLP) setupOptions(opt *otlpOptions) {
8185
if cfg == nil {
8286
return
8387
}
84-
if cfg.InstanceID != "" && cfg.APIKey != "" {
85-
if opt.HTTPHeaders == nil {
86-
opt.HTTPHeaders = map[string]string{}
87-
}
88-
opt.HTTPHeaders["Authorization"] = cfg.AuthHeader()
89-
if opt.GRPCHeaders == nil {
90-
opt.GRPCHeaders = map[string]string{}
88+
if cfg.HasAuth() {
89+
if opt.Headers == nil {
90+
opt.Headers = map[string]string{}
9191
}
92-
opt.GRPCHeaders["Authorization"] = cfg.AuthHeader()
92+
opt.Headers["Authorization"] = cfg.AuthHeader()
9393
}
9494
}

pkg/export/otel/metrics.go

+15-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"maps"
78
"net/url"
89
"os"
910
"slices"
@@ -144,6 +145,10 @@ func (m *MetricsConfig) GuessProtocol() Protocol {
144145
return ProtocolHTTPProtobuf
145146
}
146147

148+
func (m *MetricsConfig) OTLPMetricsEndpoint() (string, bool) {
149+
return ResolveOTLPEndpoint(m.MetricsEndpoint, m.CommonEndpoint, m.Grafana)
150+
}
151+
147152
// EndpointEnabled specifies that the OTEL metrics node is enabled if and only if
148153
// either the OTEL endpoint and OTEL metrics endpoint is defined.
149154
// If not enabled, this node won't be instantiated
@@ -963,7 +968,7 @@ func (mr *MetricsReporter) reportMetrics(input <-chan []request.Span) {
963968
}
964969

965970
func getHTTPMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
966-
opts := otlpOptions{}
971+
opts := otlpOptions{Headers: map[string]string{}}
967972
log := mlog().With("transport", "http")
968973
murl, isCommon, err := parseMetricsEndpoint(cfg)
969974
if err != nil {
@@ -996,12 +1001,14 @@ func getHTTPMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
9961001
}
9971002

9981003
cfg.Grafana.setupOptions(&opts)
1004+
maps.Copy(opts.Headers, HeadersFromEnv(envHeaders))
1005+
maps.Copy(opts.Headers, HeadersFromEnv(envMetricsHeaders))
9991006

10001007
return opts, nil
10011008
}
10021009

10031010
func getGRPCMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
1004-
opts := otlpOptions{}
1011+
opts := otlpOptions{Headers: map[string]string{}}
10051012
log := mlog().With("transport", "grpc")
10061013
murl, _, err := parseMetricsEndpoint(cfg)
10071014
if err != nil {
@@ -1020,6 +1027,11 @@ func getGRPCMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
10201027
log.Debug("Setting InsecureSkipVerify")
10211028
opts.SkipTLSVerify = true
10221029
}
1030+
1031+
cfg.Grafana.setupOptions(&opts)
1032+
maps.Copy(opts.Headers, HeadersFromEnv(envHeaders))
1033+
maps.Copy(opts.Headers, HeadersFromEnv(envMetricsHeaders))
1034+
10231035
return opts, nil
10241036
}
10251037

@@ -1030,15 +1042,7 @@ func getGRPCMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
10301042
// If, by some reason, Grafana changes its OTLP Gateway URL in a distant future, you can still point to the
10311043
// correct URL with the OTLP_EXPORTER_... variables.
10321044
func parseMetricsEndpoint(cfg *MetricsConfig) (*url.URL, bool, error) {
1033-
isCommon := false
1034-
endpoint := cfg.MetricsEndpoint
1035-
if endpoint == "" {
1036-
isCommon = true
1037-
endpoint = cfg.CommonEndpoint
1038-
if endpoint == "" && cfg.Grafana != nil && cfg.Grafana.CloudZone != "" {
1039-
endpoint = cfg.Grafana.Endpoint()
1040-
}
1041-
}
1045+
endpoint, isCommon := cfg.OTLPMetricsEndpoint()
10421046

10431047
murl, err := url.Parse(endpoint)
10441048
if err != nil {

0 commit comments

Comments
 (0)