Skip to content

feat: add host_instance_id field for scaled-out worker attribution - #309

Open
yeongseon wants to merge 1 commit into
mainfrom
feat/host-instance-id
Open

feat: add host_instance_id field for scaled-out worker attribution#309
yeongseon wants to merge 1 commit into
mainfrom
feat/host-instance-id

Conversation

@yeongseon

Copy link
Copy Markdown
Owner

Summary

  • Adds a new top-level structured-log field host_instance_id so logs can be attributed to the specific Azure Functions worker instance that produced them in scale-out.
  • Resolution follows the same precedence as the Functions host's EnvironmentExtensions.GetInstanceId and the OpenTelemetry Azure faas.instance detector: WEBSITE_INSTANCE_IDWEBSITE_POD_NAMECONTAINER_NAMEsocket.gethostname(); null if all fail.
  • Value is resolved once per process and PID-aware cached (forked Consumption workers recompute their own identity); resolution is fully fail-safe and never raises (Principle 3).
  • Stamped in both ContextFilter and the LogRecordFactory; JsonFormatter resolves directly as a fallback for records that bypass filter/factory. Added to _LIBRARY_RESERVED_KEYS and the emergency payload.

Behavior notes

  • host_instance_id is complementary to, but not guaranteed equal to, Application Insights' cloud_RoleInstance.
  • Breaking (factory mode): host_instance_id is now a reserved key — passing it via extra= under use_record_factory=True raises KeyError (same as other context fields).
  • The socket.gethostname() fallback may surface a container/machine name (documented).

Tests & docs

  • New tests/test_host_instance.py covering each fallback tier, all-sources-fail → None, per-PID caching/recompute, JSON payload (record + resolver fallback), and emergency payload. Coverage 96.57% (≥95% gate).
  • Updated JSON schema contract test.
  • Docs: usage.md, configuration.md, examples/json_output.md, and all four READMEs (en/ko/ja/zh-CN) synced.

Closes #308

Resolve a per-worker host_instance_id from WEBSITE_INSTANCE_ID →
WEBSITE_POD_NAME → CONTAINER_NAME → socket.gethostname(), mirroring the
Functions host's GetInstanceId and the OpenTelemetry Azure faas.instance
precedence. The value is PID-aware cached so forked workers recompute their
own identity, and resolution never raises. It is stamped by ContextFilter
and the LogRecordFactory, with JsonFormatter resolving directly as a
fallback, and is reserved alongside the other context keys.

Closes #308

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a new structured logging field, host_instance_id, to attribute logs to the specific Azure Functions worker instance in scale-out scenarios. The value is resolved via a host-aligned env-var precedence chain with a PID-aware per-process cache, then injected via both ContextFilter and the LogRecordFactory (with JsonFormatter providing a resolver fallback).

Changes:

  • Introduces _host_instance.py resolver with WEBSITE_INSTANCE_ID → WEBSITE_POD_NAME → CONTAINER_NAME → socket.gethostname() precedence and PID-aware caching.
  • Stamps host_instance_id into records via ContextFilter and context_record_factory, and emits it from JsonFormatter (record value or resolver fallback).
  • Updates reserved key sets, contract tests, and adds dedicated tests + documentation/README updates for the new field.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/test_host_instance.py Adds unit tests for resolver precedence, fail-safety, PID-aware caching, formatter fallback, and emergency payload shape.
tests/test_contracts.py Extends JSON contract key set to include host_instance_id.
src/azure_functions_logging/_json_formatter.py Emits host_instance_id (record value or resolver fallback) and includes it in the emergency payload schema.
src/azure_functions_logging/_host_instance.py New internal resolver module implementing precedence chain + PID-aware caching and test-only reset helper.
src/azure_functions_logging/_context.py Injects host_instance_id in both ContextFilter and the LogRecordFactory path.
src/azure_functions_logging/_constants.py Adds host_instance_id to _LIBRARY_RESERVED_KEYS so it’s protected from user extra collisions.
docs/usage.md Documents host_instance_id, its resolution chain, caching behavior, and reserved-key behavior in factory mode.
docs/examples/json_output.md Updates example output and field breakdown to include host_instance_id.
docs/configuration.md Updates use_record_factory docs to include host_instance_id among injected/reserved fields.
README.md Mentions host_instance_id injection and clarifies its relationship to cloud_RoleInstance.
README.ko.md Syncs README changes and examples to include host_instance_id.
README.ja.md Syncs README changes and examples to include host_instance_id.
README.zh-CN.md Syncs README changes and examples to include host_instance_id.
Suppressed comments (3)

docs/usage.md:116

  • The "Fields populated from context" list omits span_id, even though it is injected onto the record (and appears in the JSON payload). Adding it here avoids an incomplete field list.
- `invocation_id`
- `function_name`
- `trace_id`
- `cold_start`
- `host_instance_id`

docs/examples/json_output.md:41

  • The "Top-level fields generated by formatter" list omits span_id, which JsonFormatter includes in every payload. This makes the documented field breakdown incomplete.
- `invocation_id`
- `function_name`
- `trace_id`
- `cold_start`
- `host_instance_id` — identifies the worker instance that produced the log (resolved from `WEBSITE_INSTANCE_ID` → `WEBSITE_POD_NAME` → `CONTAINER_NAME` → `socket.gethostname()`, or `null` if none resolve)

docs/examples/json_output.md:71

  • The parsing example JSON string is missing span_id. Since JsonFormatter outputs it, leaving it out can cause confusion when readers compare their logs to this example.
line = '{"timestamp":"...","level":"INFO","logger":"orders","message":"order accepted","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"host_instance_id":"0d1f2a3b4c5d","exception":null,"extra":{"order_id":"ord-1001"}}'

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/configuration.md
Comment on lines 104 to 107
`use_record_factory` is an opt-in flag that also installs a global
`logging.LogRecordFactory` so context fields (`invocation_id`, `function_name`,
`trace_id`, `cold_start`) are injected at LogRecord creation time. It guarantees
`trace_id`, `cold_start`, `host_instance_id`) are injected at LogRecord creation time. It guarantees
context propagation even when handler filters are misconfigured or bypassed by
Comment thread docs/usage.md

```json
{"timestamp":"...","level":"INFO","logger":"orders","message":"service started","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"exception":null,"extra":{"service":"orders","region":"eastus"}}
{"timestamp":"...","level":"INFO","logger":"orders","message":"service started","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"host_instance_id":"0d1f2a3b4c5d","exception":null,"extra":{"service":"orders","region":"eastus"}}

```json
{"timestamp":"2026-03-14T10:20:30.123456+00:00","level":"INFO","logger":"orders","message":"service started","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"exception":null,"extra":{"service":"orders","environment":"prod"}}
{"timestamp":"2026-03-14T10:20:30.123456+00:00","level":"INFO","logger":"orders","message":"service started","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"host_instance_id":"0d1f2a3b4c5d","exception":null,"extra":{"service":"orders","environment":"prod"}}
Comment thread README.zh-CN.md
Comment on lines 145 to 149
{"timestamp": "2024-01-15T10:30:00+00:00", "level": "INFO", "logger": "function_app",
"message": "Processing order", "invocation_id": "abc-123-def",
"function_name": "process_order", "trace_id": null, "cold_start": true,
"host_instance_id": "0d1f2a3b4c5d",
"exception": null, "extra": {"order_id": "o-999"}}
Comment thread README.ja.md
Comment on lines 145 to 149
{"timestamp": "2024-01-15T10:30:00+00:00", "level": "INFO", "logger": "function_app",
"message": "Processing order", "invocation_id": "abc-123-def",
"function_name": "process_order", "trace_id": null, "cold_start": true,
"host_instance_id": "0d1f2a3b4c5d",
"exception": null, "extra": {"order_id": "o-999"}}
Comment thread README.ko.md
Comment on lines 145 to 149
{"timestamp": "2024-01-15T10:30:00+00:00", "level": "INFO", "logger": "function_app",
"message": "Processing order", "invocation_id": "abc-123-def",
"function_name": "process_order", "trace_id": null, "cold_start": true,
"host_instance_id": "0d1f2a3b4c5d",
"exception": null, "extra": {"order_id": "o-999"}}
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.

feat(context): add host_instance_id field for scaled-out instance identification

3 participants