Skip to content

Conversation

@harsh543
Copy link

Summary

This PR adds an OTel-safe logging mode for Temporal's Python LoggerAdapter classes, addressing Issue #837 where nested dicts in LogRecord.extra break OpenTelemetry logging pipelines.

Problem

Temporal's workflow.LoggerAdapter and activity.LoggerAdapter inject nested dictionaries into LogRecord.extra:

extra["temporal_workflow"] = {"workflow_id": "...", "run_id": "...", ...}

OpenTelemetry log attributes must be AnyValue types (scalars, not nested dicts). This causes Temporal context to be dropped or cause errors in OTel pipelines.

Solution

Add a temporal_extra_mode attribute to both LoggerAdapter classes with three modes:

Mode Behavior Use Case
"dict" (default) Nested dict under temporal_workflow/temporal_activity Backward compatibility
"flatten" Scalar attributes with temporal.workflow.* / temporal.activity.* prefixes OpenTelemetry
"json" Single JSON string under existing key Systems requiring string values

Usage

from temporalio import workflow, activity

# For OpenTelemetry compatibility:
workflow.logger.temporal_extra_mode = "flatten"
activity.logger.temporal_extra_mode = "flatten"

# Log records now have flat attributes:
# - temporal.workflow.workflow_id
# - temporal.workflow.workflow_type
# - temporal.workflow.run_id
# - temporal.activity.activity_id
# etc.

Changes

  • New: temporalio/_log_utils.py - Shared helper functions and TemporalLogExtraMode type
  • Modified: temporalio/workflow.py - Added temporal_extra_mode to LoggerAdapter
  • Modified: temporalio/activity.py - Added temporal_extra_mode to LoggerAdapter
  • New: tests/test_log_utils.py - Unit tests for all modes
  • Modified: tests/worker/test_workflow.py - Integration tests for workflow logging modes
  • Modified: tests/worker/test_activity.py - Integration tests for activity logging modes
  • Modified: README.md - Clarified passthrough vs invalid_module_members for datetime

Verification Checklist

  • Default mode ("dict") preserves existing behavior - no breaking changes
  • Flatten mode produces only scalar values (OTel-safe)
  • Flatten mode uses temporal.workflow.* and temporal.activity.* prefixes
  • Flattened keys don't conflict with LogRecord core attributes
  • JSON mode produces valid JSON string
  • Non-primitive values converted to strings in flatten mode
  • Type checking passes (pyright)
  • Linting passes (ruff)
  • Unit tests cover all modes and edge cases
  • Integration tests verify workflow and activity adapters

Test Plan

  1. Verify default behavior unchanged:

    # Should still produce nested dict
    assert "temporal_workflow" in record.__dict__
    assert isinstance(record.__dict__["temporal_workflow"], dict)
  2. Verify flatten mode is OTel-safe:

    workflow.logger.temporal_extra_mode = "flatten"
    # Should produce flat scalar attributes
    assert "temporal.workflow.workflow_id" in record.__dict__
    assert "temporal_workflow" not in record.__dict__
  3. Verify JSON mode:

    workflow.logger.temporal_extra_mode = "json"
    # Should produce JSON string
    assert isinstance(record.__dict__["temporal_workflow"], str)
    json.loads(record.__dict__["temporal_workflow"])  # Valid JSON

Fixes #837


🤖 Generated with Claude Code

@harsh543 harsh543 requested a review from a team as a code owner January 20, 2026 03:14
@CLAassistant
Copy link

CLAassistant commented Jan 20, 2026

CLA assistant check
All committers have signed the CLA.

harsh543 and others added 2 commits January 19, 2026 19:29
- Add TestFlattenModeOTelSafety class with critical assertions
- Verify zero dict values exist for temporal keys in flatten mode
- Verify legacy nested keys (temporal_workflow, temporal_activity) don't exist
- Test both workflow and activity contexts
- Test update context handling in flatten mode
Resolve conflict in temporalio/workflow.py by keeping both:
- temporal_extra_mode from this branch
- disable_sandbox from main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
namespaced prefix. Values that are not primitives (str/int/float/bool)
are converted to strings. This mode is recommended for OpenTelemetry
and other logging pipelines that require flat, scalar attributes.
json: Add context as a JSON string under a single key. Useful when
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually have any known use case for json mode?

as passthrough modules.

**For performance and behavior reasons, users are encouraged to pass through all third party modules whose calls will be
**Passthrough modules are about import-time behavior and determinism, not just about whether a module is third-party.**
Copy link
Contributor

Choose a reason for hiding this comment

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

There seem to be a number of unrelated readme changes.

activity.logger.temporal_extra_mode = "flatten"

handler = logging.handlers.QueueHandler(queue.Queue())
activity.logger.base_logger.addHandler(handler)
Copy link
Contributor

Choose a reason for hiding this comment

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

If you do this pattern a few times, it is probably worth creating a context manager to reduce duplication.

Copy link
Contributor

Choose a reason for hiding this comment

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

But actually, I think we might be better served here by a single parameterized test with different assertions

Args:
extra: The mutable extra dict to update.
key: The key to use for dict/json modes (e.g., "temporal_workflow").
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see a great reason to distinguish key and prefix. Do you have any compelling argument not to use the same one for both?

)


class TestApplyTemporalContextToExtra:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there might also be a way to merge some of these tests with parameterization. Not entirely sure, but give it a pass and see.

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.

[Feature Request] Make Temporal logger adapter accomodate to OpenTelemetry

3 participants