Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 87 additions & 2 deletions cmd/root/otel_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,104 @@
package root

import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
semconv "go.opentelemetry.io/otel/semconv/v1.43.0"
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
"google.golang.org/protobuf/proto"
)

// A literal, not semconv.SchemaURL, so a semconv bump is an explicit,
// reviewed change. NoError also guards against ErrSchemaURLConflict if
// resource.Default()'s semconv version diverges.
func TestNewOTelResourceUsesCurrentSchemaURL(t *testing.T) {
t.Parallel()

res, err := newOTelResource()
require.NoError(t, err)
assert.Equal(t, semconv.SchemaURL, res.SchemaURL())
assert.Equal(t, "https://opentelemetry.io/schemas/1.43.0", res.SchemaURL())
}

// TestTraceExportEmitsResourceSchemaURL verifies the schema URL on the
// wire: the OTLP payload's resource schema_url must be the documented
// literal, while the scope schema_url stays empty because docker-agent
// sets none on its own tracers.
func TestTraceExportEmitsResourceSchemaURL(t *testing.T) {
// Not parallel: t.Setenv. Clear env that would alter sampling or
// body encoding; endpoint env vars are already overridden by the
// explicit WithEndpointURL.
for _, key := range []string{
"OTEL_TRACES_SAMPLER",
"OTEL_TRACES_SAMPLER_ARG",
"OTEL_EXPORTER_OTLP_COMPRESSION",
"OTEL_EXPORTER_OTLP_TRACES_COMPRESSION",
} {
t.Setenv(key, "")
os.Unsetenv(key)
}

// Empty ExportTraceServiceResponse: the full-success OTLP reply.
respBody, err := proto.Marshal(&coltracepb.ExportTraceServiceResponse{})
require.NoError(t, err)

type export struct {
path string
body []byte
readErr error
}
exports := make(chan export, 4)

// No assertions here — the handler runs on a server goroutine.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, readErr := io.ReadAll(r.Body)
select {
case exports <- export{path: r.URL.Path, body: body, readErr: readErr}:
default:
}
w.Header().Set("Content-Type", "application/x-protobuf")
_, _ = w.Write(respBody)
}))
t.Cleanup(srv.Close)

res, err := newOTelResource()
require.NoError(t, err)
tp, err := newTracerProvider(t.Context(), res, srv.URL)
require.NoError(t, err)
t.Cleanup(func() {
// t.Context() is already canceled in cleanup; WithoutCancel
// detaches it so shutdown still gets a bounded 5s window.
ctx, cancel := context.WithTimeout(context.WithoutCancel(t.Context()), 5*time.Second)
defer cancel()
_ = tp.Shutdown(ctx)
})

_, span := tp.Tracer("test").Start(t.Context(), "schema-url-probe")
span.End()
require.NoError(t, tp.ForceFlush(t.Context()))

var got export
select {
case got = <-exports:
case <-time.After(10 * time.Second):
t.Fatal("no OTLP trace export received")
}
require.NoError(t, got.readErr)
assert.Equal(t, "/v1/traces", got.path)

var req coltracepb.ExportTraceServiceRequest
require.NoError(t, proto.Unmarshal(got.body, &req))
require.Len(t, req.ResourceSpans, 1)
rs := req.ResourceSpans[0]
assert.Equal(t, "https://opentelemetry.io/schemas/1.43.0", rs.SchemaUrl)
require.Len(t, rs.ScopeSpans, 1)
assert.Empty(t, rs.ScopeSpans[0].SchemaUrl)
}

// TestProvidersWithoutEndpoint verifies all three providers build cleanly
Expand Down
17 changes: 17 additions & 0 deletions docs/community/opentelemetry/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,23 @@ docker agent run agent.yaml --otel
>
> Both backends accept the traces signal only. Docker Agent also wires metric and log exporters at the same endpoint, so their periodic exports return `404` against trace-only backends. This is harmless to traces but appears in the debug log. Point a full OTLP collector at the endpoint if you also want metrics and logs.

## Schema URL

Every OTLP export — traces, metrics, and logs — carries a resource `schema_url` that declares which version of the [semantic conventions](https://opentelemetry.io/docs/specs/semconv/) the resource attributes follow. Docker Agent emits:

```text
https://opentelemetry.io/schemas/1.43.0
```

The URL resolves to the published [1.43.0 schema](https://opentelemetry.io/schemas/1.43.0) and identifies the conventions the data was produced under. Docker Agent sets no schema URL on its own tracer, meter, or logger scopes, so their instrumentation-scope `schema_url` fields are empty. Bundled instrumentation can set one — the ADK tracer (`gcp.vertex.agent`, active when serving A2A) declares `1.36.0` on its scope. A non-empty scope `schema_url` applies only to that scope's data and is independent of the resource `schema_url`.

For most consumers this is pass-through metadata: collectors and backends that ingest or forward OTLP need no changes when the version advances. Schema-aware consumers can use the URL to translate telemetry between convention versions.

> [!NOTE]
> **Pipelines that validate or translate schemas**
>
> If your pipeline checks schema URLs against an allowlist or translates telemetry from bundled schema definitions (for example the Collector's schema processor), add `1.43.0` to its supported versions when upgrading Docker Agent — before the OTel SDK 1.45 migration, Docker Agent emitted `https://opentelemetry.io/schemas/1.41.0`. No published schema transformation applies to Docker Agent telemetry in the move from `1.41.0` to `1.43.0`: the `1.42.0` schema's only transform renames a `v8js` metric Docker Agent does not emit, and `1.43.0` lists no transforms.

## Inspecting traces locally

Use `--debug` to print telemetry activity to the debug log (`~/.cagent/cagent.debug.log` by default) without standing up a backend:
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ require (
go.opentelemetry.io/otel/sdk/log v0.21.0
go.opentelemetry.io/otel/sdk/metric v1.45.0
go.opentelemetry.io/otel/trace v1.45.0
go.opentelemetry.io/proto/otlp v1.11.0
go.yaml.in/yaml/v4 v4.0.0-rc.6
golang.org/x/image v0.45.0
golang.org/x/net v0.58.0
Expand All @@ -89,6 +90,7 @@ require (
golang.org/x/term v0.45.0
google.golang.org/adk/v2 v2.2.1-0.20260818092052-16e33673bc9e
google.golang.org/genai v1.69.0
google.golang.org/protobuf v1.36.11
gopkg.in/dnaeon/go-vcr.v4 v4.0.7
gotest.tools/v3 v3.5.2
modernc.org/sqlite v1.57.0
Expand Down Expand Up @@ -213,7 +215,6 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/crypto v0.55.0 // indirect
golang.org/x/mod v0.38.0 // indirect
Expand All @@ -224,7 +225,6 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
google.golang.org/grpc v1.83.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.74.4 // indirect
Expand Down
Loading