Repository navigation
feat: implement structured GCP Cloud Logging with explicit severity across all apps - #155
Merged
Merged
Conversation
…cross all apps - Add centralized structured logger to @httparchive/shared with Cloud Logging severity support - Update report-api controllerHelpers, cdnController, and datesController to use structured error/warning logging - Update dataform-service to log operational events and errors with structured severity, plus process warning/rejection handlers - Update bigquery-export to log batch deletion, streaming, and export errors with structured severity, plus process warning/rejection handlers - Add unit tests for shared logger
- Use stack_trace so Error Reporting picks up errors - Keep primary message/severity when metadata has overlapping keys - Never throw from logging (BigInt, circular refs) - Include error cause and gRPC details - Add shared registerProcessLogging(); bigquery-export exits 1 on unhandled rejection - Count failed BulkWriter writes and fail the delete/write phase instead of reporting success - Migrate report-api index.js and mcpHandler.js to the shared logger - Set NODE_NO_WARNINGS=1 in images to avoid duplicate unstructured warnings
max-ostapenko
force-pushed
the
feat/structured-logging-and-severity
branch
from
September 30, 2026 18:47
4b8ae4f to
d1aa113
Compare
…rities - Append the error text to `message` so the reason is visible on the summary line - Keep ApiError sub-errors, statusCode and reason on logged errors - Put stacks in `stack_trace` only for ERROR entries so warnings stay out of Error Reporting - Add withTraceContext() to set logging.googleapis.com/trace from traceparent or X-Cloud-Trace-Context - Log 4xx client errors as WARNING; fix the Pub/Sub message being dropped from trigger logs - Log BulkWriter write failures with document path instead of a stack per retry - Alerts: dataform-service fires on WARNING; report-api excludes app entries with statusCode < 500 - Set GOOGLE_CLOUD_PROJECT on dataform-service for trace resource names
Request logs for scanner 403s are WARNING (about 19 a day), so matching every WARNING on the service kept the alert open. Match WARNING only on stderr, keep ERROR and above from any log, and widen the bigquery-export alert to severity>=ERROR.
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
Adds structured GCP Cloud Logging to all three apps in
apps/(report-api,dataform-service,bigquery-export). Every entry now has a severity, errors carry their stack trace in the field Error Reporting reads, and app logs are linked to the Cloud Run request log they belong to.Problem
console.error(Error ${operation}:, error)and similar), so Cloud Logging stored them asDEFAULTseverity, notERRORorWARNING.Changes
@httparchive/shared,packages/shared/src/logger.js)severityandmessage, plus any metadata passed in. Metadata can't overwritemessage,severityor the trace fields.messagebecomes"<message>: <error.message>", so the reason shows on the Logs Explorer summary line. The error is also kept inerror,code,details,reason,statusCode,errors[](BigQuery/GCS sub-errors withreasonandlocation) andcause(nested causes included).stack_traceonly onERRORentries; lower severities put them instack. Error Reporting readsstack_traceregardless of severity, so this keeps warnings out of it.withTraceContext(req, fn)usesAsyncLocalStorageto addlogging.googleapis.com/trace,spanIdandtrace_sampledto every entry logged while handling a request, including afterawaits. It reads the trace fromtraceparent, falling back toX-Cloud-Trace-Context, the same order@google-cloud/logginguses, without that dependency. The project ID comes fromGOOGLE_CLOUD_PROJECTorPROJECT; without one, the trace fields are left out.[Circular], and there's a minimal fallback entry if serialization still fails.registerProcessLogging({ exitOnUnhandledRejection })logs Node runtime warnings and unhandled rejections as structured entries.BigQueryExporthelper, used by dataform-service and bigquery-export, logs through the logger too, withjobId, androwCounton completion.apps/report-apiwithTraceContext.handleControllerErrorand the top-level catch log 4xx asWARNINGand 5xx asERROR.jsonPayload.statusCodeis included when the error has one.registerProcessLogging().apps/dataform-serviceconsole.*calls are replaced with the logger, the handler is wrapped inwithTraceContext, andregisterProcessLogging()is added.WARNING. Export failures log asERROR. Response codes are unchanged.handleExportnow logs the error when an export fails, and returnserror.messageinerrorMessage. Before, the Error was serialized as{}.pubsubMessage. It used to be dropped.apps/bigquery-exportconsole.*calls are replaced with the logger. High memory usage and retried writes log asWARNING. Pagination, streaming, export failures and writes that fail after all retries log asERROR.documentPath,operationType,codeandfailedAttemptsinstead of a stack for every retry.registerProcessLogging({ exitOnUnhandledRejection: true })).NODE_NO_WARNINGS=1, so runtime warnings are logged once through the structured handler and not also as unstructuredDEFAULTlines.severity=ERROR→(severity>=ERROR OR (severity=WARNING AND log_id("run.googleapis.com/stderr"))). App warnings, such as bad requests to dataform-service, now page too. WARNING request logs are excluded: in the last 30 days prod had 565 of them, all 403s from scanners probing paths like/.envand/swagger/, which IAM blocks before they reach the app.-(jsonPayload.statusCode < 500), so 4xx app entries don't page. Entries withoutstatusCodestill match (runtime warnings, GCS errors, unhandled errors).severity=ERROR→severity>=ERROR.GOOGLE_CLOUD_PROJECT, which the trace resource name needs. report-api already hasPROJECT.Behavior changes to be aware of
❌ Export,⚠️ High memory,Error fetching dates from GCS:) won't match anymore. Messages are now injsonPayload.message, without emojis or trailing colons.Verification
npm test: shared 14/14, report-api 120/120, dataform-service 5/5.npm run lintshows no errors (3 warnings, all from before this PR).terraform fmtis clean.Deployed to report-api-dev and tested with real requests:
tools/call(200)INFOMCP Tool Call: list_categories/v1/versions?technology=<40 values>(400)WARNINGError fetching versions: Too many technologies specified…,statusCode: 400, stack instack/v1/technologies?technology=<40 values>(500)ERRORError fetching technologies: Cannot read properties of null…, withstack_trace/technologiesis the bug fixed in fix(report-api): return 400 instead of 500 for invalid technology and date input #156. That fix is inmainand has since been merged into this branch; the dev test ran before the merge.