Skip to content

feat(google-api-core): centralize telemetry error attribute and trace context builders in _observability - #18893

Draft
chalmerlowe wants to merge 1 commit into
feat/otel-logging-m1-foundationfrom
feat/otel-logging-m2-builders
Draft

chalmerlowe wants to merge 1 commit into
feat/otel-logging-m1-foundationfrom
feat/otel-logging-m2-builders

Conversation

@chalmerlowe

@chalmerlowe chalmerlowe commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem / Context

To emit consistent error metadata and trace correlation across both GAPIC method wrappers (gapic_v1.method) and transport-level interceptors (_observability), google-api-core needs shared helpers that work identically on wrapped GoogleAPICallError exceptions and raw gRPC RpcError exceptions (before gapic_v1.method's wrap_errors runs):

  1. Duplicate Error Attribute & Status Code Extraction: _extract_error_attributes previously lived inside google.api_core.gapic_v1.method, bypassed GoogleAPICallError's public properties, and required callers to invoke exceptions._extract_status_code separately (unwrapping exceptions multiple times per failed RPC). At the L4 gRPC interceptor boundary, failures are still raw grpc.RpcError / grpc.aio.AioRpcError instances whose ErrorInfo details live in trailing_metadata() (grpc-status-details-bin) and whose message comes from exc.details().
  2. Trace Context Correlation: Structured error logs require Cloud Logging trace correlation fields (logging.googleapis.com/trace, logging.googleapis.com/spanId, logging.googleapis.com/trace_sampled) extracted from an active OpenTelemetry span without raising if OpenTelemetry is not installed or the span is non-recording.

Solution

  • google/api_core/_observability.py:
    • Added has_opentelemetry() with module-level caching to check whether opentelemetry.trace is importable.
    • Added extract_trace_context(span=None) to extract trace_id (32-char lowercase hex), span_id (16-char lowercase hex), and trace_sampled (bool) from an explicit or active OpenTelemetry span.
    • Added build_telemetry_error_attributes(exc, status_code=None, status_message=None, include_exception=False) to unwrap chained exceptions once, resolve rpc.response.status_code via exceptions._extract_status_code, and extract error.type, status.message, gcp.errors.domain, gcp.errors.metadata.*, and optional exception.* fields using GoogleAPICallError properties or raw gRPC RpcError trailing metadata (google.rpc.status_pb2.Status).
  • google/api_core/gapic_v1/method.py:
    • Replaced local _extract_error_attributes implementation and separate _extract_status_code call with a single call to _observability.build_telemetry_error_attributes.
  • Unit Tests:
    • Updated tests/unit/gapic/test_method.py and added unit tests in tests/unit/test_observability.py achieving 100% line and branch coverage across all new helpers.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request centralizes and enhances telemetry and error attribute extraction by introducing has_opentelemetry, extract_trace_context, and _build_telemetry_error_attributes helpers in _observability.py, replacing the redundant _extract_error_attributes implementation in method.py, and adding comprehensive unit tests. However, a critical issue was identified in _observability.py where exceptions._extract_status_code is called with an unsupported default keyword argument, which will cause a TypeError at runtime; this should be resolved using a logical or fallback instead.

@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 5659093 to 25b8205 Compare October 29, 2026 10:13
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from f4d9d6a to 3240601 Compare October 29, 2026 10:13
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 25b8205 to d05a1be Compare October 29, 2026 11:29
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 3240601 to 7fad61b Compare October 29, 2026 11:29
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from d05a1be to 703466a Compare October 29, 2026 13:37
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 7fad61b to 858b5dc Compare October 29, 2026 13:37
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 703466a to 915abfc Compare October 29, 2026 14:01
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 858b5dc to f6ab8ea Compare October 29, 2026 14:01
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 915abfc to 4f0a450 Compare October 29, 2026 14:19
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from f6ab8ea to 024c8b8 Compare October 29, 2026 14:19
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 4f0a450 to 9d4a85f Compare October 29, 2026 14:59
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 024c8b8 to b1c49d0 Compare October 29, 2026 14:59
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 9d4a85f to 59e1573 Compare October 30, 2026 14:38
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from b1c49d0 to 95e34e8 Compare October 30, 2026 14:38
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 59e1573 to f396cf3 Compare October 30, 2026 16:43
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 95e34e8 to eae240a Compare October 30, 2026 16:43
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from f396cf3 to 37c22d1 Compare October 30, 2026 17:50
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from eae240a to 0709089 Compare October 30, 2026 17:50
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 37c22d1 to 9db5284 Compare November 2, 2026 15:16
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 0709089 to c64a257 Compare November 2, 2026 15:16
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 9db5284 to 0b0e1a9 Compare November 3, 2026 09:26
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from c64a257 to 5a89260 Compare November 3, 2026 09:27
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 0b0e1a9 to ea2203b Compare November 3, 2026 09:31
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 5a89260 to 0fdb7d0 Compare November 3, 2026 09:31
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from ea2203b to e05cd41 Compare November 3, 2026 11:49
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 0fdb7d0 to 6062724 Compare November 3, 2026 11:49
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from e05cd41 to b275232 Compare November 3, 2026 14:45
return method_str, service, method


def _extract_error_attributes(exc: Optional[BaseException]) -> dict[str, Any]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: To reviewer: we are moving the _extract_error_attributes() function out of method.py and into _observability.py because it is primarily focused on building out span attributes. The new function is renamed: build_telemetry_error_attributes()

@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from b275232 to 6ed950e Compare November 3, 2026 15:11
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 6ed950e to 030828f Compare November 3, 2026 17:52
…t builders in _observability

- Move error attribute extraction from gapic_v1.method into _observability.build_telemetry_error_attributes with single-pass exception unwrapping, rpc.response.status_code resolution, native GoogleAPICallError property access, raw gRPC RpcError trailing_metadata parsing, and optional exception.* fields.
- Update gapic_v1.method and tests to call _observability.build_telemetry_error_attributes directly without separate _extract_status_code calls.
- Add has_opentelemetry() (with module-level caching) and extract_trace_context() in _observability.
- Add unit tests achieving 100% statement and branch coverage.
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m2-builders branch from 030828f to 6fcf1d7 Compare November 3, 2026 18:07
@chalmerlowe
chalmerlowe force-pushed the feat/otel-logging-m1-foundation branch from 6062724 to d573011 Compare November 3, 2026 18:07

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant