Skip to content

Commit

Permalink
Add ability to auto-instrument with the OTel Java SDK (#1704)
Browse files Browse the repository at this point in the history
  • Loading branch information
grcevski authored Feb 28, 2025
1 parent 998ab56 commit c9291b2
Show file tree
Hide file tree
Showing 32 changed files with 1,834 additions and 108 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -1201,3 +1201,6 @@ test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/writerc.go
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yaml.go
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yamlh.go
test/integration/components/testserver_1.17/vendor/gopkg.in/yaml.v2/yamlprivateh.go
*_bpfel.go
*_bpfel.o
pkg/internal/otelsdk/grafana-opentelemetry-java.jar
17 changes: 12 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,19 @@ update-offsets: prereqs

# Prefer docker-generate instead, as it's able to perform a parallel build
.PHONY: generate
generate: export BPF_CLANG := $(CLANG)
generate: export BPF_CFLAGS := $(CFLAGS)
generate: export BPF2GO := $(BPF2GO)
generate: bpf2go
generate: bpf-generate javaagent-generate

.PHONY: bpf-generate
bpf-generate: export BPF_CLANG := $(CLANG)
bpf-generate: export BPF_CFLAGS := $(CFLAGS)
bpf-generate: export BPF2GO := $(BPF2GO)
bpf-generate: bpf2go
@echo "### Generating BPF Go bindings"
go generate ./pkg/...
grep -rlI "BPF2GO" pkg/internal/ | xargs -P 0 -n 1 go generate

.PHONY: javaagent-generate
javaagent-generate:
@go generate pkg/internal/otelsdk/sdk_inject.go

.PHONY: docker-generate
docker-generate:
Expand Down
4 changes: 3 additions & 1 deletion generator.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM golang:alpine3.21 AS base
ARG EBPF_VER

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

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

export GENFILES=\$1

go generate pkg/internal/otelsdk/sdk_inject.go

if [ -z "\$GENFILES" ]; then
echo No genfiles specified - regenerating everything
grep -rlI "BPF2GO" pkg/internal/ | xargs -P 0 -n 1 go generate
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/grafana/beyla/v2

go 1.23.0
go 1.23.2

require (
github.com/AlessandroPomponio/go-gibberish v0.0.0-20191004143433-a2d4156f0396
Expand All @@ -17,6 +17,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/grafana/go-offsets-tracker v0.1.7
github.com/grafana/jvmtools v0.0.3
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/go-offsets-tracker v0.1.7 h1:2zBQ7iiGzvyXY7LA8kaaSiEqH/Yx82UcfRabbY5aOG4=
github.com/grafana/go-offsets-tracker v0.1.7/go.mod h1:qcQdu7zlUKIFNUdBJlLyNHuJGW0SKWKjkrN6jtt+jds=
github.com/grafana/jvmtools v0.0.3 h1:4uPquep5v+54Z04OQYOCldrv2SR42IRagHQXrNHsc4g=
github.com/grafana/jvmtools v0.0.3/go.mod h1:b0tiPI5zNyIuPUz2DwvxUCI+5bFoG7A4i47F9sc8w98=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/ebpf_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,7 @@ type EBPFTracer struct {

// Enables debug printing of the protocol data
ProtocolDebug bool `yaml:"protocol_debug_print" env:"BEYLA_PROTOCOL_DEBUG_PRINT"`

// Enables Java instrumentation with the OpenTelemetry JDK Agent
UseOTelSDKForJava bool `yaml:"use_otel_sdk_for_java" env:"BEYLA_USE_OTEL_SDK_FOR_JAVA"`
}
38 changes: 27 additions & 11 deletions pkg/export/otel/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
envProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL"
envHeaders = "OTEL_EXPORTER_OTLP_HEADERS"
envTracesHeaders = "OTEL_EXPORTER_OTLP_TRACES_HEADERS"
envMetricsHeaders = "OTEL_EXPORTER_OTLP_METRICS_HEADERS"
envResourceAttrs = "OTEL_RESOURCE_ATTRIBUTES"
)

Expand Down Expand Up @@ -229,8 +230,7 @@ type otlpOptions struct {
BaseURLPath string
URLPath string
SkipTLSVerify bool
HTTPHeaders map[string]string
GRPCHeaders map[string]string
Headers map[string]string
}

func (o *otlpOptions) AsMetricHTTP() []otlpmetrichttp.Option {
Expand All @@ -246,8 +246,8 @@ func (o *otlpOptions) AsMetricHTTP() []otlpmetrichttp.Option {
if o.SkipTLSVerify {
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))
}
if len(o.HTTPHeaders) > 0 {
opts = append(opts, otlpmetrichttp.WithHeaders(o.HTTPHeaders))
if len(o.Headers) > 0 {
opts = append(opts, otlpmetrichttp.WithHeaders(o.Headers))
}
return opts
}
Expand All @@ -262,8 +262,8 @@ func (o *otlpOptions) AsMetricGRPC() []otlpmetricgrpc.Option {
if o.SkipTLSVerify {
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
}
if len(o.GRPCHeaders) > 0 {
opts = append(opts, otlpmetricgrpc.WithHeaders(o.GRPCHeaders))
if len(o.Headers) > 0 {
opts = append(opts, otlpmetricgrpc.WithHeaders(o.Headers))
}
return opts
}
Expand All @@ -281,8 +281,8 @@ func (o *otlpOptions) AsTraceHTTP() []otlptracehttp.Option {
if o.SkipTLSVerify {
opts = append(opts, otlptracehttp.WithTLSClientConfig(&tls.Config{InsecureSkipVerify: true}))
}
if len(o.HTTPHeaders) > 0 {
opts = append(opts, otlptracehttp.WithHeaders(o.HTTPHeaders))
if len(o.Headers) > 0 {
opts = append(opts, otlptracehttp.WithHeaders(o.Headers))
}
return opts
}
Expand All @@ -297,8 +297,8 @@ func (o *otlpOptions) AsTraceGRPC() []otlptracegrpc.Option {
if o.SkipTLSVerify {
opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})))
}
if len(o.GRPCHeaders) > 0 {
opts = append(opts, otlptracegrpc.WithHeaders(o.GRPCHeaders))
if len(o.Headers) > 0 {
opts = append(opts, otlptracegrpc.WithHeaders(o.Headers))
}
return opts
}
Expand Down Expand Up @@ -359,7 +359,7 @@ func (l *LogrAdaptor) WithName(name string) logr.LogSink {
return &LogrAdaptor{inner: l.inner.With("name", name)}
}

func headersFromEnv(varName string) map[string]string {
func HeadersFromEnv(varName string) map[string]string {
headers := map[string]string{}

addToMap := func(k string, v string) {
Expand Down Expand Up @@ -404,3 +404,19 @@ func ResourceAttrsFromEnv(svc *svc.Attrs) []attribute.KeyValue {
parseOTELEnvVar(svc, envResourceAttrs, apply)
return otelResourceAttrs
}

func ResolveOTLPEndpoint(endpoint, common string, grafana *GrafanaOTLP) (string, bool) {
if endpoint != "" {
return endpoint, false
}

if common != "" {
return common, true
}

if grafana != nil && grafana.CloudZone != "" && grafana.Endpoint() != "" {
return grafana.Endpoint(), true
}

return "", false
}
42 changes: 42 additions & 0 deletions pkg/export/otel/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,45 @@ func TestParseOTELEnvVar_nil(t *testing.T) {

assert.True(t, reflect.DeepEqual(actual, map[string]string{}))
}

func TestResolveOTLPEndpoint(t *testing.T) {
grafana1 := GrafanaOTLP{
CloudZone: "foo",
}

const grafanaEndpoint = "https://otlp-gateway-foo.grafana.net/otlp"

grafana2 := GrafanaOTLP{}

type expected struct {
e string
common bool
}

type testCase struct {
endpoint string
common string
grafana *GrafanaOTLP
expected expected
}

testCases := []testCase{
{endpoint: "e1", common: "c1", grafana: nil, expected: expected{e: "e1", common: false}},
{endpoint: "e1", common: "", grafana: nil, expected: expected{e: "e1", common: false}},
{endpoint: "", common: "c1", grafana: nil, expected: expected{e: "c1", common: true}},
{endpoint: "", common: "", grafana: nil, expected: expected{e: "", common: false}},
{endpoint: "e1", common: "c1", grafana: &grafana1, expected: expected{e: "e1", common: false}},
{endpoint: "", common: "c1", grafana: &grafana1, expected: expected{e: "c1", common: true}},
{endpoint: "", common: "", grafana: &grafana1, expected: expected{e: grafanaEndpoint, common: true}},
{endpoint: "", common: "", grafana: &grafana2, expected: expected{e: "", common: false}},
}

for _, tc := range testCases {
t.Run(fmt.Sprint(tc), func(t *testing.T) {
ep, common := ResolveOTLPEndpoint(tc.endpoint, tc.common, tc.grafana)

assert.Equal(t, ep, tc.expected.e)
assert.Equal(t, common, tc.expected.common)
})
}
}
16 changes: 8 additions & 8 deletions pkg/export/otel/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,18 @@ func (cfg *GrafanaOTLP) AuthHeader() string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(cfg.InstanceID+":"+cfg.APIKey))
}

func (cfg *GrafanaOTLP) HasAuth() bool {
return cfg.InstanceID != "" && cfg.APIKey != ""
}

func (cfg *GrafanaOTLP) setupOptions(opt *otlpOptions) {
if cfg == nil {
return
}
if cfg.InstanceID != "" && cfg.APIKey != "" {
if opt.HTTPHeaders == nil {
opt.HTTPHeaders = map[string]string{}
}
opt.HTTPHeaders["Authorization"] = cfg.AuthHeader()
if opt.GRPCHeaders == nil {
opt.GRPCHeaders = map[string]string{}
if cfg.HasAuth() {
if opt.Headers == nil {
opt.Headers = map[string]string{}
}
opt.GRPCHeaders["Authorization"] = cfg.AuthHeader()
opt.Headers["Authorization"] = cfg.AuthHeader()
}
}
26 changes: 15 additions & 11 deletions pkg/export/otel/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"maps"
"net/url"
"os"
"slices"
Expand Down Expand Up @@ -144,6 +145,10 @@ func (m *MetricsConfig) GuessProtocol() Protocol {
return ProtocolHTTPProtobuf
}

func (m *MetricsConfig) OTLPMetricsEndpoint() (string, bool) {
return ResolveOTLPEndpoint(m.MetricsEndpoint, m.CommonEndpoint, m.Grafana)
}

// EndpointEnabled specifies that the OTEL metrics node is enabled if and only if
// either the OTEL endpoint and OTEL metrics endpoint is defined.
// If not enabled, this node won't be instantiated
Expand Down Expand Up @@ -963,7 +968,7 @@ func (mr *MetricsReporter) reportMetrics(input <-chan []request.Span) {
}

func getHTTPMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
opts := otlpOptions{}
opts := otlpOptions{Headers: map[string]string{}}
log := mlog().With("transport", "http")
murl, isCommon, err := parseMetricsEndpoint(cfg)
if err != nil {
Expand Down Expand Up @@ -996,12 +1001,14 @@ func getHTTPMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
}

cfg.Grafana.setupOptions(&opts)
maps.Copy(opts.Headers, HeadersFromEnv(envHeaders))
maps.Copy(opts.Headers, HeadersFromEnv(envMetricsHeaders))

return opts, nil
}

func getGRPCMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
opts := otlpOptions{}
opts := otlpOptions{Headers: map[string]string{}}
log := mlog().With("transport", "grpc")
murl, _, err := parseMetricsEndpoint(cfg)
if err != nil {
Expand All @@ -1020,6 +1027,11 @@ func getGRPCMetricEndpointOptions(cfg *MetricsConfig) (otlpOptions, error) {
log.Debug("Setting InsecureSkipVerify")
opts.SkipTLSVerify = true
}

cfg.Grafana.setupOptions(&opts)
maps.Copy(opts.Headers, HeadersFromEnv(envHeaders))
maps.Copy(opts.Headers, HeadersFromEnv(envMetricsHeaders))

return opts, nil
}

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

murl, err := url.Parse(endpoint)
if err != nil {
Expand Down
Loading

0 comments on commit c9291b2

Please sign in to comment.