feat: structured Cloud Logging access logs, trace correlation and Error Reporting events - #164
Merged
Merged
Conversation
…or Reporting events
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
balebbae
added a commit
to hackutd/hackutd-harp
that referenced
this pull request
Sep 11, 2026
…or Reporting events (hackutd#164) Cherry-picked from upstream hackutd/harp. Two conflicts were resolved in favour of this fork's diverged code: - cmd/api/emails.go: kept per-recipient marking after delivery (660fbbf); upstream still marks all recipients before dispatch. - cmd/api/main.go: kept SuperTokens init after the mailer, which this fork needs for themed passwordless email (InitSuperTokens takes mailClient). Its Fatal call moved to the sugared Fatalw style the commit introduces, since the zap import is gone. (cherry picked from commit 69b1c73) Co-authored-by: Caleb Bae <baecal000@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CcXtZJizDrfbeYZ551eZ5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Audit of how the Go API's logs reach GCP found the Zap JSON encoder was already Cloud-Logging-shaped (
severity/message/timestamp), but everything around it leaked out of that pipeline or lacked the fields Cloud Logging / Error Reporting key on:chi/middleware.Loggerandmiddleware.Recovererwrote plain-text lines to stdout, so every access log and every panic showed up in Cloud Logging astextPayloadwith default severity — invisible tojsonPayload.*log-based metrics and never grouped by Error Reporting.log.Println/log.Fatalinmain.goandhealth.godid the same.zap.NewProductionConfig()samples Info/Warn entries (100/s then 1-in-100), which silently undercounts any log-based metric./v1/healthreturned 200 regardless of DB state, so Cloud Run readiness and the uptime check couldn't see a dead database.conflictResponse(409) logged atERROR, polluting the 5xx/Error Reporting view with client-side conflicts.What changed
internal/loggerNewProductionConfig(): sampling disabled, RFC3339Nano timestamps, Cloud Logging severity names (incl.WARNING,CRITICAL,EMERGENCY).gcp.go:ParseTraceContext(http.Header)(W3Ctraceparentfirst, thenX-Cloud-Trace-Context, decimal span → 16-hex),TraceFields(projectID, tc)emittinglogging.googleapis.com/{trace,spanId,trace_sampled}, andErrorReportFields(...)emitting@type,serviceContext,context.{httpRequest,reportLocation},stack_trace.cmd/apirequestLoggingMiddlewarereplacesmiddleware.Logger+middleware.Recoverer:http.ErrAbortHandleris re-panicked as net/http expects.app.requestLogger(r)/app.loggerFromContext(ctx)return the request-scoped logger (fallback: process logger). All handler-pathapp.logger.*calls switched over; background/dispatcher logs stay on the process logger.internalServerErrornow logs at ERROR with Error Reporting fields +reportLocationof the caller;conflictResponse→ WARNING.observabilityConfig{projectID, service, version}:GOOGLE_CLOUD_PROJECT→ else GCE metadata server (2s timeout, no-op off GCP); service =K_SERVICE→SERVICE_NAME. Trace fields are omitted when no project ID is known (Cloud Logging requires theprojects/<id>/traces/<id>form).main.go: no more stdlog; missing.envis not logged in non-dev; startup line logs version/env/service/project/go version; all init failures uselogger.Fatalw./v1/healthpings the DB (2s timeout) →200 {status:ok,database:ok,version,env}or503 {status:degraded,database:unreachable}. Still Basic-Auth-protected.endpoint_hostinstead of the endpoint URL;pushSendErrorunwraps*url.Errorso the URL doesn't leak via the error string.Config / docs:
.env.exampledocumentsGOOGLE_CLOUD_PROJECTandSERVICE_NAME; swagger regenerated for the health 503.Not touched / for you to verify in the console
jsonPayload.httpRequest.status/jsonPayload.route, and Error Reporting will start grouping panics and 500s by stack. If the Cloud Run service's health probe or uptime check hits/v1/health, note it now returns 503 when Postgres is unreachable — confirm that's the desired probe semantics (it will restart/flag the instance on DB outages).Verification
gofmt -l .clean,go vet ./...,staticcheck ./...(v0.8.1, as in CI),go mod verify,go test -race ./...all pass. New tests:internal/logger/gcp_test.go,cmd/api/observability_test.go(access-log shape, request-id/trace propagation into handler logs, panic → 500 + Error Reporting entry,ErrAbortHandlerpassthrough, 500 report payload, static-asset filtering, health DB probe, push URL redaction).Link to Devin session: https://app.devin.ai/sessions/4f1e2c60bdb1404692edbce1079e0ac6
Open in Devin Desktop: https://app.devin.ai/desktop/session/4f1e2c60bdb1404692edbce1079e0ac6?variant=devin
Requested by: @balebbae