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
4 changes: 3 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ version: 2
updates:
- package-ecosystem: "gomod"
open-pull-requests-limit: 10 # avoid spam, if no one reacts
# The tree has 32 go.mod directories. The globs below must cover all of
# The tree has 34 go.mod directories (`git ls-files '*go.mod' | wc -l`, so
# the number stays checkable). The globs below must cover all of
# them — `pkg/gofr/metrics/exporters/gcp` was matched by none of the
# original entries and so received no dependency updates at all, despite
# vendoring the GCP client libraries. When adding a module outside these
Expand All @@ -16,6 +17,7 @@ updates:
- "/pkg/gofr/datasource/kv-store/*"
- "/pkg/gofr/datasource/pubsub/*"
- "/pkg/gofr/metrics/exporters/*"
- "/pkg/gofr/traces/exporters/*"
schedule:
interval: "weekly"

Expand Down
5 changes: 3 additions & 2 deletions docs/guides/distributed-tracing/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ Tracing is opt-in. Set:

| Env var | Purpose | Notes |
|------------------|------------------------------------------------------|------------------------------------------|
| `TRACE_EXPORTER` | `otlp`, `jaeger`, `zipkin` (deprecated) | Required to enable |
| `TRACER_URL` | Endpoint URL or `host:port` | Required when exporter is set |
| `TRACE_EXPORTER` | `otlp`, `jaeger`, `gcp`, `zipkin` (deprecated) | Required to enable |
| `TRACER_URL` | Endpoint URL or `host:port` | Required except for `gcp`, which defaults it |
| `TRACER_INSECURE`| Plaintext for a schemeless `TRACER_URL` | Defaults to `true`; ignored when the URL carries a scheme |
| `TRACER_RATIO` | Sample ratio (0.0–1.0) | Defaults to `1` (100%); an unparsable value also resolves to `1` |
| `TRACER_HEADERS` | Custom headers (e.g., for SaaS auth) | Comma-separated `key=value` pairs |
| `TRACER_AUTH_KEY`| Single auth header value | Use `TRACER_HEADERS` for multiple |
Expand Down
46 changes: 42 additions & 4 deletions docs/guides/production-tracing/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ nextjs:
# Production Tracing for GoFr

{% answer %}
GoFr ships built-in OpenTelemetry tracing — every HTTP request, gRPC call, and datasource operation is traced automatically. Configure the exporter via `TRACE_EXPORTER` (`otlp`, `jaeger`, `zipkin`, or `gofr`) and `TRACER_URL`, set `TRACER_RATIO` for head-based sampling, and W3C Trace Context propagation flows through GoFr's HTTP service client without extra code.
GoFr ships built-in OpenTelemetry tracing — every HTTP request, gRPC call, and datasource operation is traced automatically. Configure the exporter via `TRACE_EXPORTER` (`otlp`, `jaeger`, `gcp`, `zipkin`, or `gofr`) and `TRACER_URL`, set `TRACER_RATIO` for head-based sampling, and W3C Trace Context propagation flows through GoFr's HTTP service client without extra code.
{% /answer %}

{% howto name="Wire production tracing for a GoFr service" description="Configure OTLP gRPC tracing in GoFr, point it at Jaeger / Tempo / Honeycomb, and tune sampling for production." steps=[{"name": "Set TRACE_EXPORTER", "text": "Set TRACE_EXPORTER=otlp in configs/.env (or an env-based ConfigMap in K8s) — GoFr ships an OTLP gRPC exporter."}, {"name": "Set TRACER_URL", "text": "Set TRACER_URL to the collector endpoint on port 4317 for OTLP gRPC \u2014 https:// for a TLS-terminating backend, http:// or a bare host:port for plaintext; route to Jaeger collector, Tempo, or any OTLP backend."}, {"name": "Tune TRACER_RATIO", "text": "Set TRACER_RATIO to 1.0 in dev for full sampling; in prod step down to 0.1 or lower based on volume."}, {"name": "Add custom spans", "text": "Use ctx.Trace(name) inside handlers to mark sub-operations; existing HTTP, gRPC, and datasource spans are emitted automatically."}, {"name": "Verify in the backend", "text": "Hit a route, then open the Jaeger UI / Grafana Tempo and search for the service by APP_NAME — confirm spans show up with trace_id."}, {"name": "Propagate across services", "text": "GoFr injects W3C TraceContext on outbound calls via ctx.GetHTTPService — so two GoFr services share a single trace ID end to end."}] /%}
Expand Down Expand Up @@ -38,9 +38,9 @@ GoFr reads tracing config from environment variables. The relevant keys (verifie

| Variable | Purpose | Default |
|---|---|---|
| `TRACE_EXPORTER` | One of `otlp`, `jaeger`, `zipkin`, `gofr` | unset (tracing disabled) |
| `TRACER_URL` | Endpoint for the chosen exporter | unset |
| `TRACER_INSECURE` | Plaintext transport for a schemeless `TRACER_URL` (`host:port`); `false` uses TLS. Ignored when `TRACER_URL` has a scheme | `true` |
| `TRACE_EXPORTER` | One of `otlp`, `jaeger`, `gcp`, `zipkin`, `gofr` | unset (tracing disabled) |
| `TRACER_URL` | Endpoint for the chosen exporter. `gcp` requires a schemeless `host:port` | unset |
| `TRACER_INSECURE` | Plaintext transport for a schemeless `TRACER_URL` (`host:port`); `false` uses TLS. Ignored when `TRACER_URL` has a scheme — except for `gcp`, which rejects a scheme at startup rather than ignoring it | `true` |
| `TRACER_HOST` | **Deprecated** — use `TRACER_URL` | unset |
| `TRACER_PORT` | **Deprecated** — use `TRACER_URL` | `9411` |
| `TRACER_RATIO` | Head-based sampling ratio (0.0–1.0) | `1` |
Expand Down Expand Up @@ -81,6 +81,10 @@ endpoint. `TRACER_INSECURE` is ignored (with a warning) when the URL already car
GoFr warns when credentials from `TRACER_HEADERS`/`TRACER_AUTH_KEY` would travel over a plaintext
connection.

None of this applies to `gcp`, whose destination is always TLS on 443: there is no transport to
select, so a scheme-bearing `TRACER_URL` is rejected at startup rather than interpreted. See the
Google Cloud section below.

Precedence is `TRACER_URL`'s scheme, then `TRACER_INSECURE`, then the OTel standard
`OTEL_EXPORTER_OTLP_INSECURE` / `OTEL_EXPORTER_OTLP_TRACES_INSECURE`. The SDK applies its
environment variables before any option GoFr passes, so a GoFr config always wins over them.
Expand Down Expand Up @@ -116,6 +120,40 @@ TRACER_AUTH_KEY: "Bearer YOUR_TOKEN"

For a SaaS endpoint over the public internet, give `TRACER_URL` an `https://` scheme (or set `TRACER_INSECURE: "false"` on a schemeless one) so the connection is encrypted — see the TLS section above. A schemeless URL with no `TRACER_INSECURE` stays cleartext, and GoFr warns when credentials would travel over it.

### Google Cloud (keyless)

Export straight to Google Cloud's Telemetry (OTLP) API — no key file, no Collector sidecar. The
exporter is an optional module, enabled by a blank import:

```go
import _ "gofr.dev/pkg/gofr/traces/exporters/gcp"
```

```yaml
TRACE_EXPORTER: "gcp"
TRACER_RATIO: "0.1"
# TRACER_URL is optional; it defaults to telemetry.googleapis.com:443.
# Set it to telemetry.<region>.rep.googleapis.com:443 for a regional endpoint.
```

Authentication uses Application Default Credentials, so on Cloud Run, GKE or GCE the attached
service account is enough. Grant it `roles/telemetry.tracesWriter` on the project receiving the
spans: `telemetry.googleapis.com` checks `telemetry.traces.write`, and that is the least-privilege
predefined role carrying it. `roles/telemetry.writer` and `roles/cloudtrace.agent` carry it too and
grant more besides — verified with `gcloud iam roles describe` on 2026-09-21.

`TRACER_URL` must be a schemeless `host:port` here; a scheme is rejected at startup.

With a service account attached, the quota project resolves automatically. User credentials do not
carry one: grant `roles/serviceusage.serviceUsageConsumer` on the quota project and set
`GOOGLE_CLOUD_QUOTA_PROJECT`.

The token refresh is the reason this is a distinct exporter rather than a `TRACER_HEADERS` value:
Google's tokens last about an hour, so a static header authenticates once and then stops.

The destination project comes from the credentials, falling back to `GOOGLE_CLOUD_PROJECT`, and is
published as the `gcp.project_id` resource attribute. See `examples/using-gcp-traces`.

## Sampling: head-based vs tail-based

`TRACER_RATIO` is **head-based**: the sampling decision is made when the trace starts. With `TRACER_RATIO=0.1`, 10% of root spans are kept; the other 90% are dropped at the source. Cheap, predictable, but you cannot retroactively keep a slow or errored trace that wasn't sampled.
Expand Down
30 changes: 29 additions & 1 deletion docs/quick-start/observability/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,35 @@ Open {% new-tab-link title="zipkin" href="http://localhost:2005/zipkin/" /%} and



#### 4. [GoFr Tracer](https://tracer.gofr.dev/):
#### 4. [Google Cloud Trace](https://cloud.google.com/trace) (keyless):

Exports spans straight to Google Cloud's Telemetry (OTLP) API — no key file and no Collector
sidecar. Authentication uses Application Default Credentials, so on Cloud Run, GKE or GCE the
attached service account is enough. The exporter lives in its own module, enabled by a blank import:

```go
import _ "gofr.dev/pkg/gofr/traces/exporters/gcp"
```

```dotenv
# ... no change in other env variables

# tracing configs
TRACE_EXPORTER=gcp
TRACER_RATIO=1.0
# TRACER_URL is optional; it defaults to telemetry.googleapis.com:443
```

Grant the workload's service account `roles/telemetry.tracesWriter` on the project receiving the
spans — the least-privilege predefined role carrying `telemetry.traces.write`, which is what
`telemetry.googleapis.com` checks. Locally, run `gcloud auth application-default login` and set
`GOOGLE_CLOUD_PROJECT`, since a user credential carries no project.

`TRACER_URL` must be a schemeless `host:port` for `gcp`; a scheme is rejected at startup.

See `examples/using-gcp-traces` for a full Cloud Run deployment.

#### 5. [GoFr Tracer](https://tracer.gofr.dev/):

GoFr tracer is GoFr's own custom trace exporter as well as collector. Users can search a trace by its TraceID (correlationID)
in GoFr's own tracer service, available anywhere, anytime.
Expand Down
4 changes: 2 additions & 2 deletions docs/references/configs/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ This document lists all the configuration options supported by the GoFr framewor
---

- TRACE_EXPORTER
- Tracing exporter to use. Supported values: gofr, zipkin, jaeger, otlp.
- Tracing exporter to use. Supported values: gofr, zipkin, jaeger, otlp, gcp. `gcp` exports directly to Google Cloud's Telemetry (OTLP) API using Application Default Credentials, and requires the blank import `_ "gofr.dev/pkg/gofr/traces/exporters/gcp"`.

---

Expand All @@ -146,7 +146,7 @@ This document lists all the configuration options supported by the GoFr framewor
---

- TRACER_URL
- URL of the trace collector. Required if TRACE_EXPORTER is set to zipkin or jaeger. An `http://` or `https://` scheme selects the transport; a schemeless `host:port` is governed by TRACER_INSECURE.
- URL of the trace collector. Required if TRACE_EXPORTER is set to zipkin, jaeger or otlp; optional for gcp, which defaults to `telemetry.googleapis.com:443`. For zipkin, jaeger and otlp an `http://` or `https://` scheme selects the transport, and a schemeless `host:port` is governed by TRACER_INSECURE. For gcp the value must be a schemeless `host:port` — that destination is always TLS on 443, so a scheme is rejected at startup rather than interpreted.

---

Expand Down
146 changes: 146 additions & 0 deletions examples/using-gcp-traces/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# using-gcp-traces

Export GoFr traces **directly** to Google Cloud's Telemetry (OTLP) API — the
ingestion front door to Cloud Trace — with **no key file and no Collector
sidecar**, using the workload's attached service account (Application Default
Credentials).

## Enable it

One blank import registers the exporter:

```go
import _ "gofr.dev/pkg/gofr/traces/exporters/gcp"
```

and config (see `configs/.env`):

```
TRACE_EXPORTER=gcp
TRACER_RATIO=1
# TRACER_URL defaults to telemetry.googleapis.com:443
```

The `gcp` exporter authenticates with a **refreshing** OAuth2 token from ADC.
That refresh is the reason a vendor exporter is needed at all: Google's tokens
last about an hour, so a static `TRACER_AUTH_KEY` or `TRACER_HEADERS` value
authenticates once and then stops working. TLS is always on, and
`TRACER_INSECURE` does not apply.

For the same reason `TRACER_URL` must be a schemeless `host:port`: there is no
transport to select, so a `http://`/`https://` value is rejected at startup
rather than interpreted. Unlike the `otlp` exporter, where the scheme is
meaningful, here it would be silently dropped by the gRPC dialer. Surrounding
whitespace is trimmed before either check, because nothing between the
environment and this exporter trims it and a single stray space is enough to
make the value unusable as a gRPC target with no startup diagnostic.

## Startup cost

Selecting this exporter puts two calls to the GCE metadata server on the startup
path, before the HTTP server binds its port: the platform resource detector, and
the Application Default Credentials lookup. Off Google Cloud the connection is
refused instantly and neither costs anything measurable.

Each is bounded at 5 seconds, so a metadata server that accepts the connection
and never replies cannot hold the app short of serving. A container that never
binds its port never passes its startup probe, which is a worse failure than the
spans it would have exported.

On a timeout the app binds either way, and the two degrade differently:

- the **detector** timing out logs `traces: resource detection was incomplete`
and tracing continues on a partial resource — the destination project is still
resolvable from `GOOGLE_CLOUD_PROJECT`;
- the **credentials** lookup timing out logs `failed to initialize "gcp" trace
exporter … tracing is disabled`, since there is no token to authenticate with.

## Deploy to Cloud Run (keyless)

1. Grant the service's runtime service account permission to write traces:

```bash
gcloud projects add-iam-policy-binding <PROJECT_ID> \
--member="serviceAccount:<RUNTIME_SA>@<PROJECT_ID>.iam.gserviceaccount.com" \
--role="roles/telemetry.tracesWriter"
```

`telemetry.googleapis.com` — what this exporter pushes to — checks the
`telemetry.traces.write` permission. `roles/telemetry.tracesWriter` is the
least-privilege predefined role carrying it; `roles/telemetry.writer` and
`roles/cloudtrace.agent` carry it as well and grant more besides. Verified
with `gcloud iam roles describe` on 2026-09-21:

```bash
gcloud iam roles describe roles/telemetry.tracesWriter # telemetry.traces.write
gcloud iam roles describe roles/telemetry.writer # + logging, monitoring writes
gcloud iam roles describe roles/cloudtrace.agent # + cloudtrace.traces.patch
```

With a service account attached, the quota project is resolved automatically.
If you authenticate with **user** credentials instead, also grant
`roles/serviceusage.serviceUsageConsumer` on the quota project and set
`GOOGLE_CLOUD_QUOTA_PROJECT`. Do not pass `x-goog-user-project` as a tracer
header; Google documents that this is not the supported route, and the
exporter warns if you do.

2. Deploy — no credentials mounted, no `GOOGLE_APPLICATION_CREDENTIALS`:

```bash
gcloud run deploy using-gcp-traces --source . --region <REGION>
```

3. Hit `/hello` and the trace appears in Cloud Trace.

> Cross-project: grant `roles/telemetry.tracesWriter` on the **destination**
> project. No Workload Identity Federation is needed on Cloud Run itself — the
> attached service account is sufficient. WIF only applies to workloads running
> outside Google Cloud.

## Destination project

Spans are routed to a project, and the exporter resolves it in this order:

1. `gcp.project_id` from the ambient credentials (`google.FindDefaultCredentials`) —
the normal path on Cloud Run, GKE and GCE;
2. the `GOOGLE_CLOUD_PROJECT` environment variable.

The resolved value is published as the `gcp.project_id` resource attribute on
every span. If neither resolves, the exporter warns at startup rather than
failing — spans may then not reach a project.

Local runs with `gcloud auth application-default login` produce an
`authorized_user` credential, which carries no project, so set
`GOOGLE_CLOUD_PROJECT` explicitly there.

## Other resource attributes

Anything the framework cannot infer goes through the OpenTelemetry-standard
variable, which GoFr merges into the trace resource:

```
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=staging
```

On Cloud Run, GKE and GCE the platform detector fills in the service, revision
and region for you.

## Sampling

`TRACER_RATIO` is head-based and parent-respecting
(`ParentBased(TraceIDRatioBased(ratio))`), so an upstream sampling decision is
honored. Cloud Trace bills on ingested spans — lower the ratio on a busy
service.

## Alternative: Collector sidecar

If you prefer not to authenticate in-process, run an OpenTelemetry Collector
alongside the service and point plain OTLP at it — no `gcp` import needed:

```
TRACE_EXPORTER=otlp
TRACER_URL=localhost:4317
```

Google recommends the direct OTLP route used here for services that are not
already running a Collector.
13 changes: 13 additions & 0 deletions examples/using-gcp-traces/configs/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
HTTP_PORT=8080

APP_NAME=using-gcp-traces
APP_VERSION=v0.1.0

# Keyless span export to Google Cloud via the attached service account
# (Application Default Credentials). TRACER_URL defaults to
# telemetry.googleapis.com:443, so it is intentionally left unset — set it only
# to pin a regional endpoint (telemetry.<region>.rep.googleapis.com:443).
TRACE_EXPORTER=gcp

# Head-based sampling. 1 sends every trace; lower it on a busy service.
TRACER_RATIO=1
Loading
Loading