#4204·gofr

traces: no IAM-authenticated exporter for Google Cloud — OTLP trace exporter is pinned to plaintext, unlike metrics

Author: akshat-kumar-singhalCreated Sep 13, 2026Updated Sep 13, 2026

Summary

There is no way to export traces from GoFr to Google Cloud with an IAM-authenticated push. The metrics side already solves exactly this (metrics/exporters/gcp, keyless ADC), but the trace side has (a) no exporter plug-in seam for a gcp module to live in, and (b) an OTLP exporter hardcoded to plaintext, which would override TLS credentials even if a module existed.

Checked against v1.60.1 (latest at the time of writing) and main — identical code in both.

1. The trace OTLP exporter is pinned to plaintext

pkg/gofr/otel.go:193:

go
opts := []otlptracegrpc.Option{otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(url)}

WithInsecure() is unconditional, so TRACER_URL can only ever point at a plaintext collector. It cannot be worked around from the environment either: the OTel SDK applies env config first and explicit options afterwards (otlptrace/otlptracegrpc/internal/otlpconfig/options.go:115-131), so OTEL_EXPORTER_OTLP_INSECURE=false loses to the hardcoded option. An https:// endpoint is silently downgraded rather than refused.

The metrics exporter already got this right, and its own comment describes this exact failure mode (pkg/gofr/metrics/exporters/otlp.go:106-111):

WithEndpointURL derives Insecure from the URL scheme (http vs https); an unconditional WithInsecure() appended afterwards would override that and silently downgrade an https:// endpoint to plaintext. Only apply the [insecure option when the endpoint carries no scheme].

The trace path is the code that comment warns about.

2. Static headers cannot express an IAM identity

getTracerHeaders (pkg/gofr/otel.go:144-156) supports TRACER_HEADERS / TRACER_AUTH_KEY — fixed strings decided at boot. Google's OTLP ingest requires an OAuth token that refreshes (~1h lifetime), so a static header cannot authenticate to it even once TLS is available. This is why the fix is not "add a TLS flag".

3. No exporter registry, so there is nowhere to put a gcp trace exporter

Traces resolve the exporter through a hardcoded switch with three arms (pkg/gofr/otel.go:167-179: otlp/jaeger, zipkin, gofr). Metrics, by contrast, have a registry with blank-import plug-ins (pkg/gofr/metrics/exporters/registry.go; provider.go:151 tells the user to "add a blank import to enable it"), which is precisely why gofr.dev/pkg/gofr/metrics/exporters/gcp can exist as its own module without pulling Google's SDK into core.

The precedent is already in the repo

gofr.dev/pkg/gofr/metrics/exporters/[email protected], gcp.go:144-162, does the IAM-authenticated push today — keylessly, with no service-account JSON:

go
creds, err := google.FindDefaultCredentials(ctx, cloudPlatformScope)
opts := []otlpmetricgrpc.Option{
    otlpmetricgrpc.WithEndpoint(defaultEndpoint), // telemetry.googleapis.com:443
    otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(nil)),
    otlpmetricgrpc.WithDialOption(
        grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: creds.TokenSource}),
    ),
}

with the module's own note: "On Cloud Run this uses the attached service account via the metadata server — no key file. The token source refreshes automatically, which Google's direct OTLP ingest requires."

A trace equivalent is close to a mechanical port: same ADC lookup, same endpoint, same per-RPC token source, otlpmetricgrpcotlptracegrpc. On the GCP side the permission required is telemetry.traces.write (the sole permission in roles/telemetry.tracesWriter, and included in roles/cloudtrace.agent), so an operator grants a role and sets no secret at all.

Why this matters operationally

Without it, exporting traces from a GoFr service on Cloud Run means running an OpenTelemetry Collector sidecar purely to terminate a plaintext hop and re-sign it with ADC. That is an extra container, an extra image to keep patched, and — because gcloud run services update --image is ambiguous on a multi-container service — a change to whatever deploy pipeline is in use. All of it to reach a destination the same binary can already authenticate to for metrics.

Proposal

  1. Stop forcing plaintext in buildOtlpExporter — adopt the metrics package's scheme-derived behaviour (WithEndpointURL, apply insecure only when the endpoint carries no scheme). This is a prerequisite for anything below, and a fix in its own right.
  2. Give traces the same exporter registry the metrics package has, so third-party/optional exporters can register without core depending on them.
  3. Add pkg/gofr/traces/exporters/gcp (name open) as a port of the metrics module: ADC + TLS + per-RPC token source to telemetry.googleapis.com:443, selected by TRACE_EXPORTER=gcp.

Happy to send a PR for (1) on its own if that is a useful first step — it is self-contained and the metrics package is the reference implementation.

Adjacent, for context (not part of this ask)

  • The trace resource carries only service.name (pkg/gofr/otel.go:68-71): no resource.FromEnv or detectors, so OTEL_RESOURCE_ATTRIBUTES is ignored for traces and spans cannot be attributed to a Cloud Run revision.
  • Buffered spans are dropped on shutdown — already filed as #3771.