github gofr-dev/gofr v1.62.0

latest releases: pkg/gofr/traces/exporters/gcp/v0.1.0, pkg/gofr/metrics/exporters/gcp/v0.2.1, pkg/gofr/datasource/surrealdb/v0.3.6...
4 hours ago

Release v1.62.0

๐Ÿš€ Features

๐Ÿ”น Google Pub/Sub Flow Control

Three new configs control how many messages a Google Pub/Sub subscriber pulls at once. They are applied to the subscription before Receive:

config description default
GOOGLE_MAX_OUTSTANDING_MESSAGES Most unacknowledged messages held at once. A negative value means no limit. 1000
GOOGLE_MAX_OUTSTANDING_BYTES Most unacknowledged bytes held at once. A negative value means no limit. 1000000000 (1 GB)
GOOGLE_NUM_GOROUTINES Number of StreamingPull streams. This is not handler concurrency: GoFr delivers one message at a time per topic, so raising it does not raise throughput. 10

The defaults match the SDK's own, so leaving these configs unset keeps the current behavior. An unset, zero or non-integer value uses the default, and a non-integer value is also logged as an error. The defaults are exported as google.DefaultMaxOutstandingMessages, DefaultMaxOutstandingBytes and DefaultNumGoroutines, and the same fields are on google.Config. Query is a one-shot read and does not use these configs.

๐Ÿ”น Keyless Trace Export to Google Cloud

TRACE_EXPORTER=gcp sends spans directly to Google Cloud's Telemetry (OTLP) API using Application Default Credentials. On Cloud Run, traces reach Cloud Trace with an IAM role alone: no key file and no Collector sidecar. Tokens refresh on their own, which a static TRACER_HEADERS value can't do.

import _ "gofr.dev/pkg/gofr/traces/exporters/gcp"
TRACE_EXPORTER=gcp

Grant the service account roles/telemetry.tracesWriter. It is the least-privilege role that carries telemetry.traces.write, the permission this endpoint checks. roles/telemetry.writer and roles/cloudtrace.agent also carry it, but they grant more besides. With user credentials instead of a service account, also set GOOGLE_CLOUD_QUOTA_PROJECT. The exporter is its own module, so gofr.dev gains no Google dependencies. The module also sets the gcp.project_id resource attribute from the ambient credentials, falling back to GOOGLE_CLOUD_PROJECT.

๐Ÿ”ง Enhancements

๐Ÿ”น Trace Exporter Registry

Trace exporters now register themselves, the same way metrics exporters already do. A vendor exporter can live in its own module instead of pulling its SDK into gofr.dev. Also in this change:

  • The trace resource reads OTEL_RESOURCE_ATTRIBUTES and records framework_version. service.name still comes from APP_NAME.
  • Spans are flushed when the app shuts down.
  • If an exporter can't be built, the app starts with a NeverSample provider instead of crashing, so X-Correlation-ID still carries a real trace ID.

๐Ÿ”น Build Tags to Leave Out Unused Dependencies

Six opt-in build tags drop code a service doesn't use. A build that sets none of them is unchanged.

tag leaves out
gofr_nosqldrivers the blank-imported SQL drivers
gofr_nopubsub the Kafka, Google and MQTT clients
gofr_nographql graphql-go and gqlparser
gofr_nogrpc the gRPC server, reflection, health and recovery middleware
gofr_nodgraph the Dgraph migrator
gofr_nootlp the OTLP trace and metric transports
go build -tags gofr_nosqldrivers,gofr_nopubsub,gofr_nographql ./...

A minimal service built with those three tags is 41.26 MB instead of 57.10 MB (โˆ’27.7%) and compiles 554 packages instead of 831.

โšก Performance

path before after
trie router, static route (100 routes) 14 allocs, 1072 B, ~570 ns 8 allocs, 536 B, ~264 ns
request log line 10 allocs 8 allocs
  • Router: the middleware chain for each route is built once and reused, instead of being rebuilt on every request.
  • Logging: the request-log path no longer wraps each entry in a one-element slice. The output bytes are identical.

๐Ÿ› ๏ธ Fixes

  • Auth Could Start Disabled โ€” If the RBAC config couldn't be loaded, the app still started and served every route with no role checks. The only sign was one log line. EnableBasicAuth (no credentials, or an odd number of arguments) and EnableOAuth (a bad JWKS URL) failed the same way. All three now return an error, so the app can refuse to start: if err := app.EnableRBAC(path); err != nil { app.Logger().Fatalf("%v", err) }.

  • Retries After the Caller Gave Up โ€” RetryConfig kept retrying after the caller's context was done. Each of those attempts failed immediately, yet each was logged and recorded as a 503. In one case a single closed health page produced 28 of them in 0 ms. Retrying now stops once the context is done.

  • Circuit Breaker Opened by Cancellations โ€” When a caller cancelled its own request, the breaker counted it as an upstream failure, so callers going away could open the breaker for everyone. Those cancellations are no longer counted, and they no longer reset the failure count either.

  • New Connection for Every Health Check โ€” The health-check response body was closed without being read, so net/http never reused the connection. Every check opened a new TCP connection, plus a new TLS handshake over TLS. The body is now drained.

  • ClickHouse Durations Were ~0ยตs โ€” Exec, Select and AsyncInsert took their start time after the call had already returned. The app_clickhouse_stats histogram, the log duration and the span attribute now measure the real call.

  • Cassandra and ScyllaDB Swallowed Query Errors โ€” QueryWithCtx never closed the gocql iterator, and closing it is the only place gocql reports a failed query. So a failed query looked like an empty result. The error is now returned.

  • Dgraph Mutate Didn't Persist โ€” The transaction was committed only if the caller had set CommitNow. Otherwise nothing was written, the error was nil, and the transaction stayed open on the server. Mutate now commits.

  • GCP Metrics Exporter Stopped Exporting โ€” Google rejects any request with more than 200 points. A long-running process eventually grew past that, and from then on every collection was lost. Exports are now split into requests of at most 200 points.

  • Kafka Query Held a Timer for 30s โ€” A Query whose caller set no deadline got a 30s read timeout whose cancel func was thrown away. So every such query left a timer and a child context alive for the full 30s after it returned. The timeout is now cancelled as soon as the read returns.

  • Event Hub Health Probe Leak โ€” Against a broker that accepts a connection but never answers, every health poll left another goroutine stuck. At most one probe runs at a time now.

  • GCS, FTP and Azure File Stores Raced on Reconnect โ€” When the first connect failed, the background retry wrote the connection fields while requests read them, with no lock. GCS Stat, Open, ReadDir, Remove and Rename (and Azure Stat/Remove on a directory) also panicked if called before the store connected. Connection state is now guarded, a store that isn't connected yet returns its "not initialized" error, and a redundant connection from a concurrent Connect is closed instead of leaked.

  • WebSocket Stack Overflow โ€” The handler's context was set as its own parent, so .Done(), or a .Value() lookup for any other key, overflowed the stack. A data race on c.Context between the request goroutine and App.WebSocket is fixed too.

  • Typed-Nil Pub/Sub Client โ€” When kafka.New or google.New rejected a config, the typed nil they returned passed != nil checks, and a health check then called a method on it. It is now filtered out, including in GetPublisher() and GetSubscriber(), and isNil no longer panics.

  • EnableMCP Exited the Process โ€” A busy MCP port triggered Fatalf during setup, which skipped every shutdown hook. The port is now claimed with net.Listen when Run starts, before any server starts. If it is busy, or MCP_PORT is outside 1-65535, startup stops cleanly: the error is logged, open datasources are released, and nothing calls os.Exit. Set MCP_PORT=0 to run without the MCP transport.

  • Static Files With a Trailing Slash โ€” AddStaticFiles("static/", dir) logged success but served 404. static, /static, static/ and /static/ now all map to /static.

  • Silent Remote Log-Level Changes โ€” 6 of the 30 possible level changes took effect without being logged: every change out of FATAL, plus ERROR โ†’ FATAL. All of them are logged now.

  • GraphQL Error Response โ€” A failure to encode the error response is now logged, as it already was for successful responses.

Full Changelog: v1.61.0...v1.62.0

Don't miss a new gofr release

NewReleases is sending notifications on new releases.