Claude Skill

otel-collector

OpenTelemetry Collector component configuration. Use when authoring, reviewing, or debugging Collector YAML for a specific receiver, processor, exporter, connector, or extension — config keys, defaults, validation rules, signal support, stability levels, and component-level gotch

LLM Mart · 0 points · 0 views 1 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ollygarden-opentelemetry-agent-skills-skills_otel-collector-9671795.zip · 224 KB
Part of ollygarden/opentelemetry-agent-skills — 19 skills

Install

skills CLI npx skills add https://github.com/ollygarden/opentelemetry-agent-skills/tree/main/skills/otel-collector
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ollygarden-opentelemetry-agent-skills@llmmart
Git git clone https://github.com/ollygarden/opentelemetry-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole ollygarden/opentelemetry-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

OpenTelemetry Collector

This skill covers the configuration surface of individual OpenTelemetry Collector components. It targets opentelemetry-collector and opentelemetry-collector-contrib.

It does not cover OTTL expressions (see otel-ottl), declarative SDK configuration (otel-declarative-config), deploying the eBPF profiler or other profiles producers (otel-profiles), or end-to-end pipeline design choices. Reach for those skills when the question is about transformation language, SDK setup, or pipeline composition.

Workflow

  1. Identify the component. Find the type in the user's config or question (log_dedup, interval, otlp, …). Note that many components have been renamed to snake_case since v0.146.0 with deprecated aliases preserved — see Recent renames.
  2. Load the component page before answering. If the component is in the Component index, always read components/<type>/README.md first — the top-level index is only a routing aid, not enough evidence for an answer. Follow its Details index to the smallest relevant file: read configuration.md for versioned keys, defaults, or validation; quirks.md for stability, availability, failure behavior, or migration traps; and the other detail files only as the question requires. Do not infer exact configuration or status from the summary row or memory.
  3. If the component is not indexed, say so explicitly and fall back to the upstream README under processor/<name>/, receiver/<name>/, exporter/<name>/, connector/<name>/, or extension/<name>/ in opentelemetry-collector-contrib. Don't invent config keys from memory — Collector components evolve quickly.
  4. Apply Collector-wide conventions. Named instances (type/name), stability levels, and pipeline placement rules in Collector-wide conventions apply to every component.
  5. Verify. Run the component page's Verification recipe — telemetrygen (see the otel-telemetrygen skill) plus a debug or file exporter — to confirm the component behaves as the docs claim. Alpha- and Development-stability components are common here, and behavior changes between releases. See Verification harness for how to run a recipe end-to-end.

Component index

Each component is a directory under components/<type>/. The File column points at the lean README.md (metadata, description, main use-cases, and a Details index); the full config reference, verification recipe, advanced use-cases, and quirks live in on-demand detail files linked from that README.

Coverage is intentionally selective. If a component is not indexed here, fall back to the upstream component README for the user's Collector version.

Verified 2026-10-15: Stability cells here and in component README headers match metadata.yaml at core and contrib v0.163.0; rename and "since" versions match metadata.yaml at the named tag and the one before. Offline, cite that release and date, not current stability.

Type File Kind Signals Stability Summary
log_dedup components/log_dedup/README.md processor logs Alpha Deduplicates identical log records over a time window; emits one aggregated log with a count. Renamed from logdedup in v0.151.0; alias preserved.
interval components/interval/README.md processor metrics Alpha Buffers metrics and emits one point per series per interval: latest value for cumulative monotonic series, gauges, and summaries; the summed value for delta series. Non-monotonic sums pass through unchanged.
tail_sampling components/tail_sampling/README.md processor traces Beta Buffers whole traces and makes a single keep/drop decision after a wait window via policies. Supports in-process concurrency through num_shards; scaling across Collector instances still requires a load_balancing layer. v0.160 adds tracestate-aware thresholds for rate/bytes limiting.
drain components/drain/README.md processor logs Alpha Clusters log bodies and annotates each record with a template. v0.158 replaced extract_parameters / params_attribute with masking_rules + parameter_key_prefix for named values and emit_wildcards + wildcards_attribute for remaining <*> positions.
redaction components/redaction/README.md processor traces, logs, metrics Beta (traces), Alpha (logs/metrics) Allow/block-list masking or removal of sensitive attribute keys and values, with hashing and URL/DB sanitizers.
filter components/filter/README.md processor traces, metrics, logs, profiles Alpha (traces/metrics/logs), Development (profiles) Drops spans, metric data points, log records, and profiles that match OTTL conditions (or legacy metric-name / severity filters). The most direct telemetry-volume lever.
transform components/transform/README.md processor traces, metrics, logs, profiles Beta (traces/metrics/logs), Development (profiles) Applies OTTL statements to mutate spans, metric data points, log records, and profiles in place (rename/redact/convert/aggregate attributes). The OTTL language itself lives in the otel-ottl skill.
probabilistic_sampler components/probabilistic_sampler/README.md processor traces, logs Beta (traces), Alpha (logs) Head sampling — deterministically keeps a configured percentage of traces/logs by hashing the trace ID (or an attribute). The cheaper, stateless counterpart to tail_sampling.
attributes components/attributes/README.md processor traces, metrics, logs Beta Modifies span/log/datapoint attributes via an ordered action list (insert/update/upsert/delete/hash/extract/convert), scoped by include/exclude matching.
resource components/resource/README.md processor traces, metrics, logs, profiles Beta (traces/metrics/logs), Development (profiles) Modifies the resource attributes of telemetry via the same action grammar as attributes (e.g. set service.name, drop noisy resource keys). No include/exclude matching.
k8s_attributes components/k8s_attributes/README.md processor traces, metrics, logs, profiles Stable (traces/metrics/logs, since v0.161), Development (profiles) Enriches telemetry with Kubernetes pod/namespace/node/workload metadata, associating each record to a pod by IP or resource attribute. v0.161 enables the v1 semconv gates by default (singular k8s.pod.label.*, container.image.tags).
routing components/routing/README.md connector traces, metrics, logs Alpha Routes same-signal telemetry to different pipelines by OTTL condition/statement per context; ordered table, first match wins, default_pipelines fallback. Replaces the deprecated routingprocessor.
memory_limiter components/memory_limiter/README.md processor traces, metrics, logs, profiles Beta (traces/metrics/logs), Alpha (profiles) Safety valve against OOM: refuses data with backpressure when Go heap exceeds a soft/hard limit. Its receiver-middleware extension reached Beta in v0.158 but is absent from stock distributions; the bundled processor remains the standard form.
load_balancing components/load_balancing/README.md exporter traces, logs, metrics Beta (traces/logs), Alpha (metrics) Distributes telemetry across downstream Collectors via a consistent-hash ring keyed on trace ID / service.name / (v0.162+) OTel randomness, etc., pinning related records to one backend. The standard way to scale tail_sampling/span_metrics. Renamed from loadbalancing in v0.153.0; alias preserved.
otlp (receiver) components/otlp/README.md receiver traces, metrics, logs, profiles Stable (traces/metrics/logs), Alpha (profiles) The canonical OTLP ingress over gRPC (4317) and/or HTTP (4318, protobuf + JSON). protocols: block, at least one required. Default endpoint is localhost, not 0.0.0.0 — must be set explicitly to receive containerized traffic.
otlp_grpc (exporter) components/otlp_exporter/README.md exporter traces, metrics, logs, profiles Stable (traces/metrics/logs), Alpha (profiles) Sends OTLP over gRPC to a downstream endpoint; gzip, sending queue, and retry are enabled by default. Exporter-side sending_queue.batch is opt-in unless the pkg.exporterhelper.queueBatchEnabled gate is enabled. Renamed from otlp in core v1.50.0; alias preserved.
file_log (receiver) components/file_log/README.md receiver logs Beta Tails log files (glob include), turns each line/entry into a log record, and parses it via a stanza operators pipeline. Fingerprint + offset tracking across rotation; storage for durable offsets. v0.160 adds opt-in skip_unmodified_files.
resource_detection components/resource_detection/README.md processor traces, metrics, logs, profiles Beta (traces/metrics/logs), Development (profiles) Auto-detects resource attributes via an ordered detectors list with global retry settings. Includes Azure Container Apps, Azure App Service (v0.160+), and Azure Functions (v0.162+) detectors. Renamed from resourcedetection in v0.153.0; alias preserved.
file_storage components/file_storage/README.md extension — (not pipeline-scoped) Beta Persists component state to local disk (bbolt) so it survives a Collector restart: receiver read offsets (storage:), exporter persistent send queues (sending_queue.storage:), and stateful-processor decision caches. One bbolt file per consumer; directory must exist unless create_directory: true. Not placed in a pipeline — listed under service.extensions: and referenced by other components.
prometheus (receiver) components/prometheus/README.md receiver metrics Beta Scrapes Prometheus-format HTTP endpoints and converts them to OTLP metrics, embedding prometheus/prometheus's scrape manager — the config: block is the same YAML as prometheus.yml (global, scrape_configs, scrape_config_files, service discovery, relabeling). Extra keys: trim_metric_suffixes, target_allocator (sharded targets from the OTel Operator), api_server (debug API). Scrape-only: rejects remote_write/remote_read/rule_files/alerting. Stateful, does not auto-shard across replicas.
prometheus (exporter) components/prometheus_exporter/README.md exporter metrics Beta Exposes pipeline metrics for Prometheus to scrape. In-memory accumulator expires stale series after 5m. v0.160 adds resource_constant_labels and deprecates resource_to_telemetry_conversion; add_metric_suffixes remains deprecated in favor of translation_strategy.
prometheus_remote_write (exporter) components/prometheus_remote_write/README.md exporter metrics Beta Pushes OTLP metrics as Prometheus Remote Write. Nests HTTP client settings under http; uses remote_write_queue (not sending_queue). v0.160 adds resource_constant_labels and deprecates resource_to_telemetry_conversion; RW2 remains gated and in development.

Collector-wide conventions

Named instances

Every component type supports the type/name pattern so the same type can be configured more than once. The pipeline references the named form:

processors:
  log_dedup/health-checks:
    interval: 30s
    conditions:
      - 'log.attributes["log.type"] == "health_check"'
  log_dedup/access-logs:
    interval: 10s

service:
  pipelines:
    logs/health:
      processors: [log_dedup/health-checks]
    logs/access:
      processors: [log_dedup/access-logs]

Stability levels

Components publish a stability level per signal. Treat these as load-bearing when recommending production use:

Level Use in
Development Tests and prototypes only — breaking changes expected.
Alpha Limited, non-critical workloads — config keys can still change.
Beta Production viable — breaking changes rare.
Stable Production — backward compatibility guaranteed.

Stability now varies per component — and per signal for multi-signal components (e.g. redaction is Beta for traces but Alpha for logs/metrics). Don't assume a single level for the indexed set: check each component's README header metadata table for the authoritative stability and surface it when the user asks about production readiness.

Recent renames

Many components have been renamed to snake_case since v0.146.0, and renames are still landing (e.g. the awss3 receiver → aws_s3 in v0.162.0; receivers and exporters of one family rename separately, so check the component's metadata.yaml type). The legacy names remain as deprecated aliases — old configs keep working but new configs should use the new names. Check the upstream component README for the exact rename version before flagging a config as broken.

Examples: logdedup → log_dedup, hostmetrics → host_metrics, spanmetrics → span_metrics, servicegraph → service_graph, k8sattributes → k8s_attributes, plus several _log and _check receivers.

Pipeline placement

Two rules of thumb that apply across components:

  • memory_limiter belongs first in any processor list, before anything that allocates buffers (log_dedup, transform, tail_sampling, …).
  • The otlp_grpc exporter's sending queue is enabled by default, but its sending_queue.batch sub-block is opt-in. Enable exporter-side batching explicitly (or with the alpha pkg.exporterhelper.queueBatchEnabled migration gate). The separate batch processor remains Beta and supported; use it when pipeline-level batching is needed and place it after data-dropping processors.

Verification harness

Each component page's Verification section gives a config, a telemetrygen command, and the expected output. To run any of them:

  1. Save the YAML to a file and start a collector that bundles the component — for components in the contrib/k8s distributions, otelcol-contrib --config <file>.yaml; for components not in any distribution, build a custom collector with the OpenTelemetry Collector Builder (OCB) first.
  2. Send telemetry with telemetrygen (see the otel-telemetrygen skill).
  3. Watch the debug exporter's stdout (or the file exporter's output) for the expected result.

The Verification configs are minimal repros: they omit memory_limiter and other production scaffolding on purpose, to isolate the component under test. Don't copy them verbatim into production.

Adding a new component to this skill

When extending coverage:

  1. Create the directory components/<type>/. Files carry no frontmatter — only SKILL.md has frontmatter.
  2. Write a lean README.md — always loaded, kept small:
    • Header metadata table — kind, type name, signals, per-signal stability, distributions, Go module, upstream README link, and a rename note if the component was renamed.
    • Description — what the component does and the mechanism, in 1–2 tight paragraphs (push detailed mechanism/reference into configuration.md).
    • Main use-cases — "Use when" / "Avoid when".
    • Related components — cross-links.
    • Details index — a bullet list where each item links a detail file followed by an em dash and a short description of its contents (e.g. - [Configuration](configuration.md) — config keys, defaults, validation), so the reader loads only what a question needs.
  3. Split the rest into on-demand detail files under the same directory:
    • configuration.md — full config-reference table (key, type, default, validation) plus any mechanism/reference tables.
    • verification.md — a telemetrygen + debug/file exporter recipe that proves the documented behavior; cross-reference the otel-telemetrygen skill. Verify every telemetrygen flag against that skill — never assert a flag that doesn't exist. If telemetrygen can't produce the input the component needs, say so and point to an alternative (OTTL/transform, a custom emitter). Keep the config a minimal repro (see Verification harness).
    • advanced.md — named instances, multi-pipeline setups, combinations, and edge configs.
    • quirks.md — gotchas, stability caveats, memory model, a validation-error→fix table, anti-patterns, and troubleshooting.
    • Split a heavy section into its own file when it's large (e.g. policies.md for a big policy catalog); merge trivial sections into a sibling rather than create a stub. Repoint any in-page anchor links that now cross files.
  4. Use components/log_dedup/ and components/interval/ as reference implementations of this structure.
  5. Add a row to the Component index above (the File column points at components/<type>/README.md).
  6. Update the description trigger phrases in this file's frontmatter if the new component introduces a clearly distinct user-facing keyword.
Files (opentelemetry-agent-skills)
  • components
    • attributes
      • advanced.md 3.5 KB
        # `attributes`: advanced use-cases
        
        ## Scope with include / exclude
        
        Limit the actions to a subset so you don't touch unrelated telemetry. `include` is evaluated first; `exclude` then trims that set.
        
        ```yaml
        processors:
          attributes/scoped:
            include:
              match_type: strict
              services: ["auth-service"]
            exclude:
              match_type: strict
              attributes:
                - key: test_request
                  value: true
            actions:
              - key: sensitive_data
                action: delete
              - key: user.email
                action: hash
        ```
        
        Only spans from `auth-service` are considered, and test requests within that set are skipped. For metrics, only `metric_names` and `resources` are valid match fields — see [Configuration](configuration.md#valid-fields-per-signal).
        
        ## Hash PII instead of deleting
        
        Hashing keeps a stable, joinable token without exposing the raw value. Hash by key or by regex `pattern`:
        
        ```yaml
        actions:
          - key: user.email
            action: hash
          - pattern: "^pii_.*"
            action: hash
        ```
        
        ## Extract structured fields from a string
        
        `extract` runs a regex with **named** capture groups against a string attribute and creates one attribute per group. The source value is left in place.
        
        ```yaml
        actions:
          - key: http.url
            pattern: '^(?P<http_scheme>https?):\/\/(?P<http_host>[^\/]+)(?P<http_path>.*)'
            action: extract
          # http.url = "https://api.example.com/v1/users"
          # -> http_scheme="https", http_host="api.example.com", http_path="/v1/users"
        ```
        
        ## Convert types
        
        Coerce an attribute to `int`, `double`, or `string` for downstream compatibility:
        
        ```yaml
        actions:
          - key: http.status_code
            action: convert
            converted_type: int
          - key: response_time_ms
            action: convert
            converted_type: double
        ```
        
        ## Ordered-action pipelines
        
        Actions run top to bottom, so you can stage a transform: copy through a temporary key, then clean up.
        
        ```yaml
        actions:
          - key: operation        # 1. backfill a default
            value: default
            action: insert
          - key: svc.operation    # 2. copy to the standardized key
            from_attribute: operation
            action: upsert
          - key: operation        # 3. drop the old key
            action: delete
        ```
        
        ## Inject request context
        
        Pull values from transport metadata or an authenticator (requires `include_metadata: true` on the receiver, or an authenticator extension — see [Configuration](configuration.md#context-values)).
        
        ```yaml
        actions:
          - key: client.ip
            from_context: client.address
            action: insert
          - key: enduser.id
            from_context: auth.subject
            action: insert
        ```
        
        ## Named instances
        
        Configure the same type more than once with `type/name` and reference each in the pipeline that needs it:
        
        ```yaml
        processors:
          attributes/redact:
            actions:
              - key: user.email
                action: hash
          attributes/enrich:
            actions:
              - key: region
                value: us-east-1
                action: upsert
        
        service:
          pipelines:
            traces:
              processors: [attributes/redact, attributes/enrich]
        ```
        
        ## Combining with `resource` and `filter`
        
        - Pair with **`resource`** when you also need to edit resource-level attributes (`attributes` cannot — it only sees span/log/datapoint attributes).
        - Run **before `filter`** so the attribute you want to filter on exists, or has been normalized, by the time `filter`'s condition is evaluated.
        
        ```yaml
        processors:
          attributes:
            actions:
              - key: env
                from_attribute: deployment.env
                action: upsert
          filter:
            traces:
              span:
                - 'attributes["env"] == "test"'   # drop test spans normalized above
        
        service:
          pipelines:
            traces:
              processors: [attributes, filter]
        ```
        
      • configuration.md 7.7 KB
        # `attributes`: configuration
        
        The processor operates on **span / log-record / metric-datapoint attributes** — not resource attributes (use the `resource` processor for those). It has two parts: a required ordered list of `actions`, and an optional `include`/`exclude` matching block that scopes which records the actions apply to.
        
        ## Root configuration
        
        | Field | Type | Default | Required | Description |
        |-------|------|---------|----------|-------------|
        | `actions` | list | none | Yes | Ordered list of attribute actions. At least one is required; applied in the order specified. |
        | `include` | object | none | No | Match criteria; only matching records are processed. |
        | `exclude` | object | none | No | Match criteria; matching records are skipped. When both are set, `include` is evaluated first, then `exclude` filters the included set. |
        
        ## Action fields
        
        Each entry in `actions` is one operation:
        
        | Field | Type | Required | Description |
        |-------|------|----------|-------------|
        | `key` | string | Conditional | Attribute key to act on. Required for all actions except `delete`/`hash` when only a `pattern` is given. |
        | `action` | string | Yes | One of `insert`, `update`, `upsert`, `delete`, `hash`, `extract`, `convert` (case-insensitive). |
        | `value` | any | Conditional | Value to set (string/int/double/bool). Supports env-var expansion (`${env:VAR}`); an unset reference falls back to `default_value`. Required for `insert`/`update`/`upsert` unless `from_attribute`, `from_context`, or `default_value` is used. Mutually exclusive with `from_attribute`/`from_context`. |
        | `from_attribute` | string | Conditional | Copy the value from another attribute on the same record. Mutually exclusive with `value` and `from_context`. |
        | `from_context` | string | Conditional | Pull the value from request context. Mutually exclusive with `value` and `from_attribute`. See [Context values](#context-values). |
        | `default_value` | any | No | Fallback value when the `value`/`from_attribute`/`from_context` source is missing (`insert`/`update`/`upsert` only; v0.152.0). Prevents the action from being skipped; ignored by other actions. |
        | `pattern` | string | Conditional | Regex (Go syntax). For `delete`/`hash`, matches attribute **keys** to act on. For `extract`, the regex applied to the value (must use named capture groups). |
        | `converted_type` | string | Conditional | Target type for `convert`: `int`, `double`, or `string`. Required for `convert`. |
        
        ### Action types
        
        | Action | Requires | Behavior |
        |--------|----------|----------|
        | `insert` | `key` + one of `value`/`from_attribute`/`from_context` | Adds the attribute only if the key does **not** already exist. No-op if the key exists, or if the source is missing and no `default_value` is set. |
        | `update` | `key` + one of `value`/`from_attribute`/`from_context` | Replaces the value only if the key **already** exists. No-op if the key is absent, or if the source is missing and no `default_value` is set. |
        | `upsert` | `key` + one of `value`/`from_attribute`/`from_context` | Insert if absent, update if present (insert + update). No-op only if the source value is missing and no `default_value` is set. |
        | `delete` | `key` and/or `pattern` | Removes the named key and/or every key matching `pattern`. |
        | `hash` | `key` and/or `pattern` | Replaces the value(s) of the named key and/or keys matching `pattern` with their SHA-256 hash (hex string). |
        | `extract` | `key` + `pattern` (named groups) | Applies the regex to the **string** value of `key` and creates one attribute per named capture group. Source value is left unchanged. Only acts on string values; overwrites existing attributes when group names collide. |
        | `convert` | `key` + `converted_type` | Converts the existing value of `key` to `int`/`double`/`string`. No-op if the key is absent; on a failed conversion the original value is kept (logged at debug). |
        
        Example covering several actions:
        
        ```yaml
        processors:
          attributes:
            actions:
              - key: environment        # default value if absent
                value: production
                action: insert
              - key: db.statement       # redact in place
                value: "[REDACTED]"
                action: update
              - key: user.email         # hash PII
                action: hash
              - pattern: "^temp_.*"     # bulk delete by key regex
                action: delete
              - key: http.status_code   # coerce type
                action: convert
                converted_type: int
        ```
        
        ## Include / exclude matching
        
        `include` and `exclude` share the same structure. Both are optional; if neither is set, every record is processed.
        
        | Field | Type | Description |
        |-------|------|-------------|
        | `match_type` | string | `strict` (exact, case-sensitive) or `regexp` (Go regexp). Required when `include`/`exclude` is present. |
        | `regexp` | object | Regex tuning, only valid with `match_type: regexp`. See [Regexp options](#regexp-options). |
        | `services` | list[string] | Match by service name (traces and logs only). |
        | `span_names` | list[string] | Match by span name (traces only). |
        | `span_kinds` | list[string] | Match by span kind, e.g. `SPAN_KIND_INTERNAL` (traces only). |
        | `log_bodies` | list[string] | Match by log body, **string bodies only** (logs only). |
        | `log_severity_texts` | list[string] | Match by log severity text (logs only). |
        | `log_severity_number` | object | Match by numeric severity (logs only). See below. |
        | `metric_names` | list[string] | Match by metric name (metrics only). |
        | `attributes` | list[object] | Match by attribute: each entry has `key` and optional `value`. Key-only matches any value. |
        | `resources` | list[object] | Match by resource attribute: `key` and optional `value`. |
        | `libraries` | list[object] | Match by instrumentation library: `name` and optional `version`. |
        
        `log_severity_number` takes `min` (inclusive minimum severity number — TRACE 1-4, DEBUG 5-8, INFO 9-12, WARN 13-16, ERROR 17-20, FATAL 21-24) and `match_undefined` (when `true`, records with no severity number also match; default `false`).
        
        ### Valid fields per signal
        
        - **Traces:** `services`, `span_names`, `span_kinds`, `attributes`, `resources`, `libraries`.
        - **Logs:** `log_bodies` (string bodies), `log_severity_texts`, `log_severity_number`, `attributes`, `resources`, `libraries`.
        - **Metrics:** `metric_names`, `resources`. (No `services`, `attributes`, `span_*`, or `log_*` fields.)
        
        ### Regexp options
        
        When `match_type: regexp`, a nested `regexp` block can tune an LRU cache of match results for high-throughput pipelines:
        
        | Field | Type | Default | Description |
        |-------|------|---------|-------------|
        | `cacheenabled` | bool | `false` | Cache compiled-regex match results. |
        | `cachemaxnumentries` | int | unlimited | Cap on cache entries when caching is enabled. |
        
        ```yaml
        include:
          match_type: regexp
          regexp:
            cacheenabled: true
            cachemaxnumentries: 1000
          services: ["^auth-.*", "^payment-.*"]
        ```
        
        ## Context values
        
        `from_context` pulls a value from request context. Three source kinds:
        
        - **`client.address`** — the client IP (plain key, no prefix).
        - **`metadata.*`** — gRPC metadata / HTTP headers, e.g. `metadata.x-request-id`. Requires the receiver to set `include_metadata: true`.
        - **`auth.*`** — data from a server authenticator extension, e.g. `auth.subject`. Requires an authenticator on the receiver; available keys depend on the authenticator.
        
        If a context key has multiple values, they are joined with `;`.
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                include_metadata: true   # required for metadata.* keys
        
        processors:
          attributes/context:
            actions:
              - key: client.ip
                from_context: client.address
                action: insert
              - key: request_id
                from_context: metadata.x-request-id
                action: insert
              - key: enduser.id
                from_context: auth.subject
                action: insert
        ```
        
      • quirks.md 4.8 KB
        # `attributes`: known quirks
        
        ## Action order matters
        
        `actions` run strictly in the order listed, and each sees the result of the previous ones. A `delete` before a `from_attribute` copy removes the source first; an `insert` of a default before an `upsert` copy changes what gets copied. Order cheap actions (`delete`, `insert`) before expensive ones (`extract`, `convert`) when possible. If related actions are split across named processor instances, their order in `service.pipelines.<signal>.processors` is likewise load-bearing: the later instance sees changes from the earlier one only when it is listed after it.
        
        ## It does NOT touch resource attributes
        
        This is the most common confusion. `attributes` operates on **span / log-record / metric-datapoint** attributes only. To add, rename, hash, or delete **resource** attributes (e.g. `service.name`, `host.name`, `deployment.environment`), use the **`resource`** processor — it has the same `actions` model but resource scope. Note that include/exclude *matching* can read resource attributes (the `resources` match field), but the actions still only write to non-resource attributes.
        
        ## insert vs update vs upsert
        
        - `insert` — writes only if the key is **absent**. Silently no-ops on an existing key. Use for defaults/backfill.
        - `update` — writes only if the key is **present**. Silently no-ops on a missing key. Use to rewrite/redact known fields.
        - `upsert` — always writes (insert if absent, update if present). The safe choice when you don't care about prior state; also the right tool for debugging "why didn't my action apply" (swap to `upsert` to rule out existence checks).
        
        A frequent bug: using `insert` and seeing nothing change because the key already existed, or `update` and seeing nothing because the key was absent.
        
        ## `extract` quirks
        
        - Requires **named** capture groups (`(?P<name>...)`); a pattern with unnamed groups is a config error.
        - Only acts on **string** values. A non-string source (or a missing key) is a no-op.
        - **Overwrites** existing attributes when a capture-group name collides with an existing key — there is no insert-only mode for `extract`.
        - The source `key`'s own value is left unchanged; `extract` only adds the captured fields.
        
        ## Regexp performance
        
        `match_type: regexp` filters and `pattern`-based `delete`/`hash` compile and run Go regexes per record. On high-throughput pipelines enable the LRU cache (`regexp.cacheenabled: true`, bound with `cachemaxnumentries`) and keep patterns simple — anchored prefixes (`^foo_.*`) let the engine reject non-matches early, while unanchored patterns scan the whole value. (Go's `regexp` is RE2-based and linear-time, so there's no catastrophic backtracking; the cost is per-record CPU and cache pressure, not exponential blow-up.) Remember `.` matches any character; use `\.` for a literal dot.
        
        ## Metric support caveats (identity conflict)
        
        For metrics, **adding new** data-point attributes is safe, but **modifying or deleting existing** data-point attributes can create an **identity conflict**: a metric's identity includes its data-point attributes, and this processor does **not** re-aggregate. Changing them can split or duplicate time series and corrupt cardinality. If you must change existing metric attributes, re-aggregate downstream (a connector, or the `metricstransform` processor), or fix it at the source. This warning does **not** apply to traces or logs — attributes there can be freely modified. Also note `attributes` does not rename metrics or change metric names — that's `metricstransform`.
        
        ## Common config errors
        
        | Symptom | Cause | Fix |
        |---------|-------|-----|
        | Action silently does nothing | `insert` on an existing key, or `update` on a missing key | Use `upsert`, or check the include/exclude filter isn't blocking the record. |
        | Config validation error | More than one of `value` / `from_attribute` / `from_context` set on one action | Use exactly one value source per action. |
        | Config validation error | `extract` pattern has unnamed groups | Use `(?P<name>...)` named groups. |
        | Convert no-op | Value can't be parsed to the target type (e.g. `"abc"` → int), or key absent | Pre-validate; conversion failures keep the original value (logged at debug). |
        | `from_context` empty | Receiver missing `include_metadata: true`, or no authenticator for `auth.*` | Configure the receiver/authenticator (see configuration.md). |
        | Wrong-signal field rejected | e.g. `span_names` under a metrics pipeline | Use only the match fields valid for the signal. |
        
        ## Stability
        
        All three signals (traces, logs, metrics) are **Beta** — production viable, breaking changes rare. Hashing uses SHA-256 on modern versions; very old collectors used SHA-1, so hashes are not comparable across a version boundary. `default_value` on an action is newer (v0.152.0) — confirm your collector version before relying on it.
        
      • README.md 3.4 KB
        # `attributes` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `attributes` |
        | Signals | traces (Beta), logs (Beta), metrics (Beta) |
        | Distributions | core, contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/attributesprocessor> |
        
        ## Description
        
        Modifies the **attributes** of spans, log records, and metric data points through an ordered list of `actions`. Each action names an attribute `key` and one of `insert`, `update`, `upsert`, `delete`, `hash`, `extract`, or `convert`; actions run in the order listed, so later actions see the effect of earlier ones. Values can be literal (`value`), copied from another attribute (`from_attribute`), or pulled from request context (`from_context` — client IP, transport metadata, or authenticator data).
        
        An optional `include`/`exclude` matching block scopes the processor to a subset of telemetry (by service, span name, attribute, resource attribute, instrumentation library, log body, log severity, metric name, …) so only matching records are touched. This processor operates on **span/log/metric-datapoint attributes only** — it does **not** touch resource attributes; use the `resource` processor for those.
        
        ## Main use-cases
        
        Use it when:
        - You want to standardize, backfill, or rename attribute keys across services (`insert` defaults, `upsert` to copy with `from_attribute`).
        - You need to redact or hash sensitive attribute values (`hash` PII, `delete` credentials) by key or regex `pattern`.
        - You want to extract structured fields from a string attribute (`extract` with named capture groups) or coerce types (`convert`).
        - You want to inject request context — client IP, headers, authenticated subject — onto telemetry (`from_context`).
        
        Avoid it when:
        - You need to edit **resource** attributes — use `resource` (same action set, resource scope).
        - You need arbitrary conditional logic, math, or cross-field transforms — use `transform` (OTTL is a superset).
        - You only need to drop records — use `filter`.
        - You need a fail-closed allow list for egress governance — use `redaction`.
        
        ## Related components
        
        - `resource` — same `actions` model but applied to **resource** attributes instead of span/log/datapoint attributes.
        - `transform` — OTTL-based mutation; a superset of what `attributes` can do, at the cost of more verbosity.
        - `redaction` — allow/block-list masking with a fail-closed allow list and URL/DB sanitizers.
        - `filter` — drops whole records that match a condition (run `attributes` before it to shape the matchable attributes).
        
        ## Details
        
        - [Configuration](configuration.md) — the `actions` list (every action type and its `key`/`value`/`pattern`/`from_attribute`/`from_context`/`converted_type` fields), the `include`/`exclude` matching block, and context-value sources.
        - [Verification](verification.md) — telemetrygen recipe that adds/overwrites an attribute and confirms it in `debug` output.
        - [Advanced use-cases](advanced.md) — include/exclude scoping, hashing PII, regex `extract`, type conversion, ordered-action pipelines, named instances, and combining with `resource`/`filter`.
        - [Known quirks](quirks.md) — action order, the resource-attribute confusion, `insert`/`update`/`upsert` semantics, `extract` overwrites, regexp performance, metric identity-conflict caveats, and stability.
        
      • verification.md 2.3 KB
        # `attributes`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `attributes` ships in the `core`, `contrib`, and `k8s` distributions.
        
        Config (`attributes-verify.yaml`) — an `upsert` action that sets (or overwrites) an `env` attribute on every span:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          attributes:
            actions:
              - key: env
                value: prod
                action: upsert
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [attributes]
              exporters: [debug]
        ```
        
        Send a known number of traces, each carrying an `env` attribute with a *different* value so the `upsert` overwrite is observable. Use `--workers 1` so the `--traces` count (which is **per worker**) is exact:
        
        ```bash
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 \
          --workers 1 --traces 5 --telemetry-attributes 'env="staging"'
        ```
        
        Flags used, all confirmed against the `otel-telemetrygen` skill (`references/flags.md`):
        
        - `--otlp-insecure` — disable transport security (common flag).
        - `--otlp-endpoint` — destination, defaults to `localhost:4317` for gRPC (common flag).
        - `--workers` — concurrent workers; `1` keeps the count exact (common flag).
        - `--traces` — traces **per worker** (trace-specific flag).
        - `--telemetry-attributes` — span-level attribute as `key="value"` (must be quoted). Resource-level `--otlp-attributes` would attach to the Resource, which the `attributes` processor does **not** touch.
        
        **What proves it worked:** in the `debug` exporter output, every span shows `env: Str(prod)` in its span attributes — the value sent (`staging`) has been overwritten by the `upsert` action. To prove the **insert** half of `upsert`, drop `--telemetry-attributes` and send plain traces; `env: Str(prod)` still appears because the key was absent and got inserted. (Use `insert` instead of `upsert` to confirm it leaves a pre-existing `env` untouched, or `update` to confirm it only writes when the key already exists.)
        
        You can run the same recipe with `telemetrygen logs ... --logs 5` and a `logs` pipeline to confirm log-record attributes; `--logs` is likewise per-worker, so keep `--workers 1`.
        
    • drain
      • advanced.md 3.2 KB
        # `drain`: advanced use-cases
        
        ## Persist the tree across restarts
        
        Without persistence the parse tree is rebuilt from scratch on every restart, so templates reset and warm up again. Back the processor with a storage extension and snapshot periodically:
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/drain
        
        processors:
          drain:
            storage: file_storage
            save_interval: 5m
        
        service:
          extensions: [file_storage]
        ```
        
        On startup a valid snapshot is loaded (skipping `seed_templates`/`seed_logs`, and warmup if the loaded tree already satisfies `warmup_min_clusters`); `save_interval > 0` snapshots periodically; a final snapshot is always saved on shutdown. With a shared backend (Redis, a database) new instances inherit a tree trained by running instances, avoiding cold starts.
        
        ## Tune cluster granularity
        
        ```yaml
        processors:
          drain:
            tree_depth: 5          # higher → more specific templates
            merge_threshold: 0.5   # higher → more specific templates
        ```
        
        Raise `tree_depth`/`merge_threshold` for **more specific** templates (less over-merging); lower them for **more general** ones. Add `extra_delimiters` when tokens you want split are joined by non-whitespace characters such as `:` or `,`.
        
        ## Suppress warmup before annotating
        
        ```yaml
        processors:
          drain:
            warmup_min_clusters: 20
        ```
        
        With `warmup_min_clusters > 0`, the processor trains on every record but does **not** write the template attribute until that many distinct clusters exist. Records still pass through immediately — no buffering, no added latency — they simply arrive unannotated during the warmup window (and are not retroactively annotated). This keeps unstable, half-trained templates from reaching downstream `filter`/`routing` rules.
        
        ## Split structured tokens with `extra_delimiters`
        
        ```yaml
        processors:
          drain:
            extra_delimiters: [":", ","]
        ```
        
        Useful when values you want abstracted are glued to neighboring tokens by punctuation rather than whitespace.
        
        ## Mask named values and emit remaining wildcards
        
        Since v0.158.0, `masking_rules` extracts selected regex matches under stable names, while `emit_wildcards` exposes any remaining Drain `<*>` positions in template order:
        
        ```yaml
        processors:
          drain:
            masking_rules:
              - name: ip
                pattern: '\b(?:\d{1,3}\.){3}\d{1,3}\b'
            emit_wildcards: true
        ```
        
        For body `user alice logged in from 10.0.0.1`, this can produce template `user <*> logged in from <ip>`, `log.record.template.parameter.ip: 10.0.0.1`, and `log.record.template.wildcards: ["alice"]`. A fully literal or fully named template produces no wildcard attribute.
        
        ## Combine templates with downstream components
        
        Feed the derived template into a `filter` to drop a noisy class, a `routing` connector to fan log classes into separate pipelines, or a `log_dedup` processor to aggregate identical records with a count:
        
        ```yaml
        processors:
          drain:
          filter/drop_noisy:
            error_mode: ignore
            logs:
              log_record:
                - attributes["log.record.template"] == "heartbeat ping <*>"
                - attributes["log.record.template"] == "connected to <*>"
        
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [drain, filter/drop_noisy]
              exporters: [otlp]
        ```
        
      • configuration.md 5.3 KB
        # `drain`: configuration
        
        The Drain algorithm tokenizes each log line and walks a **fixed-depth parse tree** (depth set by `tree_depth`). Lines with similar token structure land in the same **cluster**; each cluster carries a template. As more logs arrive, the template is refined — tokens that vary across a cluster's members become `<*>` wildcards while stable tokens are kept verbatim. The algorithm is deterministic: given the same configuration and a representative sample of log forms, the same token structure always yields the same template. Because of this, independent collector instances converge on identical templates once each has seen enough lines.
        
        ## Typical config
        
        ```yaml
        processors:
          drain:
            template_attribute: log.record.template
        
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [memory_limiter, drain]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `tree_depth` | int | `4` | Max depth of the Drain parse tree (`depth` in the paper). Higher = more specific templates. **Minimum: 3.** |
        | `merge_threshold` | float | `0.4` | Minimum fraction of tokens that must match an existing cluster for a line to merge into it rather than form a new cluster (`st` in the paper). Range `[0.0, 1.0]`. |
        | `max_node_children` | int | `100` | Maximum children per internal parse-tree node (`maxChild` in the paper). Bounds memory on high-cardinality token positions. |
        | `max_clusters` | int | `0` | Maximum clusters tracked; when exceeded, the least-recently-used cluster is evicted. `0` = unlimited. |
        | `extra_delimiters` | []string | `[]` | Additional token delimiters beyond whitespace (e.g. `[",", ":"]`). |
        | `body_field` | string | `""` | If set and the body is a map, the value of this **single top-level key** is templated instead of the full body. Full OTTL paths are not supported. `""` = use the full body string. |
        | `template_attribute` | string | `"log.record.template"` | Attribute key the derived template string is written to. |
        | `masking_rules` | list of `{name, pattern}` | `[]` | Ordered RE2 substitutions applied before clustering. Each whole match becomes `<name>` and is emitted at `<parameter_key_prefix>.<name>`. |
        | `parameter_key_prefix` | string | `"log.record.template.parameter"` | Prefix for dynamically named attributes produced by `masking_rules`. |
        | `emit_wildcards` | bool | `false` | Emit body tokens occupying Drain-generated `<*>` positions as a positional string slice. |
        | `wildcards_attribute` | string | `"log.record.template.wildcards"` | Attribute key for the wildcard slice. Consulted only when `emit_wildcards: true`. |
        | `warmup_min_clusters` | int | `0` | Distinct clusters that must be observed before annotation starts. `0` disables warmup. |
        | `storage` | component ID | `""` | ID of a storage extension used to persist the tree across restarts. `""` = disabled. |
        | `save_interval` | duration | `0s` | Interval between periodic snapshot saves. `0s` = save on shutdown only. Requires `storage`. |
        | `seed_templates` | []string | `[]` | Template strings pre-loaded at startup. Empty/whitespace-only entries are skipped. |
        | `seed_logs` | []string | `[]` | Raw example log lines trained on at startup. Empty/whitespace-only entries are skipped. |
        
        ## Validation rules
        
        - `tree_depth` must be `>= 3`.
        - `merge_threshold` must be in `[0.0, 1.0]`.
        - `warmup_min_clusters` must be `>= 0`.
        - `save_interval` must be `>= 0`.
        - `save_interval > 0` requires `storage` to be set.
        - Every masking-rule `name` must be non-empty, cannot be `*`, and cannot contain angle brackets or whitespace.
        - Every masking-rule `pattern` must be a non-empty valid Go RE2 regular expression.
        
        ## Structured (map) body behavior
        
        If the body is a map, the full serialized form is templated by default, which yields low-value templates. Set `body_field` to the **single top-level** message key (full OTTL paths like `body["event"]["message"]` are not supported), or promote that field to a plain string body upstream (e.g. a `move` operator in the filelog receiver). The full body is used unchanged if the key is absent or the body is not a map.
        
        ## Named masking and positional wildcard emission
        
        Masking rules run in declaration order on a working copy of the body. Each whole regex match becomes a named token in the template and its original value is emitted as an attribute:
        
        ```yaml
        processors:
          drain:
            masking_rules:
              - name: ip
                pattern: '\b(?:\d{1,3}\.){3}\d{1,3}\b'
            parameter_key_prefix: log.record.template.parameter
            emit_wildcards: true
            wildcards_attribute: log.record.template.wildcards
        ```
        
        For body `user alice logged in from 10.0.0.1`, masking can produce template `user <*> logged in from <ip>`, attribute `log.record.template.parameter.ip: 10.0.0.1`, and wildcard slice `["alice"]`. If a name matches multiple template positions, the first value wins and `otelcol_processor_drain_masks_duplicates` increments once per record and duplicated name. Wildcard emission uses the same tokenization as the tree and writes no slice when no `<*>` remains. During warmup suppression none of these output attributes is written.
        
        The v0.157.0-only keys `extract_parameters` and `params_attribute` were removed in v0.158.0. Replace them with `emit_wildcards` and `wildcards_attribute`; the default output key changed from `log.record.template.params` to `log.record.template.wildcards`.
        
      • quirks.md 2.7 KB
        # `drain`: known quirks
        
        ## Unbounded memory with `max_clusters: 0`
        
        The default `max_clusters: 0` means *unlimited* — active clusters and tree nodes grow with unique log patterns. On high-diversity streams this can grow without bound. Bound it with `max_clusters` (enables LRU eviction) and cap fan-out with `max_node_children`. Track `otelcol_processor_drain_clusters_active` for growth.
        
        ## Warmup withholds templates
        
        When `warmup_min_clusters > 0`, the template attribute does not appear until that many clusters have been observed. If the attribute "never shows up", check `otelcol_processor_drain_clusters_active` against the threshold, or lower `warmup_min_clusters`. The warmup window is observable as `otelcol_processor_incoming_items - otelcol_processor_drain_log_records_annotated`.
        
        Warmup also withholds named-mask and wildcard attributes. Even after warmup, a template with no `<*>` positions emits no wildcard slice; this is not an extraction failure.
        
        ## Templates reset on restart without persistence
        
        The tree lives in memory. Without a `storage` extension, templates are rebuilt (and re-warmed) on every restart, and independent instances may disagree on a template during early training on low-volume or highly variable streams. Mitigate with `seed_templates`/`seed_logs`, `warmup_min_clusters`, or shared `storage` (see [Advanced use-cases](advanced.md)).
        
        ## Structured (map) bodies
        
        If the body is a map, the full serialized form is templated by default, which yields low-value templates. Set `body_field` to the **single top-level** message key (full OTTL paths like `body["event"]["message"]` are not supported), or promote that field to a plain string body upstream (e.g. a `move` operator in the filelog receiver). The full body is used unchanged if the key is absent or the body is not a map.
        
        ## Alpha stability and provisional attribute name
        
        The processor is **Alpha** — keys and behavior may change. This happened in v0.158.0: the v0.157.0-only `extract_parameters` / `params_attribute` keys became `emit_wildcards` / `wildcards_attribute`. The default `log.record.template` tracks a *proposed* (not yet adopted) semantic convention ([semantic-conventions#1283](https://github.com/open-telemetry/semantic-conventions/issues/1283), [#2064](https://github.com/open-telemetry/semantic-conventions/issues/2064)); pin output keys explicitly if you depend on them. The internal `otelcol_processor_drain_*` metrics are Development stability and may change or be removed.
        
        ## Drain does not reduce volume
        
        It only annotates. Expecting `drain` alone to drop or aggregate logs is a common mistake — always add a `filter`/`routing` (or `log_dedup`) component downstream to act on the template.
        
      • README.md 3.6 KB
        # `drain` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `drain` |
        | Signals | logs |
        | Stability | Alpha (processor); Development (telemetry metrics `otelcol_processor_drain_*`) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/drainprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/drainprocessor> |
        
        Logs support is Alpha since v0.151.0; configuration keys and behavior may change between releases.
        
        ## Description
        
        Applies the [Drain](https://jiemingzhu.github.io/pub/pjhe_icws2017.pdf) log-clustering algorithm to each log record, derives a **template string** (e.g. `user <*> logged in from <*>`), and writes it to a configurable attribute (`log.record.template` by default). The template becomes a stable key for grouping, filtering, routing, or sampling whole classes of logs. Since v0.158.0, ordered regex masking rules can turn high-cardinality values into named template tokens and attributes; optional wildcard emission writes Drain's remaining `<*>` values to a string-slice attribute.
        
        Drain tokenizes each log line and walks a **fixed-depth parse tree** (depth set by `tree_depth`). Lines with similar token structure land in the same cluster, and each cluster carries a template whose varying tokens become `<*>` wildcards while stable tokens are kept verbatim. The processor **annotates only** — it does not drop, aggregate, reorder, or otherwise modify the flow of logs.
        
        ## Main use-cases
        
        Use it when:
        - You want to group high-volume, semi-structured logs into a small number of stable patterns.
        - You need a stable key for downstream filtering, routing, or sampling of log classes.
        - You need named regex-matched values or the remaining positional wildcard values as structured attributes.
        - Your log bodies are free-form strings (or a structured body with a clear message field).
        - You want to identify the noisiest log patterns before deciding what to drop.
        
        Avoid it when:
        - Your logs are already fully structured with a reliable event identifier — use that attribute directly.
        - You need exact, hand-authored templates with no early-training variance — seed them, or template upstream.
        - Bodies are large JSON blobs with no single message field — templating the serialized map produces low-value templates (set `body_field`, or promote the message field upstream).
        
        ## Related components
        
        - `filter` — drop or keep logs by matching the derived template.
        - `routing` (connector) — route log classes to different pipelines by template.
        - `log_dedup` — aggregate identical logs with a count; complements template-based grouping.
        - `transform` — OTTL-based per-record transformations.
        - Storage extensions (e.g. `file_storage`) — back `storage` for snapshot persistence.
        
        ## Details
        
        - [Configuration](configuration.md) — config keys, defaults, named masking, wildcard emission, validation rules, and structured-body behavior. Open when wiring up `drain` or tuning what gets templated.
        - [Verification](verification.md) — telemetrygen recipe to confirm templates are written. Open when checking the processor is bundled and working end-to-end.
        - [Advanced use-cases](advanced.md) — persistence, granularity tuning, warmup, named masking, wildcard emission, delimiters, and combining with downstream components. Open when going beyond a single stock instance.
        - [Known quirks](quirks.md) — unbounded memory, warmup withholding, restart resets, map bodies, Alpha stability, and the "does not reduce volume" trap. Open when something behaves unexpectedly.
        
      • verification.md 1.5 KB
        # `drain`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        Confirm `drain` is in your distribution (check the component's metadata); build via OCB if it is not yet bundled.
        
        Config (`drain-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          drain:
            template_attribute: log.record.template
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [drain]
              exporters: [debug]
        ```
        
        Send log records that share structure but differ in values (see the `otel-telemetrygen` skill):
        
        ```bash
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 100 --body "user alice logged in from 10.0.0.1"
        ```
        
        (Don't add `--duration`: telemetrygen ignores `--logs` when `--duration` is set.) telemetrygen sends a **constant** body per invocation (`--body` defaults to `"the message"`), so a single run produces one template with no wildcards. To watch several distinct values collapse to one template (e.g. `user <*> logged in from <*>`), run the command a few times with different `--body` values — for example `"user bob logged in from 192.168.1.1"` and `"user carol logged in from 172.16.0.9"` — or point the pipeline at a real log source.
        
        **What proves it worked:** the `debug` exporter shows each record annotated with a `log.record.template` attribute holding the clustered template string (e.g. `user <*> logged in from <*>`).
        
    • file_log
      • advanced.md 5.9 KB
        # `file_log` receiver: advanced use-cases
        
        ## Multiline entries
        
        By default an entry is one line. The `multiline` block makes the **file reader** split the byte
        stream on a pattern instead — for stack traces, pretty-printed payloads, or any record that spans
        lines. It must contain **exactly one** of `line_start_pattern` or `line_end_pattern` (regexes);
        `omit_pattern: true` strips the matched delimiter from each entry.
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/example/multiline.log ]
            multiline:
              line_start_pattern: ^Exception     # a new entry begins at each "Exception" line
        ```
        
        Everything between two `^Exception` lines becomes a single record. For rules that can't be
        expressed as one start/end regex (e.g. "combine until the next line has a timestamp"), use the
        [`recombine`](operators.md#general-purpose-operators) operator instead — more flexible, more cost.
        
        ## Durable offsets with `storage`
        
        By default read offsets live only in memory, so a collector restart re-reads from `start_at`
        (typically losing position or re-ingesting). Point `storage` at a `file_storage` extension to
        persist them:
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/file_storage
        
        receivers:
          file_log:
            include: [ /var/log/myapp/*.log ]
            storage: file_storage          # persists Fingerprint + Offset + FileAttributes per file
        
        service:
          extensions: [file_storage]
          pipelines:
            logs:
              receivers: [file_log]
              exporters: [debug]
        ```
        
        Stored per tracked file: the fingerprint (first bytes), the byte offset, and the file attributes.
        Note this guarantees the *file* is read accurately; records can still be lost further downstream —
        for full durability also configure exporter persistent queues. With `polls_to_archive` (>0,
        experimental) plus `storage`, offsets of readers older than three poll cycles are archived to disk
        rather than purged, which helps avoid re-ingestion when used with `exclude_older_than`.
        
        ## Log rotation and `on_truncate`
        
        The receiver follows files across rotation by inode + fingerprint, so it keeps reading even when a
        rotated file no longer matches `include`. Two strategies are handled:
        
        - **move/create** — handled transparently; reading continues on the renamed file.
        - **copy/truncate** — the file shrinks in place. `on_truncate` controls the response when a
          same-fingerprint file is seen smaller than the stored offset:
          - `ignore` (default) — keep the offset; read nothing until the file grows past it. Avoids
            duplicates.
          - `read_whole_file` — reset offset to 0 and re-read everything. No data loss, possible
            duplicates.
          - `read_new` — set offset to the current (post-truncate) size; read only new appends.
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myapp/*.log ]
            on_truncate: read_whole_file
        ```
        
        **File-attribute behavior during rotation:** if the rotated name no longer matches `include`, the
        *original* `log.file.name`/`log.file.path` are preserved; if it still matches, the *new* rotated
        name is reported (prevents duplicate per-file metrics).
        
        ## Header metadata parsing
        
        Read attributes from a file's header lines and stamp them onto every record from that file.
        Requires the `filelog.allowHeaderMetadataParsing` feature gate and `start_at: beginning`.
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myapp/*.log ]
            start_at: beginning
            header:
              pattern: '^#.*'                       # every header line matches this
              metadata_operators:
                - type: regex_parser
                  regex: '^#Service: (?P<service>.*)$'
        ```
        
        Each header line is run through `metadata_operators`; the resulting attributes are merged (upsert)
        and attached to every emitted record. Header lines themselves are not emitted.
        
        ## Compression
        
        Read gzip-compressed files directly:
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/example/*.log.gz ]
            compression: gzip              # or `auto` to auto-detect gzip by header
        ```
        
        `auto` is useful for a mix of compressed and plain files in one receiver. Compressed inputs must
        be **appended** to (not rewritten) if they keep growing.
        
        ## File ordering
        
        When many rotated files match `include` but you only want the newest tracked, `ordering_criteria`
        groups and sorts matches and keeps the top N:
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myapp/app-*.log ]
            ordering_criteria:
              regex: 'app-(?P<num>\d+)\.log'
              top_n: 1
              sort_by:
                - sort_type: numeric
                  regex_key: num
                  ascending: false         # track only the highest-numbered file
        ```
        
        An explicit `top_n: 0` means "match all files." Omitting `top_n` still falls
        back to the legacy value `1` unless the alpha `filelog.requireExplicitTopN` gate is enabled; with
        that gate, a sorted configuration must state either `0` or a positive limit.
        
        ## Kubernetes pod logs
        
        The canonical node-level collection path: tail the kubelet's per-pod log files and parse the
        container runtime's wrapper format with the [`container`](operators.md#parsers) operator, which
        handles `docker`, `cri-o`, and `containerd` and lifts the runtime metadata (stream, timestamp)
        out of the line. Pair with [`k8s_attributes`](../k8s_attributes/README.md) downstream for
        namespace/pod/label enrichment.
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/pods/*/*/*.log ]
            start_at: end                  # live tail; don't backfill node history on restart
            include_file_path: true        # k8s_attributes can associate by log.file.path
            operators:
              - type: container            # auto-detects docker/cri-o/containerd
        ```
        
        ## Named instances
        
        Run the receiver more than once (e.g. different parsing per log source) with the `type/name` form:
        
        ```yaml
        receivers:
          file_log/app:
            include: [ /var/log/app/*.log ]
            operators: [ { type: json_parser } ]
          file_log/access:
            include: [ /var/log/nginx/access.log ]
            operators: [ { type: regex_parser, regex: '(?P<msg>.*)' } ]
        
        service:
          pipelines:
            logs:
              receivers: [file_log/app, file_log/access]
              exporters: [debug]
        ```
        
      • configuration.md 8.6 KB
        # `file_log` receiver: configuration
        
        Every key below traces to the contrib v0.160.0 source (`receiver/filelogreceiver/README.md`, generated from the `fileconsumer` config). The receiver wraps the stanza `file_input` operator, so most keys are file-discovery and file-reading knobs; the parsing pipeline is `operators` (see [operators.md](operators.md)).
        
        Only `include` is required.
        
        ## File discovery
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `include` | `[]string` | **required** | Glob patterns of file paths to read. |
        | `exclude` | `[]string` | `[]` | Glob patterns to exclude, applied against the `include` matches. |
        | `exclude_older_than` | duration | — | Exclude files whose modification time is older than this age. |
        | `start_at` | `beginning` \| `end` | `end` | Where to start reading **newly discovered** files. Default `end` means a pre-existing, idle file yields nothing — see [quirks.md](quirks.md). |
        | `poll_interval` | duration | `200ms` | Interval between filesystem polls for new data / new files. |
        | `max_concurrent_files` | int | `1024` | Max files read concurrently; excess files are processed in batches. |
        | `max_batches` | int | `0` | When batching is needed to respect `max_concurrent_files`, caps batches per poll. `0` = no limit. |
        
        ## File reading & sizing
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `encoding` | string | `utf-8` | File encoding. See [supported encodings](#supported-encodings). |
        | `fingerprint_size` | bytes | `1000` | Bytes from the start of a file used to uniquely identify it. Minimum `16`. Decreasing it re-ingests files larger than the new size. |
        | `initial_buffer_size` | bytes | `16KiB` | Initial read-buffer size for headers and logs; grows as needed. |
        | `max_log_size` | bytes | `1MiB` | Max size of a single log entry. Behavior on overflow set by `max_log_size_behavior`. |
        | `max_log_size_behavior` | `split` \| `truncate` | `split` | `split` emits the oversized entry as multiple records; `truncate` keeps the head and drops the rest. |
        | `force_flush_period` | duration | `500ms` | Time since last new data after which a partial trailing line may be emitted. `0` disables (waits for a delimiter). |
        | `preserve_leading_whitespaces` | bool | `false` | Keep leading whitespace on each entry. |
        | `preserve_trailing_whitespaces` | bool | `false` | Keep trailing whitespace on each entry. |
        | `compression` | `` \| `gzip` \| `auto` | `` | Read compressed files. `auto` auto-detects gzip by header. Appended (not rewritten) compressed files only. |
        
        ## Emitted file attributes
        
        Each toggles a `log.file.*` attribute on every record from that file. `*_owner_*` and `*_permissions` are **not supported on Windows**.
        
        | Key | Type | Default | Attribute added |
        |-----|------|---------|-----------------|
        | `include_file_name` | bool | `true` | `log.file.name` |
        | `include_file_path` | bool | `false` | `log.file.path` |
        | `include_file_name_resolved` | bool | `false` | `log.file.name_resolved` (after symlink resolution) |
        | `include_file_path_resolved` | bool | `false` | `log.file.path_resolved` (after symlink resolution) |
        | `include_file_owner_name` | bool | `false` | `log.file.owner.name` (Unix only) |
        | `include_file_owner_group_name` | bool | `false` | `log.file.owner.group.name` (Unix only) |
        | `include_file_permissions` | bool | `false` | `log.file.permissions`, 3-digit octal e.g. `755` (Unix only) |
        | `include_file_record_number` | bool | `false` | `log.file.record_number` |
        | `include_file_record_offset` | bool | `false` | `log.file.record_offset` |
        
        ## Static attributes & resource
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `attributes` | map | `{}` | `key: value` pairs added to each entry's attributes. Values are strings or stanza [expressions](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/stanza/docs/types/expression.md) returning a string. |
        | `resource` | map | `{}` | `key: value` pairs added to each entry's resource. Same value rules as `attributes`. |
        | `operators` | `[]operator` | `[]` | The stanza parsing pipeline. See [operators.md](operators.md). |
        | `multiline` | block | — | Split entries on a pattern instead of newlines. See [advanced.md](advanced.md#multiline-entries). |
        
        ## Durability & rotation
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `storage` | component ID | none (in-memory) | A `file_storage` (or other storage) extension ID for persisting offsets across restarts. See [advanced.md](advanced.md#durable-offsets-with-storage). |
        | `delete_after_read` | bool | `false` | Read then immediately delete each file. Requires feature gate `filelog.allowFileDeletion`; must be `false` when `start_at: end`. |
        | `acquire_fs_lock` | bool | `false` | Acquire a filesystem lock before reading (Unix only). |
        | `file_cache_advise` | bool | `false` | Hint the OS to release cached pages after read (Linux only); helps page-cache pressure on large sequential reads. |
        | `on_truncate` | `ignore` \| `read_whole_file` \| `read_new` | `ignore` | Behavior when a same-fingerprint file shrinks (copytruncate rotation). See [advanced.md](advanced.md#log-rotation-and-on_truncate). |
        | `skip_unmodified_files` | bool | `false` | Skip opening and fingerprinting a tracked file when its path and modification time are unchanged since the previous poll. Added in v0.160.0. |
        | `polls_to_archive` | int | `0` | **Experimental.** With `storage` set, retain offsets of old readers on disk for this many poll cycles instead of purging after 3 generations. |
        
        ## Header metadata parsing
        
        Requires feature gate `filelog.allowHeaderMetadataParsing` and `start_at: beginning`. See [advanced.md](advanced.md#header-metadata-parsing).
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `header.pattern` | regex | required (when `header` set) | Regex that every header line matches. |
        | `header.metadata_operators` | `[]operator` | required (when `header` set) | Operators that parse attributes out of the header lines. Merged onto every record from the file; header lines themselves are not emitted. |
        
        ## Retry on downstream failure
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `retry_on_failure.enabled` | bool | `false` | Pause reading a file and resend the current batch on a downstream error. |
        | `retry_on_failure.initial_interval` | duration | `1s` | Backoff before the first retry. |
        | `retry_on_failure.max_interval` | duration | `30s` | Upper bound on backoff. |
        | `retry_on_failure.max_elapsed_time` | duration | `5m` | Total time (incl. retries) before data is dropped. `0` = retry forever. |
        
        ## File ordering (`ordering_criteria`)
        
        Tracks only the top-N files after grouping and sorting — useful when many rotated files match but you want a deterministic read order.
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `ordering_criteria.regex` | regex | — | Must contain named capture groups referenced by `sort_by.regex_key`. |
        | `ordering_criteria.group_by` | regex | — | Named-capture regex for pre-sort grouping. |
        | `ordering_criteria.top_n` | int | unset → `1` | Number of files to track after ordering. Explicit `0` means match all files; negative values are rejected. With the alpha `filelog.requireExplicitTopN` gate enabled, omitting this key while `sort_by` is set is a startup error. Added in v0.159.0. |
        | `ordering_criteria.sort_by.regex_key` | string | — | Named group (from `ordering_criteria.regex`) to sort on. |
        | `ordering_criteria.sort_by.sort_type` | `numeric` \| `alphabetical` \| `timestamp` \| `mtime` | — | Sort strategy. `mtime` requires the `filelog.mtimeSortType` feature gate (Beta, on by default since v0.161.0; Alpha off before). |
        | `ordering_criteria.sort_by.location` | string | — | Timestamp location (when `sort_type: timestamp`). |
        | `ordering_criteria.sort_by.layout` | strptime | — | Timestamp format, strptime (when `sort_type: timestamp`). ⚠️ The upstream README documents this key as `format`, but the actual field (matcher source + `config.schema.yaml`) is `layout` — `format:` is rejected as an invalid key (verified on v0.156.0). |
        | `ordering_criteria.sort_by.ascending` | bool | — | Sort direction. |
        
        ## Supported encodings
        
        `nop` (raw bytes, no validation), `utf-8` (default), `utf-8-raw` (no invalid-byte replacement), `utf-16le`, `utf-16be`, `ascii`, `big5`. Other IANA charsets are supported best-effort.
        
        ## Minimal config
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myapp/*.log ]
            start_at: beginning            # default `end` would skip existing content
            operators:
              - type: json_parser          # parse each line as JSON into attributes
                timestamp:
                  parse_from: attributes.time
                  layout: '%Y-%m-%dT%H:%M:%S%z'
        ```
        
      • operators.md 5 KB
        # `file_log` receiver: operators
        
        The `operators` list is a **stanza** pipeline — an ordered chain of small, single-purpose
        steps that run on each log entry as it leaves the file reader. This is where parsing,
        timestamp/severity extraction, and entry combining happen. The full operator reference lives
        upstream in [`pkg/stanza/docs/operators`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/stanza/docs/operators); this page is the catalog plus the wiring rules you need to author a pipeline.
        
        ## Pipeline rules
        
        - Every operator has a **`type`**. Each can have a unique **`id`**; if you use the same `type`
          twice, you must give them distinct `id`s (otherwise `id` defaults to `type` and they collide).
        - By default each operator passes its output to the **next** operator in the list; the last one
          emits from the receiver. Override with **`output: <id>`** to jump to a specific operator
          (used for branching after a `router`).
        - For `file_log` the input is **implicit** — the receiver *is* the `file_input`. Do **not** add
          `file_input` or other input operators to `operators`; start with a parser or general-purpose
          operator. Only parsers and general-purpose operators should be used here.
        - The raw line is in the entry **body**. Parsers read from `parse_from` (default `body`) and
          write to `parse_to` (default `attributes`).
        
        ## Parsers
        
        | `type` | Parses |
        |--------|--------|
        | `container` | Docker / CRI-O / containerd container log formats — the standard first parser for Kubernetes pod logs (`/var/log/pods/...`). |
        | `json_parser` | A JSON object into fields. |
        | `json_array_parser` | A JSON array into a list of values. |
        | `csv_parser` | CSV using a configured header. |
        | `regex_parser` | Named capture groups of a regex into fields. |
        | `key_value_parser` | `k=v` pairs into fields. |
        | `uri_parser` | A URI string into its components. |
        | `syslog_parser` | RFC 3164 / RFC 5424 syslog. |
        | `severity_parser` | A field's value into `SeverityNumber`/`SeverityText`. |
        | `time_parser` | A field's value into the record timestamp. |
        | `trace_parser` | Trace/span IDs and flags into the record's trace context. |
        | `scope_name_parser` | A field into the instrumentation scope name. |
        
        ### Embedded timestamp / severity / trace parsing
        
        Most parsers can run a follow-up timestamp, severity, and/or trace parse inline instead of
        needing a separate operator — the "complex parser" form. This is the idiomatic shape:
        
        ```yaml
        operators:
          - type: regex_parser
            regex: '^(?P<ts>\S+) (?P<sev>[A-Z]+) (?P<msg>.*)$'
            timestamp:
              parse_from: attributes.ts
              layout: '%Y-%m-%dT%H:%M:%S%z'   # strptime by default; layout_type: gotime|epoch also supported
            severity:
              parse_from: attributes.sev       # maps INFO/WARN/ERROR/... to SeverityNumber
        ```
        
        ## General-purpose operators
        
        | `type` | Does |
        |--------|------|
        | `add` | Add a field with a static value or expression. |
        | `copy` | Copy a field to another location. |
        | `move` | Move/rename a field. |
        | `remove` | Delete a field. |
        | `retain` | Keep only the listed fields, drop the rest. |
        | `flatten` | Flatten a nested map one level. |
        | `assign_keys` | Assign keys to a list, turning it into a map. |
        | `filter` | Drop an entry that matches an [expression](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/stanza/docs/types/expression.md) (entry-level drop, distinct from the `filter` processor). |
        | `router` | Branch entries to different downstream operators by expression (use with `output`). |
        | `recombine` | Combine multiple consecutive entries into one (e.g. stack traces, multi-line app logs). |
        | `regex_replace` | Regex find/replace on a field. |
        | `unquote` | Unquote a quoted string field. |
        | `sanitize_utf8` | Replace invalid UTF-8 sequences. |
        | `noop` | Pass through unchanged (placeholder/testing). |
        
        > `recombine` vs `multiline`: the top-level [`multiline`](advanced.md#multiline-entries) block
        > splits the *byte stream* into entries before any operator runs (one regex, line start/end).
        > The `recombine` operator merges *already-split* entries using an expression — more flexible
        > (e.g. "combine until the next line starts with a timestamp"), at more cost. Prefer `multiline`
        > for simple cases.
        
        ## Inputs (not used here)
        
        `file_input`, `journald_input`, `syslog_input`, `tcp_input`, `udp_input`,
        `windows_eventlog_input`, `stdin` exist in the stanza library but belong to their **own
        receivers** (`journald`, `tcplog`, `udplog`, `windowseventlog`, …). Don't place them in
        `file_log`'s `operators`.
        
        ## Worked example — JSON logs with timestamp and severity
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myservice/*.json ]
            start_at: beginning
            operators:
              - type: json_parser
                timestamp:
                  parse_from: attributes.time
                  layout: '%Y-%m-%d %H:%M:%S'
                severity:
                  parse_from: attributes.level
              - type: move                 # promote the parsed message into the body
                from: attributes.message
                to: body
        ```
        
      • quirks.md 3.8 KB
        # `file_log` receiver: known quirks
        
        ## `start_at` defaults to `end` — existing files look empty
        
        The single most common "the receiver reads nothing" problem. `start_at` defaults to `end`, so for
        a **newly discovered** file the receiver begins at the *current* end and only emits lines appended
        **after** it starts watching. A file that already exists and is not being actively written to
        produces **zero records**.
        
        ```yaml
        receivers:
          file_log:
            include: [ /var/log/myapp/*.log ]
            start_at: beginning            # read existing content; default `end` skips it
        ```
        
        Use `beginning` for batch/static files and first-run backfills; `end` is the right default for
        high-volume live logs where you don't want to re-read history on every restart (pair with
        `storage` so restarts resume mid-file).
        
        ## The type was renamed `filelog` → `file_log` in v0.149.0
        
        New configs should use `file_log`. The old `filelog` name still works as a deprecated alias but
        logs a startup warning:
        
        ```
        warn  "filelog" alias is deprecated; use "file_log" instead  {"otelcol.component.id": "filelog", ...}
        ```
        
        When reviewing an existing config, `filelog:` is **not** broken — it's the legacy spelling.
        
        ## Feature-gated options
        
        Some keys do nothing (or error) unless a feature gate is enabled at startup
        (`--feature-gates=<gate>`):
        
        | Option | Required gate | State (v0.162.0) |
        |--------|---------------|-------|
        | `delete_after_read` | `filelog.allowFileDeletion` | Beta (on by default) |
        | `header` parsing | `filelog.allowHeaderMetadataParsing` | Beta (on by default) — no flag needed |
        | `ordering_criteria.sort_by.sort_type: mtime` | `filelog.mtimeSortType` | Beta (**on by default** since v0.161.0; Alpha off before) |
        | require explicit `ordering_criteria.top_n` when sorting | `filelog.requireExplicitTopN` | Alpha (off by default); unset keeps the legacy default `1`, explicit `0` matches all files |
        | include/exclude case-insensitive globbing (Windows) | `filelog.windows.caseInsensitive` | Beta (on by default) |
        | protobuf checkpoint encoding | `filelog.protobufCheckpointEncoding` | Beta (**on by default** since v0.156.0; Alpha off in v0.148.0–v0.155.0) — ~7× faster decode, ~31% smaller; reads both formats either way |
        
        ## `delete_after_read` conflicts with `start_at: end`
        
        `delete_after_read: true` reads a file then deletes it; it must be paired with
        `start_at: beginning` (it is invalid with `start_at: end`) and requires the
        `filelog.allowFileDeletion` gate to remain enabled.
        
        ## Fingerprinting and re-ingestion
        
        Files are identified by the first `fingerprint_size` bytes (default `1000`, minimum `16`) plus
        inode. Two consequences:
        
        - **Decreasing `fingerprint_size`** changes identity for files larger than the new size →
          they are treated as new and **re-ingested**.
        - Files whose first `fingerprint_size` bytes are **identical** (e.g. a fixed boilerplate header)
          can be mistaken for the same file. Raise `fingerprint_size` past the common prefix, or ensure
          early bytes differ.
        
        ## Don't put input operators in `operators`
        
        The receiver *is* the file input. Adding `file_input` (or `tcp_input`, `journald_input`, …) to the
        `operators` list is wrong — only parsers and general-purpose operators belong there. Use the
        dedicated receiver for other inputs.
        
        ## `multiline` needs exactly one pattern
        
        The `multiline` block must specify **exactly one** of `line_start_pattern` /
        `line_end_pattern` — setting both, or neither (while present), is a config error.
        
        ## Owner/permission attributes are Unix-only
        
        `include_file_owner_name`, `include_file_owner_group_name`, and `include_file_permissions` are not
        supported on Windows.
        
        ## Stability
        
        `logs` is **Beta** (production-viable; breaking config changes rare). Distributed in `contrib` and
        `k8s` — **not** in the `core` or `otlp` distributions, so a minimal/core collector won't have it.
        
      • README.md 4.8 KB
        # `file_log` receiver
        
        | | |
        |-|-|
        | Kind | receiver |
        | Type | `file_log` (formerly `filelog`) |
        | Signals | logs (Beta) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/receiver/filelogreceiver` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver> |
        | Rename | `filelog` → `file_log` in **v0.149.0**; `filelog` kept as a deprecated alias (runtime warns `"filelog" alias is deprecated; use "file_log" instead`). |
        
        ## Description
        
        Tails log files from disk and turns each line (or multi-line entry) into an OTLP log record. You give it a list of glob patterns (`include`), and it discovers matching files, follows them as they grow, and tracks how far it has read into each one. File identity is established by a content **fingerprint** (the first `fingerprint_size` bytes) plus inode, so the receiver keeps reading correctly across both common rotation strategies — move/create and copy/truncate — even when the rotated file no longer matches `include`. Read offsets live in memory by default; point `storage` at a `file_storage` extension to make them survive a collector restart.
        
        The raw line lands in the log record `Body` and a few file attributes (`log.file.name` by default) are attached. Everything else — extracting fields, parsing timestamps and severity, combining stack traces into one record — is done by an ordered pipeline of **stanza operators** under `operators:` (`json_parser`, `regex_parser`, `severity_parser`, `recombine`, …). The operator framework, not the receiver's top-level keys, is where most real-world configuration lives.
        
        The single most common surprise: **`start_at` defaults to `end`**, so a file that already exists and is not being actively appended to yields **no records**. Set `start_at: beginning` to read existing content. See [quirks.md](quirks.md).
        
        For ordered discovery, explicit `ordering_criteria.top_n: 0` means "match all." The
        `filelog.requireExplicitTopN` migration gate rejects an omitted `top_n`.
        Since v0.160.0, `skip_unmodified_files: true` can avoid reopening and fingerprinting tracked files
        whose path and modification time have not changed since the previous poll.
        
        ## Main use-cases
        
        Use when:
        - You collect logs that an application writes to files (container stdout/stderr on a node, app log files, system logs) rather than sending over OTLP.
        - You need to parse unstructured or semi-structured text into structured records — JSON, regex-delimited, syslog, key/value — with timestamp and severity extraction.
        - You need durable offset tracking so a collector restart resumes where it left off (pair with a `file_storage` extension via `storage`).
        - You tail Kubernetes pod logs (`/var/log/pods/...`) — the canonical node-level log-collection path, typically followed by `k8s_attributes` for metadata enrichment.
        
        Avoid when:
        - The source already speaks OTLP — use the [`otlp`](../otlp/README.md) receiver.
        - You only need journald, Windows Event Log, TCP/UDP syslog, or stdin — use the dedicated receiver (`journald`, `windowseventlog`, `tcplog`/`udplog`, …) rather than wiring those stanza inputs by hand.
        
        ## Related components
        
        - [`otlp` receiver](../otlp/README.md) — the OTLP-native ingress; `file_log` is its file-tailing counterpart for logs.
        - [`k8s_attributes`](../k8s_attributes/README.md) — enrich tailed pod logs with Kubernetes metadata (namespace, pod, labels).
        - [`transform`](../transform/README.md) — OTTL-based post-processing once records are in the pipeline; the stanza `operators` do parsing at ingest, `transform` does mutation downstream.
        - [`memory_limiter`](../memory_limiter/README.md) — belongs **first** in the logs pipeline fed by this receiver.
        - [`file_storage`](../file_storage/README.md) extension — set `storage:` to it so read offsets survive collector restarts.
        
        ## Details
        
        - [Configuration](configuration.md) — every top-level config key (include/exclude, `start_at`, fingerprinting, sizing, rotation, `storage`, `header`, `ordering_criteria`) with defaults and validation, including `top_n: 0` semantics.
        - [Operators](operators.md) — the stanza operator pipeline: the available inputs/parsers/general-purpose operators, the `id`/`output` wiring rules, and embedded timestamp/severity parsing.
        - [Verification](verification.md) — a file-based recipe (write lines to a mounted file → `file_log` → `debug`), verified on contrib v0.159.0. Notes why `telemetrygen` cannot drive this receiver.
        - [Advanced use-cases](advanced.md) — multiline entries, encodings, durable offsets via `storage`, header metadata parsing, log-rotation handling (`on_truncate`), file ordering, and `compression`.
        - [Known quirks](quirks.md) — the `start_at: end` default, the rename/alias, feature-gated options, `delete_after_read` constraints, fingerprint re-ingestion, and stability.
        
      • verification.md 3.4 KB
        # `file_log` receiver: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        > **`telemetrygen` cannot drive this receiver.** `telemetrygen` emits OTLP over the network; the
        > `file_log` receiver reads files from disk. There is no `telemetrygen` flag that writes a log
        > file. The input here is a **file you write yourself** and mount into the collector container.
        
        The `file_log` receiver ships in the `contrib` and `k8s` distributions. This recipe runs **one**
        collector — `file_log` tailing a mounted directory, feeding a `debug` exporter — and proves the
        pipeline by writing three pre-formatted lines and reading them back parsed.
        
        Verified on `otel/opentelemetry-collector-contrib:0.159.0` (2026-08-27).
        
        Config (`file_log.yaml`) — note `start_at: beginning`, without which an existing idle file is
        skipped (the receiver's top gotcha):
        
        ```yaml
        receivers:
          file_log:
            include: [ /logs/*.log ]
            start_at: beginning            # default `end` would read nothing from a static file
            operators:
              - type: regex_parser
                regex: '^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?P<sev>[A-Z]+) (?P<msg>.*)$'
                timestamp:
                  parse_from: attributes.ts
                  layout: '%Y-%m-%d %H:%M:%S'
                severity:
                  parse_from: attributes.sev
        exporters:
          debug:
            verbosity: detailed            # basic verbosity prints no per-record lines
        service:
          pipelines:
            logs:
              receivers: [file_log]
              exporters: [debug]
        ```
        
        Create the log file the receiver will tail:
        
        ```bash
        mkdir -p /tmp/filelog-verify/logs
        cat > /tmp/filelog-verify/logs/app.log <<'EOF'
        2026-08-27 09:00:01 INFO service started
        2026-08-27 09:00:02 WARN cache miss for key=42
        2026-08-27 09:00:03 ERROR failed to connect to upstream
        EOF
        ```
        
        Start the collector, mounting both the config and the log directory:
        
        ```bash
        IMG=otel/opentelemetry-collector-contrib:0.159.0
        docker run -d --name fcol \
          -v "$PWD/file_log.yaml:/etc/otelcol-contrib/config.yaml" \
          -v /tmp/filelog-verify/logs:/logs \
          $IMG
        ```
        
        **What proves it worked:** count the records the `debug` exporter logged, and confirm severity
        was parsed:
        
        ```bash
        docker logs fcol 2>&1 | grep -c 'LogRecord #'
        docker logs fcol 2>&1 | grep -E 'SeverityText|-> msg:'
        ```
        
        Expected output — **3 records**, each with the
        raw line in the body, a parsed `msg` attribute, the default `log.file.name=app.log` attribute,
        and severity mapped from the text:
        
        ```
        3
        SeverityText: INFO
             -> msg: Str(service started)
        SeverityText: WARN
             -> msg: Str(cache miss for key=42)
        SeverityText: ERROR
             -> msg: Str(failed to connect to upstream)
        ```
        
        If the count is **0**, the most likely cause is the `start_at: end` default (the file existed
        before the collector started and isn't being appended to) — set `start_at: beginning`, or append
        a new line *after* the collector is running.
        
        To watch live tailing instead, leave the collector running and append a line:
        
        ```bash
        echo "2026-08-27 09:00:04 INFO new line after startup" >> /tmp/filelog-verify/logs/app.log
        docker logs fcol 2>&1 | grep -c 'LogRecord #'     # now 4
        ```
        
        Tear down:
        
        ```bash
        docker rm -f fcol
        rm -rf /tmp/filelog-verify
        ```
        
        ## Note on the deprecated alias
        
        Using `filelog:` (the pre-v0.149.0 name) instead of `file_log:` still works but logs a warning
        at startup:
        
        ```
        warn  builders/builders.go:40  "filelog" alias is deprecated; use "file_log" instead  {"otelcol.component.id": "filelog", ...}
        ```
        
    • file_storage
      • advanced.md 5 KB
        # `file_storage`: advanced use-cases
        
        ## Persistent exporter queue (survives restart)
        
        By default an exporter's `sending_queue` is in-memory: data still queued at shutdown (or lost in a crash) is gone. Point the queue at `file_storage` and it becomes a **persistent queue** that survives a Collector restart:
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/storage
            create_directory: true
        receivers:
          otlp:
            protocols:
              grpc:
        exporters:
          otlp:
            endpoint: backend:4317
            tls:
              insecure: true
            sending_queue:
              enabled: true
              storage: file_storage        # <- queue persisted on disk
        service:
          extensions: [file_storage]
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [otlp]
        ```
        
        If the backend is unreachable and the queue fills disk during an outage, the still-undelivered batches are replayed after a restart instead of being dropped. See the [`otlp` exporter](../otlp_exporter/README.md) for the queue's own sizing knobs.
        
        ## Multiple named storage instances
        
        You can run several `file_storage` instances (`type/name`) so different consumers get their own directories — handy to isolate a large, spiky exporter queue from small, latency-sensitive receiver offsets:
        
        ```yaml
        extensions:
          file_storage/queue:
            directory: /var/lib/otelcol/queue
            create_directory: true
          file_storage/offsets:
            directory: /var/lib/otelcol/offsets
            create_directory: true
        receivers:
          file_log:
            include: [/var/log/app/*.log]
            storage: file_storage/offsets
        exporters:
          otlp:
            endpoint: backend:4317
            sending_queue:
              enabled: true
              storage: file_storage/queue
        service:
          extensions: [file_storage/queue, file_storage/offsets]
          pipelines:
            logs:
              receivers: [file_log]
              exporters: [otlp]
        ```
        
        Each instance still writes one bbolt file per consuming component inside its own `directory`.
        
        ## Tuning rebound compaction for persistent-queue spikes
        
        The classic case for online compaction: a persistent exporter queue balloons during a network outage, then drains once connectivity returns. bbolt is mmap-backed, so the freed pages stay **allocated** — the file does not shrink on its own. Rebound compaction reclaims that space, but only after the spike has clearly passed:
        
        ```text
        allocated MiB
           │            ┌──────────┐   <- spike: queue fills during outage
           │            │          │
        100├─ ─ ─ ─ ─ ─ ┘          │   <- rebound_needed_threshold_mib: arms the "needed" flag
           │                        ╲
         10├─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╲▼ <- rebound_trigger_threshold_mib: drops below -> compact now
           │                          └────────
           └──────────────────────────────────▶ time
        ```
        
        - `rebound_needed_threshold_mib` (default `100`) — allocation must exceed this for the extension to consider compaction needed at all (avoids compacting trivial usage).
        - `rebound_trigger_threshold_mib` (default `10`) — once "needed" is armed, compaction fires when allocation falls back below this, i.e. when the heavy period is over and the rewrite will be cheap.
        - `check_interval` (default `5s`) — how often these conditions are evaluated; **must be > 0** when `on_rebound: true`.
        
        Raise the thresholds if your steady-state working set is large (so normal operation never trips compaction); lower them if you want space reclaimed more eagerly after smaller spikes. `compaction.max_transaction_size` (default `65536`) bounds how many items move per compaction transaction.
        
        To hard-cap growth instead of just reclaiming after the fact, set `max_size` (bytes, default `0` = unlimited). It bounds **each** per-consumer bbolt file: once a file is at the cap, a write that needs to grow it is rejected with a storage-full error (writes that fit existing free space still succeed), so a runaway persistent queue can't fill the disk. If you also enable `on_rebound`, both rebound thresholds (×1,048,576) must be ≤ `max_size` or validation fails — see [quirks.md](quirks.md). In practice keep `rebound_needed_threshold_mib × 1,048,576` strictly **below** `max_size`: the "needed" flag only arms once allocation *exceeds* the threshold, and allocation can never exceed `max_size`, so a threshold exactly at the cap validates but can never fire.
        
        ## `create_directory` / `directory_permissions` for ephemeral and container environments
        
        In containers or fresh hosts the data directory often does not exist yet. Rather than pre-creating it in an init step, let the extension do it:
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/storage
            create_directory: true
            directory_permissions: "0700"   # only honored when create_directory is true
        ```
        
        `directory_permissions` defaults to `"0750"` and is applied minus the process umask. It is **ignored** unless `create_directory: true`. Remember the mounted/host directory must be writable by the uid the Collector runs as (the contrib image is non-root) — see [quirks.md](quirks.md).
        
      • configuration.md 5 KB
        # `file_storage`: configuration
        
        All keys live under the extension instance (e.g. `extensions: { file_storage: { … } }`), and the extension must be listed under `service.extensions:` to load. Facts below are traced to the `v0.157.0` contrib source (`config.go`, `factory.go`, `default_others.go`).
        
        ## Top-level keys
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `directory` | string | `/var/lib/otelcol/file_storage` (non-Windows) / `%ProgramData%\Otelcol\FileStorage` (Windows) | Dedicated data directory holding the per-consumer bbolt files. **Must already exist** unless `create_directory: true`. |
        | `timeout` | duration | `1s` | Max time to wait for a file lock before failing the operation. Rarely needs changing. |
        | `max_size` | int (bytes) | `0` | Max on-disk size of **each** per-consumer bbolt file, in bytes. A write that would grow the file past this is rejected with a storage-full error; writes that fit in already-allocated free space still succeed. `0` means unlimited. **Must be ≥ 0** and not exceed `math.MaxInt`. Added in v0.156.0. |
        | `fsync` | bool | `false` | When `true`, force an `fsync` after each DB write (integrity over performance; flips bbolt's `DB.NoSync`). |
        | `create_directory` | bool | `false` | When `true`, create the data (and compaction) directory if missing instead of failing validation. |
        | `directory_permissions` | string (octal) | `"0750"` (rwxr-x---) | Permissions used when creating directories, minus the process umask. Only relevant when `create_directory: true`. |
        | `recreate` | bool | `false` | When `true`, on certain bbolt corruption panics the extension renames the corrupted db to `{filename}.{ISO 8601 timestamp}.backup` and starts a fresh db so the Collector keeps running. State is lost / may be duplicated (contrib issue #35899). |
        
        The platform-specific `directory` defaults come from `default_others.go` (and its Windows counterpart).
        
        ## `compaction`
        
        bbolt is mmap-backed: when usage spikes, the file's allocated size grows and does **not** shrink on its own. Compaction rewrites the db to reclaim that freed-but-still-allocated space.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `compaction.on_start` | bool | `false` | Compact once at Collector start. |
        | `compaction.on_rebound` | bool | `false` | Online compaction triggered when rebound thresholds are met (see below). |
        | `compaction.directory` | string | same as the default data `directory` | Temp directory used as a midstep during compaction. |
        | `compaction.max_transaction_size` | int | `65536` | Max number of items in a single compaction transaction; `0` ignores transaction sizes. **Must be ≥ 0.** |
        | `compaction.rebound_needed_threshold_mib` | int | `100` | When allocated size exceeds this, the "compaction needed" flag is armed. |
        | `compaction.rebound_trigger_threshold_mib` | int | `10` | When the flag is armed and allocated size drops below this, compaction begins and the flag clears. |
        | `compaction.check_interval` | duration | `5s` | How often rebound conditions are checked. **Must be > 0 when `on_rebound: true`.** |
        | `compaction.cleanup_on_start` | bool | `false` | Remove leftover `tempdb*` files from the compaction directory at start (left behind if a prior process was killed mid-compaction). |
        
        **Rebound (online) compaction** reclaims space after a transient spike drains: the flag arms only once allocated size has exceeded `rebound_needed_threshold_mib`, then compaction fires when usage drops back below `rebound_trigger_threshold_mib`. See [advanced.md](advanced.md) for tuning.
        
        When `max_size` is set (> 0) **and** `on_rebound: true`, both rebound thresholds (converted MiB → bytes, i.e. ×1,048,576) must be ≤ `max_size`, or validation fails — otherwise the rebound machinery could never fire within the cap. See the validation table in [quirks.md](quirks.md).
        
        ## Per-consumer files and naming
        
        The extension creates **one bbolt file per consuming component**, named `<kind>_<type>_<name>` inside `directory`:
        
        - A `file_log` receiver with no instance name → `receiver_filelog_` (note the on-disk name uses the deprecated `filelog` type spelling and a trailing `_` for the empty name; see [quirks.md](quirks.md)).
        - A named `journald/myservice` receiver → `receiver_journald_myservice`.
        
        Only `A-Za-z0-9._-` are safe in the name; any other character (including `~` itself) is escaped as `~<hex unicode>`. Over-long names are truncated to stay within OS filename limits, and the component→filename mapping is written to the Collector logs.
        
        ## Directory permissions and umask
        
        `directory_permissions` (default `"0750"`) is applied **only** when `create_directory: true` creates a directory, and the effective mode is the configured value minus the process umask. The value must be valid octal with no bits outside `0777` — see the validation table in [quirks.md](quirks.md).
        
        ## Minimal example
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/storage
            create_directory: true        # create it on start if missing
        service:
          extensions: [file_storage]      # must be listed here to load
        ```
        
      • quirks.md 5.5 KB
        # `file_storage`: known quirks
        
        ## The directory must already exist
        
        By default `file_storage` does **not** create its data directory — if `directory` is missing, config validation fails with `directory must exist: … You can enable the create_directory option to automatically create it`. The fix is exactly what the error says: set `create_directory: true` (and, when compaction is enabled, the compaction directory must exist too, or be auto-created the same way).
        
        ## Validation errors → fix
        
        All from `config.go` `Validate()`:
        
        | Condition | Error | Fix |
        |-----------|-------|-----|
        | `directory` (or `compaction.directory` when `on_start`/`on_rebound` is set) does not exist, and `create_directory: false` | `directory must exist: … You can enable the create_directory option to automatically create it` | Create the dir, or set `create_directory: true`. |
        | Path exists but is not a directory | `<path> is not a directory` | Point `directory` at an actual directory. |
        | `compaction.max_transaction_size` < 0 | `max transaction size for compaction cannot be less than 0` | Use `0` (ignore sizes) or a positive value. |
        | `max_size` < 0 | `max size cannot be less than 0` | Use `0` (unlimited) or a positive byte count. |
        | `max_size` > `math.MaxInt` | `max size is too large` | Keep `max_size` within `math.MaxInt` bytes. |
        | `max_size` > 0, `on_rebound: true`, `rebound_needed_threshold_mib` × 1,048,576 > `max_size` | `compaction rebound needed threshold cannot be greater than max size` | Lower `rebound_needed_threshold_mib` or raise `max_size`. |
        | `max_size` > 0, `on_rebound: true`, `rebound_trigger_threshold_mib` × 1,048,576 > `max_size` | `compaction rebound trigger threshold cannot be greater than max size` | Lower `rebound_trigger_threshold_mib` or raise `max_size`. |
        | `compaction.on_rebound: true` with `check_interval <= 0` | `compaction check interval must be positive when rebound compaction is set` | Set `compaction.check_interval` to a positive duration (default `5s`). |
        | `create_directory: true` with non-octal `directory_permissions` | `directory_permissions value must be a valid octal representation` | Use a valid octal string, e.g. `"0750"`. |
        | `directory_permissions` with bits outside `0777` | `directory_permissions contain invalid bits for file access` | Keep permissions within `0777` (file-access bits only). |
        
        ## One bbolt file per consumer, with name escaping
        
        The extension writes a separate bbolt file per consuming component, named `<kind>_<type>_<name>` inside `directory`. Only `A-Za-z0-9._-` are safe; any other character (and `~` itself) is escaped as `~<hex unicode>`. Names too long for the OS filename limit are truncated, and the component→filename mapping is logged. So the file set in your storage dir is keyed to your component instances — renaming a receiver/exporter (or its instance name) creates a **new** file and orphans the old state.
        
        ## The on-disk name is `receiver_filelog_`, not `receiver_file_log_`
        
        A `file_log` receiver with no instance name produces a bbolt file named **`receiver_filelog_`** — using the deprecated `filelog` type spelling and a trailing `_` for the empty name — even though your config says `file_log`. This is expected; don't go looking for `receiver_file_log_`.
        
        ## bbolt corruption and `recreate` can lose or duplicate state
        
        bbolt is generally durable, but on certain corruption panics the extension can either crash the Collector or — with `recreate: true` — rename the corrupted db to `{filename}.{ISO 8601 timestamp}.backup` and start a fresh, empty db so the Collector keeps running (contrib issue #35899). The trade-off: the new db has **no** prior state, so offsets reset (data re-read) or a persistent queue is lost; a receiver may then re-emit already-processed data (duplicates). Treat `recreate` as availability-over-correctness.
        
        ## mmap space is not reclaimed until compaction
        
        bbolt is mmap-backed: after a usage spike (e.g. a persistent queue filling during an outage) the file's allocated size grows and does **not** shrink when the data is removed. Disk/mmap footprint stays high until a compaction runs. Enable `compaction.on_start` and/or `compaction.on_rebound` to reclaim it — see [advanced.md](advanced.md).
        
        ## `fsync` is a durability-vs-throughput trade-off
        
        `fsync: false` (the default) leaves bbolt's `DB.NoSync` behavior, which is faster but can lose the very latest writes on an unclean shutdown. `fsync: true` forces an `fsync` after every DB write for maximum integrity at a real throughput cost. Only enable it where losing the last few writes is unacceptable.
        
        ## Container volume / permission gotchas
        
        The contrib image runs as a **non-root** uid. A mounted host directory (or a Kubernetes volume) must be **writable by that uid**, or the extension can't create/open its bbolt files. In the verification recipe this is why the scratch tree is `chmod -R 777`. In production, set ownership/`fsGroup` appropriately rather than world-writable. `directory_permissions` only affects directories the extension itself creates (with `create_directory: true`); it does not fix a non-writable mount.
        
        ## Reading the data for troubleshooting
        
        The bbolt file's contents are readable with the `strings` utility, which is handy for confirming a consumer actually wrote state. This is an implementation detail and may change if the extension moves off bbolt — don't build tooling on it.
        
        ## Stability
        
        The extension is **beta**. It is widely used and stable in practice, but the config surface can still change between releases — confirm keys/defaults against the exact contrib version you run.
        
      • README.md 3.3 KB
        # `file_storage` extension
        
        | | |
        |-|-|
        | Kind | extension (storage) |
        | Type | `file_storage` |
        | Stability | beta |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/filestorage` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/storage/filestorage> |
        
        ## Description
        
        A **storage extension** that persists other components' state to local disk, backed by **bbolt** (an mmap-based on-disk B+tree key/value store). It gives the Collector durable, restart-surviving state: a receiver's read offsets, an exporter's persistent send queue, or a stateful processor's decision cache all live in files under a single `directory`. The extension writes **one bbolt file per consuming component**, named `<kind>_<type>_<name>` inside that directory.
        
        Unlike a processor or receiver, `file_storage` does **not** sit in a pipeline. It is loaded by listing it under `service.extensions:`, and is then referenced by other components — receivers via their `storage:` key, exporters via `sending_queue.storage:`. Without it, those components hold their state in memory and lose it on every restart.
        
        ## Main use-cases
        
        Use when:
        - A receiver (e.g. [`file_log`](../file_log/README.md)) must resume reading where it left off after a restart instead of re-reading or skipping data — persist its offsets via `storage:`.
        - An exporter needs a **persistent send queue** that survives a restart or crash — set `sending_queue.storage: file_storage` (see the [`otlp` exporter](../otlp_exporter/README.md)).
        - A stateful processor needs durable state across restarts — e.g. the [`tail_sampling`](../tail_sampling/README.md) decision cache or [`drain`](../drain/README.md).
        
        Avoid when:
        - You are running a stateless pipeline where losing in-memory buffers on restart is acceptable — the extension adds disk I/O and a writable directory requirement for no benefit.
        - The Collector has no stable local disk (e.g. a fully ephemeral container with no volume) — state would not actually survive a restart.
        
        ## Related components
        
        - [`file_log`](../file_log/README.md) receiver — its `storage:` key points here to persist read offsets/checkpoints across restarts.
        - [`otlp` exporter](../otlp_exporter/README.md) — set `sending_queue.storage: file_storage` for a restart-durable persistent queue (otherwise the queue is in-memory).
        - [`tail_sampling`](../tail_sampling/README.md) / [`drain`](../drain/README.md) — stateful processors whose decision/aggregation caches can be made durable via this extension.
        
        ## Details
        
        - [Configuration](configuration.md) — full top-level and `compaction` config tables, the bbolt per-consumer file naming, and the directory-permissions/umask detail.
        - [Verification](verification.md) — offset-persistence-across-restart recipe (no telemetrygen) proving the extension survives a Collector restart.
        - [Advanced use-cases](advanced.md) — persistent exporter queues, multiple named storage instances, rebound-compaction tuning, and `create_directory` for ephemeral/container environments.
        - [Known quirks](quirks.md) — directory-must-exist gotcha, the validation-error→fix table, per-consumer file naming and the `receiver_filelog_` surprise, bbolt corruption + `recreate`, mmap reclamation, fsync trade-off, and container permission gotchas.
        
      • verification.md 4.2 KB
        # `file_storage`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `file_storage` is an **extension**, so it does not sit in a pipeline and there is no "telemetrygen → component → debug" path to exercise it directly. The behavior worth proving is **state durability across a Collector restart**. This recipe drives the extension through a [`file_log`](../file_log/README.md) receiver and shows that the receiver's read offset survives a restart: after a restart, already-read lines are **not** re-read even with `start_at: beginning`. No `telemetrygen` is involved — the input is a file on disk (like the `file_log` page). Verified end-to-end on `otel/opentelemetry-collector-contrib:0.154.0`.
        
        ## Setup
        
        In a scratch dir (`/tmp/fsverify`), create a `storage/` dir and a `logs/` dir. The contrib image runs as a non-root uid, so a mounted host dir must be writable by it — make the scratch tree world-writable for the repro:
        
        ```bash
        mkdir -p /tmp/fsverify/storage /tmp/fsverify/logs
        printf 'line one\nline two\nline three\n' > /tmp/fsverify/logs/app.log
        chmod -R 777 /tmp/fsverify
        cd /tmp/fsverify
        ```
        
        Config `fs.yaml` (minimal repro — omits `memory_limiter` etc. on purpose, to isolate the extension):
        
        ```yaml
        extensions:
          file_storage:
            directory: /storage          # mounted host dir; exists, so create_directory not needed
        receivers:
          file_log:
            include: [/logs/app.log]
            start_at: beginning
            storage: file_storage        # <- receiver persists its read offset here
        exporters:
          debug:
            verbosity: detailed          # basic verbosity prints no per-record lines
        service:
          extensions: [file_storage]     # extensions must be listed here to load
          pipelines:
            logs:
              receivers: [file_log]
              exporters: [debug]
        ```
        
        ## Run 1 — first read
        
        Start the Collector, mounting **both** the storage and logs dirs:
        
        ```bash
        IMG=otel/opentelemetry-collector-contrib:0.154.0
        docker run -d --name fsrun1 \
          -v "$PWD/fs.yaml:/etc/otelcol-contrib/config.yaml" \
          -v "$PWD/storage:/storage" -v "$PWD/logs:/logs" $IMG
        sleep 5
        docker logs fsrun1 2>&1 | grep -c '^LogRecord #'        # => 3
        docker logs fsrun1 2>&1 | grep 'Body: Str(line'         # => line one/two/three
        ls /tmp/fsverify/storage                                # => receiver_filelog_  (the bbolt file)
        docker rm -f fsrun1
        ```
        
        **Verified result:** 3 records (`line one`, `line two`, `line three`), and a bbolt file **`receiver_filelog_`** (32768 bytes) appears in the storage dir. Note the on-disk name is `receiver_filelog_` (the deprecated `filelog` type spelling, trailing `_` for the empty instance name) even though the config uses `file_log` — see [quirks.md](quirks.md).
        
        ## Run 2 — restart resumes from the persisted offset
        
        Append 2 lines and restart with the **same** storage dir:
        
        ```bash
        printf 'line four\nline five\n' >> /tmp/fsverify/logs/app.log   # now 5 lines
        docker run -d --name fsrun2 \
          -v "$PWD/fs.yaml:/etc/otelcol-contrib/config.yaml" \
          -v "$PWD/storage:/storage" -v "$PWD/logs:/logs" $IMG
        sleep 5
        docker logs fsrun2 2>&1 | grep -c '^LogRecord #'        # => 2
        docker logs fsrun2 2>&1 | grep 'Body: Str(line'         # => line four/line five ONLY
        docker rm -f fsrun2
        ```
        
        **Verified result:** only **2** records (`line four`, `line five`). The persisted offset means the first 3 lines are **not** re-read, even though `start_at: beginning`.
        
        ## Control — without `storage`, everything is re-read
        
        Same restart, but with the `storage:` line removed from the receiver (`fs_nostore.yaml` = `fs.yaml` minus that line):
        
        ```bash
        sed '/storage: file_storage/d' fs.yaml > fs_nostore.yaml   # fs.yaml minus the receiver's storage: line
        docker run -d --name fsctl \
          -v "$PWD/fs_nostore.yaml:/etc/otelcol-contrib/config.yaml" \
          -v "$PWD/storage:/storage" -v "$PWD/logs:/logs" $IMG
        sleep 5
        docker logs fsctl 2>&1 | grep -c '^LogRecord #'         # => 5  (re-reads everything)
        docker rm -f fsctl
        ```
        
        **Verified result:** **5** records — without `storage`, the restart re-reads all 5 lines (no offset memory). This contrast proves the `file_storage` extension is what preserved the offset.
        
        ## Tear down
        
        ```bash
        docker rm -f fsrun1 fsrun2 fsctl 2>/dev/null
        rm -rf /tmp/fsverify
        ```
        
    • filter
      • advanced.md 4.4 KB
        # `filter`: advanced use-cases
        
        ## Combining conditions
        
        Conditions within a list are ORed — any match drops the item. Use `and`/`or`/`not()` and parentheses inside a single condition for AND logic:
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            trace_conditions:
              # OR across the list — drop either name
              - 'span.name == "readiness"'
              - 'span.name == "liveness"'
              # AND within one condition — short, successful spans only
              - '(span.end_time - span.start_time) < Duration("10ms") and span.status.code == STATUS_CODE_OK'
        ```
        
        ## Advanced style: explicit context and per-group error_mode
        
        Each entry in a `*_conditions` list can be an object that pins a `context` and/or overrides `error_mode` for its group of `conditions`. Set `context` only when inference can't resolve it — e.g. a bare `IsRootSpan()` with no path prefix:
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            trace_conditions:
              - context: span            # required: IsRootSpan() has no prefix to infer from
                error_mode: propagate    # override just this group
                conditions:
                  - IsRootSpan()
              - conditions:
                  - spanevent.name == "grpc.timeout"
        ```
        
        Basic (flat strings) and advanced (objects) styles cannot be mixed within one signal.
        
        ## error_mode
        
        ```yaml
        # Default: ignore — a bad condition logs but does not drop unrelated data
        processors:
          filter:
            error_mode: ignore
        ```
        
        ```yaml
        # Development: catch broken conditions immediately (must set explicitly; ignore is the default)
        processors:
          filter:
            error_mode: propagate   # a failing condition drops the whole batch
        ```
        
        ```yaml
        # Noisy environments: continue without logging the error at all
        processors:
          filter:
            error_mode: silent
        ```
        
        ## Metric and datapoint filtering
        
        Drop whole metrics by name or type (cheapest), or individual datapoints by value/attribute:
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            metric_conditions:
              # metric-only converters (HasAttrKeyOnDatapoint / HasAttrOnDatapoint) carry no
              # path prefix, so their context can't be inferred — pin context: metric explicitly.
              - context: metric
                conditions:
                  - 'IsMatch(metric.name, "internal\\..*")'
                  - 'metric.type == METRIC_DATA_TYPE_HISTOGRAM'
                  - 'HasAttrKeyOnDatapoint("internal")'
                  - 'HasAttrOnDatapoint("environment", "test")'
              # datapoint-level check, inferred separately (metric context can't see datapoint.value_int)
              - conditions:
                  - 'metric.name == "k8s.pod.phase" and datapoint.value_int == 4'
        ```
        
        Filter at `metric` level when you want to drop the whole series — it is cheaper than evaluating every datapoint. See [Known quirks](quirks.md) for the drop-last-datapoint rule.
        
        ## Filtering by resource attribute
        
        Resource attributes are visible in every signal, so the same condition shape works across them — useful for stripping an environment or service wholesale:
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            trace_conditions:
              - 'resource.attributes["deployment.environment"] == "dev"'
            metric_conditions:
              - 'resource.attributes["deployment.environment"] == "dev"'
            log_conditions:
              - 'resource.attributes["deployment.environment"] == "dev"'
        ```
        
        ## OTTL function usage
        
        The processor exposes the standard OTTL converters (see the `otel-ottl` skill) plus the two metric-only functions above:
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            trace_conditions:
              - 'IsMatch(resource.attributes["k8s.pod.name"], "canary-.*")'
            log_conditions:
              - 'IsMatch(log.body, ".*(password|secret|api[_-]?key).*")'
              - 'log.attributes["http.request.method"] == nil'
        ```
        
        ## Named instances
        
        ```yaml
        processors:
          filter/health:
            error_mode: ignore
            trace_conditions:
              - 'span.name == "health_check"'
          filter/debug-logs:
            error_mode: ignore
            log_conditions:
              - 'log.severity_number < SEVERITY_NUMBER_INFO'
        
        service:
          pipelines:
            traces:
              processors: [memory_limiter, filter/health]
            logs:
              processors: [memory_limiter, filter/debug-logs]
        ```
        
        ## OTTL context
        
        The `*_conditions` lists infer their context (`resource`/`scope`/`span`/`spanevent`, `metric`/`datapoint`, `log`, `profile`) from the path prefixes used; the filter processor does **not** expose an `exemplar` context. Path and converter inventories live in the `otel-ottl` skill; the field summary per context is in [configuration.md](configuration.md).
        
      • configuration.md 7.5 KB
        # `filter`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          filter:
            error_mode: ignore
            trace_conditions:
              - span.name == "health_check"
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [memory_limiter, filter]
              exporters: [otlphttp]
        ```
        
        ## Top-level keys
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `error_mode` | string | `ignore` | How OTTL condition-evaluation errors are handled: `propagate`, `ignore`, or `silent`. See [Error mode](#error-mode). |
        | `trace_conditions` | list | — | Trace conditions (contexts: `resource`, `scope`, `span`, `spanevent`). |
        | `metric_conditions` | list | — | Metric conditions (contexts: `resource`, `scope`, `metric`, `datapoint`). |
        | `log_conditions` | list | — | Log conditions (contexts: `resource`, `scope`, `log`). |
        | `profile_conditions` | list | — | Profile conditions (contexts: `resource`, `scope`, `profile`). **Development** stability. |
        | `traces` / `metrics` / `logs` / `profiles` | block | — | **Deprecated** per-signal blocks and legacy `include`/`exclude` matchers. See [Legacy configuration](#legacy-configuration). |
        
        > **Default `error_mode` is permanently `ignore` as of v0.157.0.** The `processor.filter.defaultErrorModeIgnore` gate is Stable since v0.157.0; registered and cannot be disabled (disabling it fails startup). On v0.153.0–v0.156.0 it was Beta and could still be disabled to restore the old `propagate` default.
        
        ## The `*_conditions` fields
        
        `trace_conditions`, `metric_conditions`, `log_conditions`, and `profile_conditions` are the current, recommended surface (documented upstream from v0.146.0). Each takes a list of OTTL boolean conditions; an item is **dropped** when any condition evaluates to `true` (conditions are ORed). The expression language — operators, paths, converter functions — is covered in the `otel-ottl` skill; only the filter-specific surface is described here.
        
        ### Contexts and path prefixes
        
        Within a `*_conditions` list, paths are **prefixed by context** and the processor **infers** the context from the prefixes used, so you rarely set it by hand.
        
        | Field | Available contexts | Representative paths |
        |-------|--------------------|----------------------|
        | `trace_conditions` | `resource`, `scope`, `span`, `spanevent` | `span.name`, `span.attributes[...]`, `span.status.code`, `span.kind`, `span.start_time`, `span.end_time`, `spanevent.name`, `resource.attributes[...]`, `scope.name` |
        | `metric_conditions` | `resource`, `scope`, `metric`, `datapoint` | `metric.name`, `metric.type`, `metric.unit`, `datapoint.attributes[...]`, `datapoint.value_int`, `datapoint.value_double`, `resource.attributes[...]` |
        | `log_conditions` | `resource`, `scope`, `log` | `log.body`, `log.attributes[...]`, `log.severity_number`, `log.severity_text`, `resource.attributes[...]`, `scope.name` |
        | `profile_conditions` | `resource`, `scope`, `profile` | `profile.duration_unix_nano`, `resource.attributes[...]` |
        
        The filter processor supports **only** the contexts above — notably **no `exemplar` context** (that path exists in the `transform` processor, which mutates rather than drops).
        
        **Hierarchy.** Conditions run higher-to-lower (`resource` → `scope` → signal-specific). If a higher-level item is dropped, lower-level conditions for it are skipped: a dropped span skips its `spanevent` conditions. If all datapoints of a metric are dropped, the metric is dropped too; if all span events of a span are dropped, the span is left intact. When one condition mixes paths from two contexts, it is evaluated in the **lower** context (e.g. `resource` + `spanevent` → evaluated per span event). See [Known quirks](quirks.md).
        
        **Metric-only converters.** This processor adds two functions that must run in the **`metric`** context: `HasAttrKeyOnDatapoint(key)` (true if any datapoint carries that attribute key) and `HasAttrOnDatapoint(key, value)` (true if any datapoint has that string key/value). Because they carry no path prefix, their context **cannot be inferred** — pin `context: metric` explicitly (advanced style), or validation fails with `unable to infer context from conditions`.
        
        ### Basic vs advanced style
        
        **Basic** — a flat list of OTTL strings, ORed, context inferred per condition:
        
        ```yaml
        filter:
          error_mode: ignore
          trace_conditions:
            - span.attributes["container.name"] == "app_container_1"
            - resource.attributes["host.name"] == "localhost"
            - span.name == "app_3"
        ```
        
        **Advanced** — objects that pin a `context` and/or override `error_mode` for a group of `conditions`:
        
        ```yaml
        filter:
          error_mode: ignore
          trace_conditions:
            - context: span            # set explicitly only when inference can't (e.g. IsRootSpan())
              error_mode: propagate    # overrides the top-level error_mode for this group
              conditions:
                - IsRootSpan()
            - conditions:
                - spanevent.name == "grpc.timeout"
        ```
        
        Set `context` explicitly only when a condition has no path prefix to infer from (e.g. a bare `IsRootSpan()`), or when a combination of paths/functions/enums is not allowed in a single inferred context. Basic and advanced (and the deprecated forms) **cannot be mixed within one signal** — validation rejects mixing configuration styles.
        
        ## Error mode
        
        | Mode | Behavior | When |
        |------|----------|------|
        | `ignore` | Logs the error and moves to the next condition. | **Default and recommended** — a bad condition won't drop unrelated data. |
        | `silent` | Continues without logging. | When error logging is too noisy. |
        | `propagate` | Returns the error up the pipeline, dropping the whole payload. | Development/testing — surfaces config mistakes immediately. |
        
        ## Legacy configuration
        
        Two older forms still work but are **deprecated** and slated for removal; both are documented upstream only for versions before v0.146.0. Prefer `*_conditions` for new configs.
        
        **Deprecated per-signal OTTL blocks** — same conditions, unprefixed paths inside a fixed context. Migrate by moving each into the matching `*_conditions` list with the context prefix:
        
        | Deprecated | Migrate to |
        |------------|------------|
        | `traces.resource` / `traces.span` / `traces.spanevent` | `trace_conditions` with `resource.` / `span.` / `spanevent.` prefix |
        | `metrics.resource` / `metrics.metric` / `metrics.datapoint` | `metric_conditions` with `resource.` / `metric.` / `datapoint.` prefix |
        | `logs.resource` / `logs.log_record` | `log_conditions` with `resource.` / `log.` prefix |
        | `profiles.resource` / `profiles.profile` | `profile_conditions` with `resource.` / `profile.` prefix |
        
        **Pre-OTTL include/exclude** (metrics and logs only) — name/attribute/severity matchers. Cannot be combined with any OTTL conditions for the same signal.
        
        Metrics — match by name and/or resource attribute:
        
        ```yaml
        metrics:
          include:
            match_type: strict   # or regexp
            metric_names:
              - "system.cpu.utilization"
            resource_attributes:
              - key: "env"
                value: "production"
          exclude:
            match_type: regexp
            metric_names:
              - "internal\\..*"
        ```
        
        Logs — match by severity, body, or attribute:
        
        ```yaml
        logs:
          include:
            match_type: strict   # or regexp
            severity_number:
              min: "INFO"
              match_undefined: true
            bodies:
              - ".*error.*"
            record_attributes:
              - key: "user_id"
                value: ".*"
        ```
        
        Semantics: `include` alone keeps only matches; `exclude` alone drops matches; both means `include` is applied first, then `exclude`. `match_type` is `strict` (exact) or `regexp`. For metrics, a `regexp` block (`cacheenabled`, `cachemaxnumentries`) tunes the regex cache when `match_type: regexp`.
        
      • quirks.md 4.3 KB
        # `filter`: known quirks
        
        ## Data is permanently dropped
        
        `filter` removes data from the pipeline; there is no recovery downstream. Test conditions with `error_mode: propagate` (explicitly — the default is now `ignore`) and `debug` logging before rolling out.
        
        ## Dropping a metric's only datapoint removes the metric
        
        Filtering at the `datapoint` level that empties a metric also removes the metric — if all datapoints of a metric match, the whole metric disappears. Likewise, if all telemetry for a resource is dropped, the resource is removed from the payload, and if all span events of a span are dropped the span itself survives (only the events go). Be deliberate about which level you filter at.
        
        ## Orphaned telemetry / broken trace completeness
        
        - Dropping a **parent** span orphans its children — they remain in the trace but point at a span that no longer exists, which breaks waterfall views.
        - Dropping spans that logs reference (via trace/span ID) breaks log↔trace correlation.
        - Dropping arbitrary spans makes a trace incomplete. When you need a whole-trace keep/drop decision, use `tail_sampling`, not `filter`.
        
        ## error_mode behavior
        
        - Default is `ignore`: a failing condition is logged and skipped, valid data survives. The `processor.filter.defaultErrorModeIgnore` gate is Stable since v0.157.0; registered and cannot be disabled (disabling it fails startup).
        - Setting `error_mode: propagate` explicitly makes a single failing condition (e.g. a type mismatch or a nil dereference) **drop the entire batch**, not just the offending item. Older collectors (pre-v0.153.0) defaulted to `propagate`; on v0.153.0–v0.156.0 the Beta gate could also be disabled to restore that old default.
        - `silent` hides evaluation errors entirely; if a filter "isn't working," confirm it isn't set to `silent`.
        
        ## Stability caveats
        
        All signals are **Alpha** (profiles are **Development**). Config keys can still change between releases, and profile filtering may break. Check the upstream README for the running version before treating any key as stable.
        
        ## Troubleshooting
        
        **Nothing is dropped.**
        - Missing/wrong path prefix — in `*_conditions`, paths must be prefixed (`span.name`, `log.body`, `metric.name`, `datapoint.value_int`); the context is inferred from those prefixes.
        - Attribute doesn't exist — add a nil check: `log.attributes["x"] != nil and log.attributes["x"] == "y"`.
        - `error_mode: silent` is swallowing evaluation errors — switch to `propagate` while debugging.
        
        **Everything is dropped.**
        - Overly broad condition (`IsMatch(span.name, ".*")` matches all). Tighten it and re-test with debug logging.
        
        **Validation error: `configuring multiple configuration styles is not supported`.**
        - You mixed basic (flat string list) and advanced (`context`/`conditions` objects) forms in one signal — use one style.
        
        **Validation error: `cannot use context inferred ... conditions and the settings ... at the same time`.**
        - You mixed `*_conditions` with a deprecated per-signal block or include/exclude for the same signal. Pick one.
        
        **Verifying effectiveness.** The processor emits `processor_filter_spans.filtered`, `processor_filter_datapoints.filtered`, `processor_filter_logs.filtered` (and `processor_filter_profiles.filtered`, development) — watch these to confirm drops.
        
        ## Anti-patterns
        
        **Datapoint-level filtering when a whole metric should go.**
        
        ```yaml
        # Less efficient — inferred as datapoint context, evaluates every datapoint
        metric_conditions:
          - 'datapoint.attributes["env"] == "test"'
        ```
        
        ```yaml
        # Prefer — resource condition drops the whole resource in one check
        metric_conditions:
          - 'resource.attributes["env"] == "test"'
        ```
        
        **Dropping parent/correlated spans.**
        
        ```yaml
        # Risky — orphans children, breaks log correlation
        trace_conditions:
          - 'span.kind == SPAN_KIND_SERVER'
        ```
        
        Filter leaf/noise spans, or move whole-trace decisions to `tail_sampling`.
        
        **Complex negative-lookahead regex.**
        
        ```yaml
        # Rejected at config load — Go's RE2 engine has no lookahead
        log_conditions:
          - 'IsMatch(log.body, "^(?!.*(error|warn|fail)).*$")'
        ```
        
        `IsMatch` compiles with Go's RE2 engine, which does not support lookahead (`(?!`); the pattern above fails validation outright (`invalid or unsupported Perl syntax: `(?!``). Prefer explicit positive conditions (`log.severity_number < SEVERITY_NUMBER_WARN`).
        
      • README.md 3.4 KB
        # `filter` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `filter` |
        | Signals | traces (Alpha), metrics (Alpha), logs (Alpha), profiles (Development) |
        | Distributions | core, contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor> |
        
        ## Description
        
        Drops telemetry — spans, span events, metrics, datapoints, log records (and, in development, profiles) — from a pipeline when it matches an OTTL condition. Conditions are evaluated per item; when any condition evaluates to `true`, that item is dropped. Conditions in a list are ORed together. Filtering is hierarchical: if a higher-level item is dropped (e.g. a span), the lower-level conditions for it (e.g. its span events) are not evaluated.
        
        The current surface is the per-signal `trace_conditions` / `metric_conditions` / `log_conditions` / `profile_conditions` lists, which carry OTTL conditions with context-prefixed paths (`span.name`, `log.body`, …) and infer their context automatically. The top-level `error_mode` controls what happens when a condition fails to evaluate — `ignore` (the permanent default as of v0.157.0) logs and continues, `propagate` fails the batch, `silent` continues without logging. Two older forms are deprecated: the fixed-context per-signal blocks (`traces.span`, `metrics.metric`, `logs.log_record`, …) and a pre-OTTL include/exclude matcher (metrics and logs only) that cannot be combined with OTTL conditions for the same signal.
        
        ## Main use-cases
        
        Use it when:
        - You want to drop noise outright — health-check spans, debug/trace logs, internal or test-environment metrics.
        - You need to reduce backend volume by removing low-value signals at the collector.
        - You want to strip telemetry by resource attribute (environment, service, pod) before it leaves the pipeline.
        
        Avoid it when:
        - You want to **modify** rather than drop telemetry — use `transform`.
        - You need a content-aware keep/drop decision over a **whole trace** (errors, latency) — use `tail_sampling`.
        - Dropping would orphan child spans or break trace/log correlation (see [Known quirks](quirks.md)).
        
        ## Related components
        
        - `transform` — rewrites telemetry via OTTL instead of dropping it; same expression language.
        - `attributes` / `resource` — add or modify the attributes that filter conditions match on (run before `filter`).
        - `tail_sampling` — trace-wide, content-aware keep/drop sampling for traces.
        - `probabilistic_sampler` — stateless head sampling by trace ID.
        - `routing` — sends telemetry to different pipelines by condition rather than dropping it.
        
        ## Details
        
        - [Configuration](configuration.md) — `error_mode`, the `*_conditions` lists with their inferred contexts (`resource`/`scope`/`span`/`spanevent`, `metric`/`datapoint`, `log`, `profile`), basic vs advanced style, and the deprecated per-signal blocks and include/exclude matchers.
        - [Verification](verification.md) — telemetrygen recipe that drops a subset of logs by severity so the drop is observable.
        - [Advanced use-cases](advanced.md) — combining conditions, `error_mode`, metric/datapoint filtering, resource-attribute filters, OTTL functions, named instances.
        - [Known quirks](quirks.md) — orphaned telemetry, the drop-last-datapoint rule, error-mode behavior, stability caveats, anti-patterns, troubleshooting.
        
      • verification.md 2.1 KB
        # `filter`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `filter` ships in the `core`, `contrib`, and `k8s` distributions, so a stock contrib collector can run this.
        
        Verified on `otel/opentelemetry-collector-contrib:0.159.0` with
        `ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.159.0`
        (2026-08-27).
        
        Config (`filter-verify.yaml`) — drop `ERROR`-and-above logs, let everything below pass:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          filter:
            error_mode: ignore
            log_conditions:
              - 'log.severity_number >= SEVERITY_NUMBER_ERROR'
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [filter]
              exporters: [debug]
        ```
        
        Send two batches — one that the condition matches (dropped) and one that it doesn't (kept) — see the `otel-telemetrygen` skill:
        
        ```bash
        # Info logs (severity 9) — below ERROR, expect these to survive
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 10 --severity-number 9 --severity-text Info --body "info log keep me"
        # Error logs (severity 17) — at/above ERROR, expect these to be dropped
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 10 --severity-number 17 --severity-text Error --body "error log drop me"
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure`, `--otlp-endpoint`, `--logs`, `--severity-number` (int32, 1–24; 9=Info, 17=Error per the severity-number reference), `--severity-text`, and `--body`. Telemetrygen validates that severity text agrees with the numeric range, so state both explicitly. No `--duration` here — telemetrygen ignores `--logs` when `--duration` is set, so the count would not be 10.
        
        **What proves it worked:** the `debug` exporter prints the 10 Info records ("info log keep me") and **none** of the 10 Error records ("error log drop me"). Fewer records reach `debug` than were sent, and only the non-matching severity survives.
        
    • interval
      • advanced.md 741 B
        # `interval`: advanced use-cases
        
        ## Keep gauges spiky, smooth everything else
        
        ```yaml
        processors:
          interval:
            interval: 30s
            pass_through:
              gauge: true
              summary: true
        ```
        
        Use this when your gauges represent things you can't afford to flatten — request latency, queue depth, current connections.
        
        ## Multiple intervals for different pipelines
        
        ```yaml
        processors:
          interval/fast:
            interval: 15s
          interval/slow:
            interval: 5m
        
        service:
          pipelines:
            metrics/realtime:
              receivers: [otlp]
              processors: [memory_limiter, interval/fast]
              exporters: [otlp/dashboard]
            metrics/longterm:
              receivers: [otlp]
              processors: [memory_limiter, interval/slow]
              exporters: [otlp/warehouse]
        ```
        
      • configuration.md 3.5 KB
        # `interval`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          interval:
        
        service:
          pipelines:
            metrics:
              receivers: [otlp]
              processors: [memory_limiter, interval]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `interval` | duration | `60s` | Emission cadence. After each emission, internal state is cleared. |
        | `pass_through.gauge` | bool | `false` | If `true`, gauges are forwarded unchanged instead of buffered. |
        | `pass_through.summary` | bool | `false` | If `true`, summaries are forwarded unchanged instead of buffered. |
        
        Validation: `interval` must be greater than `0`. A zero or negative value fails startup with `invalid interval value` (`ErrInvalidIntervalValue` in source). `pass_through` booleans have no validation.
        
        > **Doc typo, upstream:** the README's configuration block shows `summary: <boo>l`. It's a typo for `<bool>` — the parameter is a plain boolean.
        
        ## What it does to each metric type
        
        | Metric type | Default behavior | Pass-through option |
        |-------------|------------------|---------------------|
        | Cumulative monotonic sum | Aggregated (only latest value per series emitted per interval) | — |
        | Cumulative monotonic histogram | Aggregated | — |
        | Cumulative monotonic exponential histogram | Aggregated | — |
        | Gauge | Aggregated (lossy — see below) | `pass_through.gauge: true` |
        | Summary | Aggregated (lossy — see below) | `pass_through.summary: true` |
        | Delta monotonic sum, delta histogram, delta exponential histogram | Accumulated: the interval's points are **added** per series and emitted once, with the earliest start and latest timestamp | — |
        | Non-monotonic sum (any temporality) | Passed through unchanged, immediately | — (always pass-through) |
        
        > **Doc drift, upstream:** as of v0.162.0 the upstream README still says all delta metrics pass through unchanged; the code (`processor.go`, PR #50922) aggregates them as above.
        
        ## What "lossy" means here
        
        - Monotonic cumulative series: you lose **precision** (intermediate values), not totals — the final cumulative value still represents the full count.
        - Delta series: per-point granularity is merged into one delta per interval; the total is preserved.
        - Gauges and summaries: actual **data loss**. A value that rose and fell back inside the interval is reduced to whatever the last reading happened to be. If the up-and-down matters (latency spikes, queue depth bursts), set `pass_through.gauge: true` / `pass_through.summary: true` to keep them flowing as-is.
        
        ## Behavior example
        
        Monotonic sums arriving into the processor:
        
        | Time | Metric | Temporality | Attributes | Value |
        |------|--------|-------------|------------|------:|
        | 0 | `test_metric` | Cumulative | `labelA: foo` | 4.0 |
        | 2 | `test_metric` | Cumulative | `labelA: bar` | 3.1 |
        | 4 | `other_metric` | Delta | `fruitType: orange` | 77.4 |
        | 6 | `test_metric` | Cumulative | `labelA: foo` | 8.2 |
        | 8 | `test_metric` | Cumulative | `labelA: foo` | 12.8 |
        | 10 | `test_metric` | Cumulative | `labelA: bar` | 6.4 |
        
        At the next interval boundary, the processor forwards the latest value of each cumulative series and the sum of each delta series (here a single point):
        
        | Time | Metric | Temporality | Attributes | Value |
        |------|--------|-------------|------------|------:|
        | 4 | `other_metric` | Delta | `fruitType: orange` | 77.4 |
        | 8 | `test_metric` | Cumulative | `labelA: foo` | 12.8 |
        | 10 | `test_metric` | Cumulative | `labelA: bar` | 6.4 |
        
        Then state is cleared.
        
      • quirks.md 2.4 KB
        # `interval`: known quirks
        
        ## State and restart behavior
        
        - Internal state is keyed by metric name + attribute set and is held entirely in memory.
        - After each emission the buffer is cleared. **If no new points arrive in the next interval, nothing is emitted for that series in that interval.**
        - The state is not persisted — a collector restart drops every buffered point. For monotonic cumulative metrics this is usually fine (the next scrape carries the running total); for buffered gauges/summaries the most recent value is simply lost until the next sample.
        
        ## Troubleshooting
        
        **Nothing comes out of the processor.**
        - All upstream metrics are non-monotonic sums — those pass through but produce no buffered emission. Confirm a monotonic sum, histogram, gauge, or summary reaches the processor.
        - No new samples arrived in the most recent interval — state is cleared at emission, so a silent source means a silent output. Reduce the source's scrape gap or increase `interval`.
        
        **Delta metrics show no volume drop.**
        - Before v0.162.0, delta metrics passed through unchanged. Upgrade the collector.
        
        **Gauge spikes disappeared from dashboards.**
        - Aggregation flattened them. Set `pass_through.gauge: true`.
        
        **Memory growth.**
        - Cardinality issue. The processor stores one entry per unique (metric, attribute set). Filter or drop high-cardinality attributes upstream (`transform` / `attributes` processor) before reaching `interval`.
        
        **Output cadence is irregular after a restart.**
        - Expected. The first interval after startup may emit less than usual because state was empty. Subsequent intervals settle once the upstream resends.
        
        ## Anti-patterns
        
        **Stacking `interval` with pass-through-only metrics.**
        
        ```yaml
        # BAD when the source emits only non-monotonic sums — they pass through anyway
        processors:
          interval:
        service:
          pipelines:
            metrics:
              processors: [interval]
        ```
        
        If nothing the source emits is aggregated, remove the processor.
        
        **Using it as a backpressure / rate-limit substitute.**
        
        `interval` reduces *point count* per series at emission boundaries; it does not throttle. Use `memory_limiter` (and the exporter's `sending_queue`) for backpressure.
        
        **Aggregating spiky gauges silently.**
        
        The default aggregates gauges and summaries. If you didn't explicitly think about it, flip `pass_through.gauge: true` until you have — losing a latency spike is harder to debug than ingesting one extra point per minute.
        
      • README.md 2.4 KB
        # `interval` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `interval` |
        | Signals | metrics |
        | Stability | Alpha |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/intervalprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/intervalprocessor> |
        
        ## Description
        
        Buffers metrics and emits one point per series once per interval: the latest value for cumulative series, gauges, and summaries, and the **sum** of the interval's points for delta series. Reduces metric point volume from chatty sources.
        
        By default it aggregates monotonic sums, histograms, and exponential histograms (cumulative or delta) plus gauges and summaries; non-monotonic sums pass through unchanged. Gauges and summaries can be kept flowing as-is with `pass_through`.
        
        ## Main use-cases
        
        Use it when:
        - A receiver scrapes or emits cumulative metrics far more often than your backend needs (e.g., Prometheus receiver at 1s for a backend that ingests at 60s).
        - You want to smooth bursty ingest into a fixed cadence downstream.
        - Backend cost scales with point count and you have many slow-changing series.
        
        Avoid it when:
        - The pipeline carries mostly non-monotonic sums — they bypass the processor entirely and you've added a stateful component for nothing.
        - Gauge spikiness matters and you can't set `pass_through.gauge: true` for the relevant signals.
        - The collector restarts often — buffered state is in-memory only and is lost on restart (see [State and restart behavior](quirks.md#state-and-restart-behavior)).
        
        ## Related components
        
        - `batch` / exporter `sending_queue.batch` — batches by size/time, does not collapse per-series points. Complementary, not a replacement.
        - `metricstransform`, `transform` — rewrite metrics; do not aggregate over time.
        - `cumulativetodelta` — converts temporality. Often paired with `interval` so cumulative-source metrics reach a delta-only backend as one delta per interval.
        
        ## Details
        
        - [Configuration](configuration.md) — config keys, defaults, per-metric-type behavior, and what "lossy" means.
        - [Verification](verification.md) — telemetrygen recipe to confirm point volume drops.
        - [Advanced use-cases](advanced.md) — keeping gauges spiky, multiple intervals per pipeline.
        - [Known quirks](quirks.md) — state/restart behavior, troubleshooting, anti-patterns.
        
      • verification.md 1.1 KB
        # `interval`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `interval` ships in the `contrib` and `k8s` distributions.
        
        Config (`interval-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          interval:
            interval: 10s
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            metrics:
              receivers: [otlp]
              processors: [interval]
              exporters: [debug]
        ```
        
        Generate cumulative-sum metrics frequently (see the `otel-telemetrygen` skill). Cumulative series keep their latest value per interval (delta series are summed instead):
        
        ```bash
        telemetrygen metrics --otlp-insecure --otlp-endpoint localhost:4317 \
          --metric-type Sum --aggregation-temporality cumulative --rate 5 --duration 25s
        ```
        
        **What proves it worked:** instead of ~5 points/sec reaching `debug`, the exporter prints the metric roughly once per 10s interval (one latest value per series). Compare against the same run with the processor removed to see the volume drop.
        
    • k8s_attributes
      • advanced.md 5 KB
        # `k8s_attributes`: advanced use-cases
        
        ## RBAC
        
        The processor reads the Kubernetes API, so its ServiceAccount needs permission for exactly the resources you extract. Grant `get`/`list`/`watch` and nothing more.
        
        **Minimum (any deployment)** — `pods` and `namespaces`:
        
        ```yaml
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata:
          name: otel-collector
        rules:
          - apiGroups: [""]
            resources: ["pods", "namespaces"]
            verbs: ["get", "watch", "list"]
        ```
        
        Add resources as you extract more metadata:
        
        | Extracted metadata | Add resource | API group |
        |--------------------|--------------|-----------|
        | `k8s.deployment.name` (default heuristic) | *nothing extra* — derived from the pod's owner ReplicaSet name | — |
        | `k8s.deployment.uid`, or deployment/ReplicaSet labels or annotations | `replicasets`; `deployments` for `from: deployment` | `apps`, `extensions` |
        | `k8s.statefulset.*` / `k8s.daemonset.*` | `statefulsets` / `daemonsets` | `apps` |
        | `k8s.job.*`, `k8s.cronjob.uid`, or `from: job` / `from: cronjob` extraction | `jobs`; additionally `cronjobs` for `from: cronjob` | `batch` |
        | `k8s.cronjob.name` alone | *nothing extra* — derived from the Job name via heuristic | — |
        | `k8s.node.*` or node labels/annotations | `nodes` | `` (core) |
        
        Bind it cluster-wide with a `ClusterRoleBinding` when the collector receives telemetry from many namespaces. When the collector only handles one namespace, use a namespaced `Role` + `RoleBinding` with `filter.namespace` set — least privilege, but it **cannot** read nodes, `k8s.cluster.uid`, or namespace labels/annotations.
        
        ## Agent/gateway split (passthrough)
        
        Run the processor twice across two tiers. A per-node **agent** in `passthrough: true` mode only stamps the source pod IP (`k8s.pod.ip`) — no API calls, no RBAC — while a central **gateway** does the real enrichment off that IP.
        
        ```yaml
        # Agent (DaemonSet) — just record the pod IP
        processors:
          k8s_attributes:
            passthrough: true
        ```
        
        ```yaml
        # Gateway (Deployment) — enrich off the IP the agent stamped
        processors:
          k8s_attributes:
            auth_type: serviceAccount
            extract:
              metadata: [k8s.namespace.name, k8s.pod.name, k8s.deployment.name, k8s.node.name]
            pod_association:
              - sources:
                  - {from: resource_attribute, name: k8s.pod.ip}
        ```
        
        This concentrates the API watch and RBAC on the gateway tier instead of every node.
        
        ## Filtering for memory (large clusters)
        
        Memory scales with pods watched (~5 KB/pod). The two biggest levers:
        
        ```yaml
        # DaemonSet: watch only this node's pods — set KUBE_NODE_NAME via the downward API
        filter:
          node_from_env_var: KUBE_NODE_NAME
        ```
        
        ```yaml
        # Single-namespace collector: watch only that namespace
        filter:
          namespace: production
        ```
        
        `node_from_env_var` pairs with a pod env var sourced from `spec.nodeName`:
        
        ```yaml
        env:
          - name: KUBE_NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
        ```
        
        ## Labels & annotations
        
        Turn Kubernetes labels/annotations into resource attributes. Use exact `key` matching where possible; `key_regex` matches the **key** and can fan out to many attributes (and costs CPU):
        
        ```yaml
        extract:
          labels:
            - {tag_name: app.label.component, key: app.kubernetes.io/component, from: pod}
            - {tag_name: team, key: team, from: deployment}   # needs deployments RBAC + watch
          annotations:
            - {tag_name: git.commit, key: git-commit, from: pod}
          otel_annotations: true   # also lift resource.opentelemetry.io/* annotations
        ```
        
        Extracting `from:` a workload (deployment/replicaset/statefulset/daemonset/job/cronjob) makes the processor watch that resource — extra RBAC and roughly +30% memory. Only do it when you need it.
        
        ## Deployment-name lookup
        
        `k8s.deployment.name` is derived by trimming the pod-template-hash off the pod's owner ReplicaSet name — no ReplicaSet watch, cheaper RBAC, less memory. If you configure an explicit `extract.metadata` list, include `k8s.deployment.name` (or `service.name`) for it to be produced; the omitted list already includes `k8s.deployment.name` by default. The former `deployment_name_from_replicaset` switch was removed in v0.160.0.
        
        The heuristic sets a wrong name for pods owned by a **standalone** ReplicaSet (one with no Deployment); in rare cases deployment names of 247–253 chars come back slightly truncated. Enable `k8s.deployment.uid` to start informer-backed lookup instead, at the cost of `replicasets` RBAC and extra memory:
        
        ```yaml
        extract:
          metadata: [k8s.deployment.name, k8s.deployment.uid]
        ```
        
        ## `wait_for_metadata`
        
        Block startup until the metadata cache has synced, so telemetry arriving immediately on boot is still enriched:
        
        ```yaml
        processors:
          k8s_attributes:
            wait_for_metadata: true
            wait_for_metadata_timeout: 30s
        ```
        
        Trade-off: guaranteed enrichment of early data, but a slower start — and if the sync doesn't finish within `wait_for_metadata_timeout` the collector **fails to start**. Leave it `false` (default) when fast, best-effort startup matters more than enriching the first few records.
        
      • configuration.md 7.6 KB
        # `k8s_attributes`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          k8s_attributes:
            auth_type: serviceAccount
            extract:
              metadata:
                - k8s.namespace.name
                - k8s.pod.name
                - k8s.pod.uid
                - k8s.deployment.name
                - k8s.node.name
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [k8s_attributes]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `auth_type` | string | `serviceAccount` | How to reach the API: `serviceAccount` (in-cluster), `kubeConfig` (local dev, reads `~/.kube/config` or `$KUBECONFIG`), or `none`. |
        | `context` | string | `""` | Kubeconfig context to use; only with `auth_type: kubeConfig`. |
        | `kube_api_qps` | float32 | `5` | Max queries/sec to the API. Raise if you see client-side throttling warnings. |
        | `kube_api_burst` | int | `10` | Max request burst to the API. Raise alongside `kube_api_qps` under throttling. |
        | `passthrough` | bool | `false` | Agent mode: only annotate the record with the pod IP (`k8s.pod.ip`), don't query the API or extract metadata. The gateway does the enrichment. |
        | `extract` | object | (see below) | Which metadata, labels, and annotations to add. |
        | `filter` | object | `{}` | Restrict which pods are watched (node/namespace/label/field). The main memory lever. |
        | `pod_association` | array | (see below) | Ordered strategies for tying a record to a pod; first match wins. |
        | `exclude` | object | excludes `jaeger-agent`, `jaeger-collector` | Pods to skip (by name, regex allowed; an invalid regex fails config validation since v0.162.0). A pod can also opt out with the annotation `opentelemetry.io/k8s-processor.ignore: "true"`. |
        | `wait_for_metadata` | bool | `false` | Block startup until the metadata cache has synced. |
        | `wait_for_metadata_timeout` | duration | `10s` | Max wait for the initial sync when `wait_for_metadata: true`; on timeout the collector fails to start. |
        | `watch_sync_period` | duration | `5m` | Informer cache resync period (v0.152.0). Set `0s` to disable resync — recommended for very large clusters, where periodic resyncs cause CPU spikes and memory churn. |
        | `pod_delete_grace_period` | duration | `120s` | How long a deleted pod's metadata is kept in the cache before eviction (v0.154.0), so late-arriving records for that pod can still be enriched. |
        
        ## `extract`
        
        ```yaml
        extract:
          metadata:        # resource attributes derived from pod/namespace/node/workload
            - k8s.namespace.name
            - k8s.pod.name
          labels:          # turn k8s labels into resource attributes
            - tag_name: app.label.component       # attribute name to write
              key: app.kubernetes.io/component    # exact label key (mutually exclusive with key_regex)
              from: pod                           # pod | namespace | node | deployment | replicaset | statefulset | daemonset | job | cronjob
          annotations:     # same shape as labels, for annotations
            - tag_name: git.commit
              key: git-commit
              from: pod
          otel_annotations: true                  # auto-extract resource.opentelemetry.io/* annotations
        ```
        
        **`labels` / `annotations` fields:**
        
        | Field | Default | Meaning |
        |-------|---------|---------|
        | `tag_name` | `k8s.<from>.label.<key>` / `k8s.<from>.annotation.<key>` (singular, v1 semconv — default since v0.161.0; plural `labels`/`annotations` when the semconv gates are disabled, see [quirks](quirks.md)) | Output attribute name. With `key_regex`, may use `$1` backreferences. |
        | `key` | — | Exact key to match. Mutually exclusive with `key_regex`. |
        | `key_regex` | — | Regex matched against the **key** (not the value). Mutually exclusive with `key`. |
        | `from` | `pod` | Source object: `pod`, `namespace`, `node`, `deployment`, `replicaset`, `statefulset`, `daemonset`, `job`, `cronjob`. |
        
        `otel_annotations: true` lifts pod annotations prefixed `resource.opentelemetry.io/` straight onto the resource (`resource.opentelemetry.io/foo: bar` → attribute `foo: bar`).
        
        `deployment_name_from_replicaset` was removed in v0.160.0 and is now an unknown-key startup error.
        Deployment names use the ReplicaSet-name heuristic unless `k8s.deployment.uid` or deployment /
        ReplicaSet label or annotation extraction starts the informer lookup. When configuring an explicit
        `extract.metadata` list, include `k8s.deployment.name` (or `service.name`) to emit the derived name;
        omitting the list uses the defaults below.
        
        ### Default-extracted metadata
        
        Even with no `extract.metadata` list, these are added: `k8s.namespace.name`, `k8s.pod.name`, `k8s.pod.uid`, `k8s.pod.start_time`, `k8s.deployment.name`, `k8s.node.name`, and (when a container identifier is present) `container.image.name` plus the image-tag attribute selected by the semantic-convention gates (`container.image.tags` with the v1 gates enabled — the default since v0.161.0 — or `container.image.tag` on v0; see [quirks](quirks.md)). Set an explicit `metadata` list to narrow this.
        
        ### Available metadata fields
        
        | Field | Requires |
        |-------|----------|
        | `k8s.namespace.name`, `k8s.pod.name`, `k8s.pod.uid`, `k8s.pod.hostname`, `k8s.pod.ip`, `k8s.pod.start_time` | — |
        | `k8s.deployment.name`, `k8s.deployment.uid`, `k8s.replicaset.name`, `k8s.replicaset.uid` | pod owned by a ReplicaSet |
        | `k8s.statefulset.*`, `k8s.daemonset.*`, `k8s.job.*` | pod owned by that workload |
        | `k8s.cronjob.name`, `k8s.cronjob.uid` | pod owned by a Job owned by a CronJob |
        | `k8s.node.name`, `k8s.node.uid` | — |
        | `k8s.cluster.uid` | reads the `kube-system` namespace UID |
        | `k8s.container.name`, `container.id`, `container.image.name`, `container.image.tag` (deprecated, use `container.image.tags`), `container.image.tags`, `container.image.repo_digests` | a container identifier on the record (see [quirks](quirks.md)) |
        | `service.namespace`, `service.name`, `service.version`, `service.instance.id` | calculated (see below) |
        
        `service.name` is calculated by semantic-convention priority: `app.kubernetes.io/instance` label → `app.kubernetes.io/name` label → workload name → pod name.
        
        ## `filter`
        
        ```yaml
        filter:
          node: node-1                        # only pods on this node (static name)
          node_from_env_var: KUBE_NODE_NAME   # DaemonSet: only this node's pods (set via downward API)
          namespace: production               # single namespace only
          labels:
            - {key: environment, value: production, op: equals}   # op: equals | not-equals | exists | does-not-exist
          fields:
            - {key: status.phase, value: Running, op: equals}     # op: equals | not-equals
        ```
        
        `filter.node` / `filter.node_from_env_var` and `filter.namespace` are the two big memory levers — without them a DaemonSet caches every pod in the cluster.
        
        ## `pod_association`
        
        Each association must be unique. v0.160.0 rejects duplicate source sets even when their sources are
        listed in a different order.
        
        Ordered list; the first association whose **every** source matches wins. Default order: `k8s.pod.ip` resource attr → `ip` resource attr → connection IP → `host.name` (if a valid IP).
        
        ```yaml
        pod_association:
          - sources: [{from: resource_attribute, name: k8s.pod.uid}]
          - sources:                                       # both must match (AND)
              - {from: resource_attribute, name: k8s.pod.name}
              - {from: resource_attribute, name: k8s.namespace.name}
          - sources: [{from: connection}]                  # IP from the incoming connection
        ```
        
        | `from` | `name` |
        |--------|--------|
        | `connection` | not used (uses the incoming connection IP) |
        | `resource_attribute` | the attribute key to read |
        
        Max **4** sources per association. Of the container-level attributes, only `container.id` is usable here; `service.version` and `service.instance.id` cannot be (they are computed at runtime).
        
      • quirks.md 4.9 KB
        # `k8s_attributes`: known quirks
        
        ## No enrichment usually means an association miss
        
        If telemetry passes through with no `k8s.*` attributes added, the record almost never matched a pod. Walk the [pod_association](configuration.md#pod_association) order: the default starts with the `k8s.pod.ip` resource attribute, then the connection IP. Data that arrives without a pod IP and without a usable connection (e.g. through a gateway) matches nothing. Inspect the incoming record with a `debug` exporter **before** the processor to see what it actually carries, then add a `pod_association` on an attribute the data still has (`k8s.pod.uid`, or `k8s.pod.name` + `k8s.namespace.name`). RBAC gaps produce the same symptom — check with `kubectl auth can-i get pods --as=system:serviceaccount:<ns>:otel-collector`.
        
        ## Connection-based association breaks after batching or load balancing
        
        The `from: connection` source reads the IP off the incoming gRPC/HTTP connection. Anything that re-originates the connection — a `batch` processor upstream, a load balancer, a gateway hop — drops that context, and connection association silently stops working. When telemetry crosses a network boundary, switch to a `resource_attribute` source (have a passthrough agent stamp `k8s.pod.ip`, or associate on `k8s.pod.uid`/`k8s.pod.name`).
        
        ## Memory scales with pods watched — filter
        
        Without a `filter`, a DaemonSet caches metadata for **every pod in the cluster** (~5 KB each), which OOMKills the collector at scale. Set `filter.node_from_env_var: KUBE_NODE_NAME` (DaemonSet) or `filter.namespace` (single-namespace collector). Extracting labels/annotations `from:` a workload adds another watch (+~30% memory) — only extract what you consume.
        
        ## Container attributes need a container identifier
        
        `container.image.name`, `container.image.tag`, `k8s.container.name`, etc. are only added when the record already carries a container identifier — `container.id` (recommended) or `k8s.container.name`. For a multi-container pod without one, the processor can't tell which container the data came from. With `k8s.container.name` but no `k8s.container.restart_count`, it may resolve the wrong container instance; add `k8s.container.restart_count` for an accurate `container.id`.
        
        ## The `regex` value-extraction field was removed
        
        The old `extract.labels[].regex` / `extract.annotations[].regex` fields (which parsed a **value** out of a label/annotation) no longer exist — the `k8sattr.fieldExtractConfigRegex.disallow` gate that disallowed them went Stable and was then removed. Extract the full value here, then parse it with `transform` and OTTL's `ExtractPatterns`. This is distinct from `key_regex`, which matches label/annotation **keys** and still works.
        
        ## Semantic-convention label/annotation format changed in v0.161.0
        
        Since v0.161.0 the `processor.k8sattributes.EmitV1K8sConventions` and `processor.k8sattributes.DontEmitV0K8sConventions` gates are **Beta (enabled by default)**, so label/annotation attributes use the **singular** v1 semconv form (`k8s.pod.label.<key>`, `k8s.node.annotation.<key>`, `k8s.namespace.label.<key>`, …) and the plural v0 form (`k8s.pod.labels.<key>`) is no longer emitted. The same gates switch `container.image.tag` (string) → `container.image.tags` (slice). Upgrading from ≤ v0.160 silently renames these attributes — repoint dashboards/queries, or dual-emit during migration with `--feature-gates=-processor.k8sattributes.DontEmitV0K8sConventions,processor.k8sattributes.EmitV1K8sConventions`; disable both to restore the old names. Enabling `DontEmitV0K8sConventions` without `EmitV1K8sConventions` is a startup error. The gates affect only the **default** `tag_name`; an explicit `tag_name` is unchanged. (The older `k8sattr.labelsAnnotationsSingular.allow` gate was **removed** in v0.155.0.)
        
        The internal-telemetry gates `processor.k8sattributes.telemetry.enableNewFormatMetrics` and
        `processor.k8sattributes.telemetry.disableOldFormatMetrics` are Beta and enabled by default in
        v0.160.0; the new metric names are emitted and old-format metrics are disabled by default.
        
        ## Don't use it for sidecars, or outside Kubernetes
        
        For a sidecar, inject pod metadata via the Kubernetes downward API as env vars — it's simpler and needs no RBAC. Outside Kubernetes there is no API to watch; use [`resource_detection`](../resource_detection/README.md) for cloud/host attributes instead. Host-network pods also can't be told apart by IP — associate them on `k8s.pod.name`/`k8s.pod.uid`.
        
        ## Stability caveats
        
        Traces, metrics, and logs are Stable since v0.161.0 (the Go module is versioned `v1.x`); profiles are Development. Ongoing efficiency work (e.g. `PartialObjectMetadata` informers, and the `otelcol.k8s.pod.association` internal metric — disabled in v0.151.0, then re-enabled in v0.153.0 with a low-cardinality `pod_identifier` attribute) means behavior and internal metrics still shift between releases — confirm against the upstream README for your exact collector version.
        
      • README.md 3.7 KB
        # `k8s_attributes` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `k8s_attributes` |
        | Signals | traces (Stable), metrics (Stable), logs (Stable), profiles (Development) — Stable since v0.161.0 (Go module `v1.x`) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor> |
        | Rename | `k8sattributes` → `k8s_attributes` in v0.146.0; old name kept as a deprecated alias. |
        
        ## Description
        
        Enriches telemetry with Kubernetes metadata. The processor watches the Kubernetes API for pod, namespace, node, and workload events and keeps an in-memory cache of their metadata. For each incoming span, metric, or log record it picks a pod by matching one of the configured **pod-association** sources (by default the pod IP, falling back to the connection IP), then adds the configured pod, namespace, node, and workload attributes (`k8s.pod.name`, `k8s.namespace.name`, `k8s.deployment.name`, …) to the record's **resource**.
        
        Association is the crux: enrichment only happens when a record can be tied back to a cached pod. Records arriving with a pod IP (or over a direct connection from the pod) match out of the box; records that have lost that signal — for example after batching or load balancing strips the connection — need an explicit `pod_association` on a resource attribute the data still carries. Memory scales with the number of pods watched, so node/namespace filtering is the main lever for large clusters. The processor needs access to a **Kubernetes API** — in-cluster via a ServiceAccount, or out-of-cluster via `auth_type: kubeConfig` — since there is no cluster to enrich from otherwise; for non-Kubernetes resource attributes, use [`resource_detection`](../resource_detection/README.md).
        
        ## Main use-cases
        
        Use it when:
        - The collector runs in Kubernetes and you want telemetry correlated with pod/namespace/node/workload metadata.
        - You want service discovery from pod labels/annotations (e.g. deriving `service.name`).
        - You run an agent/gateway split: a `passthrough` agent stamps the pod IP, a gateway does the full enrichment.
        
        Avoid it when:
        - The collector runs **outside** Kubernetes — use [`resource_detection`](../resource_detection/README.md) instead.
        - The pod already injects its metadata via the Kubernetes downward API (simpler for sidecars).
        - The metadata is already on the telemetry — re-deriving it only adds overhead.
        
        ## Related components
        
        - [`resource_detection`](../resource_detection/README.md) — detects environment resource attributes such as cloud, host, and Kubernetes node/cluster metadata. Run it **before** `k8s_attributes`.
        - `resource` — sets/edits/removes resource attributes by static value or OTTL. Run it **after** `k8s_attributes` to rename or drop what was added.
        - `transform` — OTTL attribute manipulation; the supported replacement for the removed `regex` value-extraction on labels/annotations (see [Known quirks](quirks.md)).
        
        ## Details
        
        - [Configuration](configuration.md) — full config table, the `extract` / `filter` / `pod_association` blocks, default-extracted fields, and the available metadata fields.
        - [Verification](verification.md) — end-to-end recipe against a real (kind) cluster: telemetrygen → `k8s_attributes` → `debug` showing pod attributes added.
        - [Advanced use-cases](advanced.md) — RBAC, agent/gateway passthrough split, namespace/node filtering, workload labels & annotations, deployment-name lookup, `wait_for_metadata`.
        - [Known quirks](quirks.md) — association failures, connection-based association vs batching, memory, the removed `regex` field, semconv feature gates, stability caveats.
        
      • verification.md 5.1 KB
        # `k8s_attributes`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for the general approach. Unlike the other component recipes, this one **needs a real Kubernetes cluster** — the processor enriches from the live Kubernetes API, so there is nothing to verify without one. A throwaway [`kind`](https://kind.sigs.k8s.io/) cluster is enough. `k8s_attributes` ships in the `contrib` and `k8s` distributions, so the stock contrib image works.
        
        The idea: run the collector **inside** the cluster (so its ServiceAccount can watch the API), deploy a throwaway workload to enrich against, then send a trace tagged with that workload's pod IP. The default `k8s.pod.ip` pod-association matches it to the cached pod, and the pod's metadata appears on the trace's resource.
        
        ## 1. Cluster
        
        ```bash
        kind create cluster --name k8sattr-verify --wait 90s
        ```
        
        ## 2. RBAC + collector + a workload to associate against
        
        Apply this (ServiceAccount, ClusterRole/Binding, config, collector Deployment):
        
        ```yaml
        apiVersion: v1
        kind: ServiceAccount
        metadata: {name: otel-collector, namespace: default}
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
        metadata: {name: otel-collector}
        rules:
          - apiGroups: [""]
            resources: ["pods", "namespaces", "nodes"]
            verbs: ["get", "watch", "list"]
        ---
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRoleBinding
        metadata: {name: otel-collector}
        subjects: [{kind: ServiceAccount, name: otel-collector, namespace: default}]
        roleRef: {kind: ClusterRole, name: otel-collector, apiGroup: rbac.authorization.k8s.io}
        ---
        apiVersion: v1
        kind: ConfigMap
        metadata: {name: otel-collector-config, namespace: default}
        data:
          config.yaml: |
            receivers:
              otlp:
                protocols:
                  grpc:
                    endpoint: 0.0.0.0:4317
            processors:
              k8s_attributes:
                auth_type: serviceAccount
                extract:
                  metadata: [k8s.namespace.name, k8s.pod.name, k8s.pod.uid, k8s.deployment.name, k8s.node.name]
                pod_association:
                  - sources: [{from: resource_attribute, name: k8s.pod.ip}]
            exporters:
              debug:
                verbosity: detailed
            service:
              pipelines:
                traces:
                  receivers: [otlp]
                  processors: [k8s_attributes]
                  exporters: [debug]
        ---
        apiVersion: apps/v1
        kind: Deployment
        metadata: {name: otel-collector, namespace: default}
        spec:
          replicas: 1
          selector: {matchLabels: {app: otel-collector}}
          template:
            metadata: {labels: {app: otel-collector}}
            spec:
              serviceAccountName: otel-collector
              containers:
                - name: otel-collector
                  image: otel/opentelemetry-collector-contrib:0.160.0
                  args: ["--config=/etc/otel/config.yaml"]
                  ports: [{containerPort: 4317}]
                  volumeMounts: [{name: config, mountPath: /etc/otel}]
              volumes: [{name: config, configMap: {name: otel-collector-config}}]
        ```
        
        ```bash
        kubectl apply -f collector.yaml
        kubectl create deployment demo --image=nginx --replicas=1   # the workload we'll enrich against
        kubectl rollout status deployment/otel-collector
        kubectl rollout status deployment/demo
        ```
        
        ## 3. Send a trace tagged with the demo pod's IP
        
        ```bash
        DEMO_IP=$(kubectl get pod -l app=demo -o jsonpath='{.items[0].status.podIP}')
        kubectl port-forward deployment/otel-collector 14317:4317 &   # background
        
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:14317 \
          --traces 1 --workers 1 \
          --otlp-attributes "k8s.pod.ip=\"$DEMO_IP\""
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure`, `--otlp-endpoint`, `--traces` (int, **per worker**; no `--duration`, or the count is ignored), `--workers`, and `--otlp-attributes` (**resource-level** — the right scope, since pod association and the added `k8s.*` attributes all live on the resource; `--telemetry-attributes` would put it at span level where association can't see it). The `k8s.pod.ip` value must be a real pod IP from the cluster so it matches a cached pod.
        
        ## What proves it worked
        
        ```bash
        kubectl logs deployment/otel-collector | grep -A7 'Resource attributes'
        ```
        
        A successful run sends a trace carrying only `k8s.pod.ip` and shows the demo pod's metadata added to
        its resource in this shape:
        
        ```
        Resource attributes:
             -> k8s.pod.ip: Str(10.244.0.5)
             -> service.name: Str(telemetrygen)
             -> k8s.pod.name: Str(demo-54d7464888-9zhvg)
             -> k8s.namespace.name: Str(default)
             -> k8s.pod.uid: Str(a4c56ba1-2f78-480c-99d0-3160d9c7c42e)
             -> k8s.deployment.name: Str(demo)
             -> k8s.node.name: Str(k8sattr-verify-control-plane)
        ```
        
        `k8s.deployment.name: demo` is the strongest signal — producing it means the processor resolved the pod purely from the `k8s.pod.ip` we set, then derived the deployment name from the pod's owner ReplicaSet name via the default heuristic. The recipe therefore needs no `replicasets` or `deployments` grants. If you enable `k8s.deployment.uid` or workload label/annotation extraction, grant access to each selected workload source as shown in the [RBAC table](advanced.md#rbac); for example, CronJob extraction requires both `jobs` and `cronjobs`.
        
        ## Teardown
        
        ```bash
        kind delete cluster --name k8sattr-verify
        ```
        
    • load_balancing
      • advanced.md 4 KB
        # `load_balancing`: advanced use-cases
        
        ## Scaling `tail_sampling` (the canonical topology)
        
        `tail_sampling` must see **every span of a trace** to make one keep/drop decision, so it can't be sharded by chance. Put a load-balancing tier in front:
        
        ```
        agents ──▶ [ LB Collectors: otlp recv → load_balancing(routing_key: traceID) ]
                                          │  consistent hash on trace ID
                                          ▼
                   [ sampling Collectors: otlp recv → tail_sampling → otlp/backend ]
        ```
        
        - The **LB tier** does no sampling — it only fans traces out by trace ID.
        - Each **sampling Collector** receives whole traces, so its policies are correct.
        - Scale the sampling tier up/down; the resolver picks up the change and the ring rebalances with minimal churn.
        
        The same shape applies to `span_metrics`/`service_graph` downstream, but key on `service` (or `resource`) so all of a service's spans — not each trace — converge on one instance, keeping its RED/series math whole.
        
        ## Dynamic discovery
        
        Pick the resolver that matches how backends come and go:
        
        - **`static`** — fixed lab/edge setups. The ring only changes on restart; a dead backend stays in the ring (data to it is lost until you fix the list).
        - **`dns`** against a headless Service — backends scale, DNS A-records change, re-resolved every `interval`. Subject to DNS caching delay.
        - **`k8s`** EndpointSlice watch — reacts to pod churn faster than DNS and skips DNS caching; needs RBAC (see [configuration.md](configuration.md)). Use `return_hostnames: true` only with a headless Service backing a StatefulSet (stable pod identities).
        - **`aws_cloud_map`** — ECS/EC2 fleets registered in Cloud Map; filter by `health_status` and remember the 100-host cap.
        
        ## Two-level resiliency model
        
        There are **two** independent queue/retry/timeout layers:
        
        1. **Per-backend** (`protocol.otlp.sending_queue` / `retry_on_failure` / `timeout`) — handles a single backend's transient failures (network blip, brief unavailability). Each sub-exporter has its own.
        2. **Load-balancer level** (top-level `sending_queue` / `retry_on_failure` / `timeout`) — when a sub-exporter exhausts its own retries and bounces the data back, the LB level can re-queue and re-dispatch, which on the **next** resolver update may pick a healthy backend. Valuable in elastic environments (K8s scale events) where a backend disappears entirely.
        
        Flow: sub-exporter exhausts → data returns to the LB → LB queue/retry (if enabled) re-dispatches → if all exhausted, data can be dropped. With a `static` resolver and **all** backends down, loss is unavoidable — there is nowhere to re-route.
        
        > Persistent (file-storage-backed) queues are not supported at the per-backend sub-exporter level — the storage config is shared. Put persistence on the LB-level `sending_queue` instead.
        
        ## Multiple deterministic front-ends
        
        The hash ring is a pure function of the routing key and the **resolved backend set**. As of v0.157.0, endpoint ordering no longer biases ring construction: two LB Collectors on the same release with the same backend set and `routing_key` compute the **identical** key→backend mapping even if their resolvers return the endpoints in a different order. So you can run several LB front-ends behind a plain round-robin L4 load balancer for HA, and a given trace still always reaches the same sampling backend regardless of which front-end handled it — no shared state or coordinator needed. Keep their configs and versions identical, or the rings can diverge.
        
        ## Named instances
        
        Standard `type/name` works, e.g. one tier keyed by trace ID for sampling and another keyed by service for metrics:
        
        ```yaml
        exporters:
          load_balancing/traces:
            routing_key: traceID
            resolver: { k8s: { service: sampling-collectors.observability } }
            protocol: { otlp: { tls: { insecure: true } } }
          load_balancing/metrics:
            routing_key: service
            resolver: { k8s: { service: spanmetrics-collectors.observability } }
            protocol: { otlp: { tls: { insecure: true } } }
        ```
        
      • configuration.md 7.8 KB
        # `load_balancing`: configuration
        
        All keys live under the exporter instance (e.g. `exporters: { load_balancing: { … } }`). Facts below are traced to the `v0.157.0` contrib source (`config.go`, `factory.go`, `loadbalancer.go`, the per-signal `*_exporter.go` files, and the resolver files); routing-key and k8s-resolver rows re-checked at `v0.162.0`.
        
        ## Top-level keys
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `routing_key` | string | empty → see [per-signal table](#routing_key-support-per-signal) | Which value the hash ring is keyed on. |
        | `routing_attributes` | list of string | — | Attribute names to build the key from; **only** valid with `routing_key: attributes`. |
        | `protocol.otlp` | otlp exporter config | otlp defaults | The per-backend transport (see [protocol.otlp](#protocolotlp)). |
        | `resolver` | object | — (required) | Exactly **one** of `static` / `dns` / `k8s` / `aws_cloud_map` (see [Resolvers](#resolvers)). |
        | `timeout` | duration | exporter-helper default | Timeout for the load-balancer's **own** queue, before per-backend dispatch. |
        | `retry_on_failure` | object | exporter-helper `retry_on_failure` | Retry for the load-balancer level (data bounced back from a sub-exporter). |
        | `sending_queue` | object | exporter-helper `sending_queue` | Queue at the load-balancer level, ahead of distribution. |
        
        `timeout`, `retry_on_failure`, and `sending_queue` are the standard exporter-helper knobs — see the `otlp` exporter page for their sub-keys. Here they apply to the **load balancer's own** buffer (the outer level); the **per-backend** queue/retry/timeout are configured separately inside `protocol.otlp` (the inner level). The two levels form the resiliency model described in [advanced.md](advanced.md).
        
        ### `routing_key` support per signal
        
        `routing_key` is validated **per signal exporter at startup**, and support differs by signal — this is stricter than the upstream README's prose. The table reflects **v0.162.0**:
        
        | `routing_key` | Traces | Logs | Metrics | Keyed on |
        |---------------|:------:|:----:|:-------:|----------|
        | `traceID` | ✅ default | ✅ | ❌ | The trace ID. |
        | `service` | ✅ | ✅ default | ✅ default | `service.name` resource attribute. |
        | `attributes` | ✅ | ✅ | ✅ | The `routing_attributes` values. |
        | `resource` | ❌ | ✅ | ✅ | Hash of all resource attributes. |
        | `metric` | ❌ | ❌ | ✅ | Metric name. |
        | `streamID` | ❌ | ❌ | ✅ | Datapoint stream identity (resource + scope + attributes). |
        | `randomness` | ✅ (v0.162.0+) | ❌ | ❌ | OTel randomness: the tracestate `ot=rv` value when present, else the trace ID's low 56 bits. Traces sharing an explicit `rv` (e.g. one session/workflow) go to the same backend. |
        
        - **Traces** accept `traceID` (default, also when empty), `service`, `attributes`, and (since v0.162.0) `randomness`. Any other value fails startup with `unsupported routing_key: <value>`. `randomness` assumes conformant random low bytes in trace IDs and a constant `rv` across a trace; one `rv` group can be arbitrarily large and lands on a single backend — see the upstream README before using it.
        - **Logs** accept `service` (default, also when empty), `traceID`, `resource`, `attributes`. For `traceID`, a log without a trace ID gets a random one (so it still lands somewhere). **Version note:** logs routing arrived in **v0.154.0** (contrib PR #46241); at **v0.152.0–v0.153.0** the log exporter ignored `routing_key` and always routed by trace ID without validating the value. See [quirks.md](quirks.md).
        - **Metrics** accept `service` (default, also when empty), `resource`, `metric`, `streamID`, `attributes`. Metrics are **Alpha** stability.
        
        ### `protocol.otlp`
        
        `protocol.otlp` is a full nested **`otlp` exporter** config — TLS, headers, compression, `sending_queue`, `retry_on_failure`, `timeout`, auth, etc. all apply, per backend.
        
        The one rule: **do not set `protocol.otlp.endpoint`.** The exporter overwrites it with each resolved backend address; the factory seeds it to a placeholder. Setting it yourself has no effect (or is misleading).
        
        ```yaml
        exporters:
          load_balancing:
            routing_key: traceID
            protocol:
              otlp:
                # endpoint: DO NOT SET — populated per backend
                tls:
                  insecure: true
                sending_queue:
                  enabled: true          # per-backend queue
                retry_on_failure:
                  enabled: true          # per-backend retry
            resolver:
              static:
                hostnames:
                  - backend-1:4317
                  - backend-2:4317
        ```
        
        ## Resolvers
        
        Configure **exactly one**. Zero resolvers fails startup with `no resolvers specified for the exporter`; more than one fails with `only one resolver should be specified`.
        
        ### `static`
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `hostnames` | list of string | — | Fixed `host:port` backends. No dynamic updates; the ring only changes on restart. |
        
        ### `dns`
        
        Periodically resolves a hostname (e.g. a headless Service) to its A/AAAA records.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `hostname` | string | — (required) | Hostname to resolve. |
        | `port` | string | — (empty) | Port appended to each resolved IP. When unset, the load balancer appends `:4317` to any address that has no port. |
        | `interval` | duration | `5s` | Re-resolution interval. |
        | `timeout` | duration | `1s` | Per-resolution timeout. |
        
        ### `k8s`
        
        Watches a Kubernetes Service's `EndpointSlice`s directly (reacts faster than DNS, no DNS caching).
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `service` | string | — (required) | `name` or `name.namespace`; namespace defaults to `default` when omitted/undeterminable. |
        | `ports` | list of int32 | — (empty) | Ports per backend; multiple ports produce multiple backends per pod. When unset, the load balancer appends `:4317` to each address that has no port. |
        | `timeout` | duration | `1m` | List/watch timeout. |
        | `return_hostnames` | bool | `false` | Return pod hostnames instead of IPs (requires a headless Service backing a StatefulSet). |
        
        **RBAC:** the Collector's ServiceAccount needs `get`, `list`, `watch` on `discovery.k8s.io/v1` `EndpointSlice`.
        
        Since v0.161.0 endpoints whose `EndpointSlice` `conditions.ready` is explicitly `false` are excluded from the ring; set `publishNotReadyAddresses: true` on the Service to keep not-ready pods routable.
        
        ### `aws_cloud_map`
        
        Discovers backends via AWS Cloud Map service discovery.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `namespace` | string | — (required) | Cloud Map namespace. |
        | `service_name` | string | — (required) | Cloud Map service. |
        | `health_status` | string | `HEALTHY` | Filter: `HEALTHY`, `UNHEALTHY`, `ALL`, `HEALTHY_OR_ELSE_ALL`. |
        | `interval` | duration | `30s` | Discovery interval. |
        | `timeout` | duration | `5s` | Discovery timeout. |
        | `port` | uint16 | — | Optional port override for resolved instances. |
        | `owner_account` | string | — | Optional AWS account ID for cross-account service discovery. |
        
        AWS region defaults to `us-east-1` via the SDK default config chain. **Limitation:** returns at most 100 hosts — pagination is not implemented.
        
        ## Validation summary
        
        | Condition | Error | When |
        |-----------|-------|------|
        | `routing_key: attributes` with empty `routing_attributes` | `routing_attributes must be specified when routing_key is "attributes"` | config validation |
        | `routing_attributes` set with `routing_key` ≠ `attributes` | `routing_attributes can only be used when routing_key is "attributes"; …` | config validation |
        | no resolver configured | `no resolvers specified for the exporter` | startup |
        | more than one resolver configured | `only one resolver should be specified` | startup |
        | unsupported `routing_key` for the signal | `unsupported routing_key: <value>` | startup (all three signals; traces print the value unquoted, logs/metrics quoted) |
        
      • quirks.md 4.5 KB
        # `load_balancing`: known quirks
        
        ## Do not set `protocol.otlp.endpoint`
        
        The exporter overwrites `endpoint` with each resolved backend address — the factory seeds it to a placeholder (`placeholder:4317`). Any value you put there is ignored. Configure everything else (`tls`, `headers`, `compression`, `sending_queue`, `retry_on_failure`, auth) under `protocol.otlp`; leave `endpoint` out.
        
        ## Routing-key support is signal-specific (and partly version-specific)
        
        The code is stricter than the upstream README's prose, and logs changed behavior across releases:
        
        - **Logs gained `routing_key` in v0.154.0** (contrib PR #46241), accepting `service` (default), `traceID`, `resource`, `attributes`. At **v0.152.0–v0.153.0** the log exporter **ignored** `routing_key` — it always routed by trace ID and did **not** validate the value, so a `routing_key` set on a logs pipeline was silently inert. If you run an older build, don't assume `service`/`resource` log routing works.
        - **Traces** accept only `traceID` (default), `service`, `attributes`, and `randomness` (v0.162.0+). `resource`, `metric`, `streamID` fail startup with `unsupported routing_key: <value>`.
        - **Metrics** accept `service` (default), `resource`, `metric`, `streamID`, `attributes`, and are **Alpha** stability (traces/logs are Beta) — config can still change between releases.
        
        Always confirm support against the exact contrib version you run; this surface has been changing.
        
        ## Exactly one resolver
        
        `resolver` must contain **one** of `static` / `dns` / `k8s` / `aws_cloud_map`. Zero → `no resolvers specified for the exporter`; two or more → `only one resolver should be specified`. Both are exporter-build errors rather than `Config.Validate()` errors, but `validate` builds the pipeline, so it does surface them (verified on v0.156.0).
        
        ## `routing_attributes` is coupled to `routing_key: attributes`
        
        `routing_attributes` is rejected at config validation unless `routing_key` is `attributes`, and `routing_key: attributes` is rejected unless `routing_attributes` is non-empty. They travel together.
        
        ## Static resolver + all backends down = unavoidable loss
        
        A `static` resolver never changes its list at runtime. If every listed backend is unreachable, there is nowhere to re-route and data is dropped once the queues drain. Dynamic resolvers (`dns`/`k8s`/`aws_cloud_map`) eventually reflect topology changes, but a mismatch window still exists between a backend dying and the resolver noticing.
        
        ## DNS caching delays rebalancing
        
        The `dns` resolver is only as fresh as the DNS responses it gets; intermediate caches/TTLs can delay a backend list update beyond `interval`. The `k8s` resolver (EndpointSlice watch) avoids this and reacts faster to pod churn.
        
        ## Hash distribution, not load awareness
        
        The ring balances **keys**, not bytes or backend CPU. It assumes routing-key values are reasonably uniform; a few very hot trace IDs or services can skew load even though key counts are even. The exporter does not look at actual backend load.
        
        ## v0.157.0 remaps the ring even when the backend set is unchanged
        
        v0.157.0 increased virtual points per endpoint from 100 to 200, expanded the ring coordinate space from 36,000 to 131,071, and removed endpoint-order bias. Distribution across large backend sets improves, but the same routing key can map to a different backend than it did on v0.156.0. During a rolling upgrade, mixed-version front-end Collectors can therefore disagree on key→backend mapping and split a trace or service across stateful backends. Upgrade the load-balancing front-end tier together (or otherwise drain old instances) before relying on v0.157.0 affinity.
        
        ## `return_hostnames` needs a headless StatefulSet
        
        `k8s` `return_hostnames: true` only yields stable, resolvable names when the Service is headless and backs a StatefulSet (stable pod hostnames). With a normal Deployment, use IPs (the default).
        
        ## Rebalancing churn
        
        On a backend-set change, roughly `R/N` routes (R = distinct routing-key values, N = backends) move to a different backend. More backends means proportionally less disruption per change — but every move briefly breaks affinity for those keys (e.g. a trace mid-flight can split across two sampling instances). Pairing with `groupbytrace` upstream can tighten per-trace atomicity.
        
        ## Single point of failure
        
        One LB Collector in front of the fleet is itself a SPOF. Run several identical LB front-ends (same `resolver`, same `routing_key`) behind an L4 balancer — the deterministic ring means a key still maps to the same backend regardless of which front-end handles it. See [advanced.md](advanced.md).
        
      • README.md 4.3 KB
        # `load_balancing` exporter
        
        | | |
        |-|-|
        | Kind | exporter |
        | Type | `load_balancing` |
        | Signals | traces (Beta), logs (Beta), metrics (Alpha) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/loadbalancingexporter> |
        | Rename | type renamed from `loadbalancing` to `load_balancing` in **v0.153.0**; the old name is preserved as a deprecated alias. |
        
        ## Description
        
        Distributes telemetry across a set of downstream Collector backends so that all records sharing a **routing key** (the trace ID, the `service.name`, etc.) always land on the **same** backend. It wraps a nested `otlp` exporter — one sub-exporter per backend — and picks the target with a **consistent-hash ring** keyed on the routing value. The backend set comes from a `resolver` (`static`, `dns`, `k8s`, or `aws_cloud_map`); when the resolver reports a change, the ring is rebuilt and only ~`R/N` keys (R = routes, N = backends) move, so most traffic keeps its backend.
        
        The point is **affinity, not raw spreading**: stateful downstream processors — `tail_sampling` (needs every span of a trace) and `span_metrics` (needs every span of a service) — only work correctly if a whole trace, or a whole service, reaches one instance. `load_balancing` is the standard way to scale those processors horizontally. Because the ring is deterministic, every front-end Collector running the **same release** with the same resolved backend set computes the **same** key→backend mapping, regardless of endpoint ordering, so you can run several identical front-ends without a coordinator. The routing key is signal-specific (and, for logs, version-specific) — see [Configuration](configuration.md). Before v0.153.0, the type was named `loadbalancing`.
        
        ## Main use-cases
        
        Use when:
        - You run `tail_sampling` (or `groupbytrace`) on more than one downstream Collector and need every span of a trace to reach the same instance.
        - You generate span/service metrics (`span_metrics`, `service_graph`) downstream and need a service's spans pinned to one instance to avoid split/duplicated series.
        - You want to fan telemetry across a horizontally-scaled, dynamically-discovered pool of backend Collectors (DNS/headless-service/CloudMap) with stable affinity.
        
        Avoid when:
        - You just need plain round-robin/failover to a couple of static endpoints with no affinity requirement — a single `otlp` exporter (optionally with multiple DNS-resolved targets) is simpler.
        - You need to route by attribute to **different** pipelines/backends by rule — that is `routing` (a connector), not hash-based distribution.
        - Your only goal is throughput batching/retry to one backend — that is the `otlp` exporter's `sending_queue`/`retry_on_failure`.
        
        ## Related components
        
        - `tail_sampling` — the primary reason this exporter exists; place `load_balancing` in the tier **in front of** the tail-sampling Collectors so each trace is whole when it arrives.
        - `routing` — rule-based routing to named pipelines (a connector); use it for "send tenant X here", not for hash-based affinity.
        - `otlp` exporter — the per-backend transport `load_balancing` wraps under `protocol.otlp`; its queue/retry/timeout semantics apply per backend.
        - `groupbytrace` / `span_metrics` — downstream processors that depend on the per-trace / per-service affinity this exporter provides.
        
        ## Details
        
        - [Configuration](configuration.md) — full config tables for `routing_key` (and its per-signal support), `routing_attributes`, `protocol.otlp`, the four resolver types with every sub-key and default, and the load-balancer-level queue/retry/timeout.
        - [Verification](verification.md) — telemetrygen recipe with a `static` resolver fanning four services across two backend Collectors, proving per-service affinity and stable distribution.
        - [Advanced use-cases](advanced.md) — the tail_sampling scaling topology, DNS/k8s/aws_cloud_map dynamic discovery, the two-level resiliency model, and running multiple deterministic front-ends.
        - [Known quirks](quirks.md) — `protocol.otlp.endpoint` must be unset, logs ignored `routing_key` before v0.154.0, traces reject `resource`/`metric`/`streamID`, resolver count validation, the v0.157.0 ring remap, static-resolver data-loss window, and stability caveats.
        
      • verification.md 4.5 KB
        # `load_balancing`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `load_balancing` ships in the `contrib` and `k8s` distributions. This recipe needs **three** collectors — one front-end running the exporter and **two** backends — so they can talk over a shared network. The idea: route by `service`, send four distinct `service.name`s, and confirm each service's spans always land on **exactly one** backend (affinity), with the services spread across both.
        
        > Use **v0.153.0 or newer** — the type is `loadbalancing` (one word) in v0.152.0 and earlier; it was renamed to `load_balancing` in v0.153.0. Verified on `otel/opentelemetry-collector-contrib:0.159.0` (2026-08-27).
        
        Front-end config (`front.yaml`) — the exporter under test, with a `static` resolver listing the two backends:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          load_balancing:
            routing_key: service          # each service.name hashes to one backend
            protocol:
              otlp:
                tls:
                  insecure: true          # do NOT set protocol.otlp.endpoint — populated per backend
            resolver:
              static:
                hostnames:
                  - lb-backend-a:4317
                  - lb-backend-b:4317
        service:
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [load_balancing]
        ```
        
        Backend config (`backend.yaml`, used by both backends) — a plain `debug` exporter so each backend prints what it received:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          debug:
            verbosity: detailed           # basic verbosity prints no per-span lines
        service:
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [debug]
        ```
        
        Start all three on one Docker network (unique names; only the front-end publishes a host port):
        
        ```bash
        docker network create lbnet
        IMG=otel/opentelemetry-collector-contrib:0.159.0
        docker run -d --name lb-backend-a --network lbnet -v "$PWD/backend.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        docker run -d --name lb-backend-b --network lbnet -v "$PWD/backend.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        docker run -d --name lb-front     --network lbnet -p 14317:4317 \
          -v "$PWD/front.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        ```
        
        Send four services (and `delta` twice, to prove the route is stable) — see the `otel-telemetrygen` skill:
        
        ```bash
        for svc in alpha beta gamma delta; do
          go run github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@v0.159.0 \
            traces --otlp-endpoint localhost:14317 --otlp-insecure \
            --traces 30 --workers 1 --service "$svc"
        done
        # repeat delta -> must hit the same backend as the first delta batch
        go run github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@v0.159.0 \
          traces --otlp-endpoint localhost:14317 --otlp-insecure \
          --traces 30 --workers 1 --service delta
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-endpoint` (string), `--otlp-insecure` (bool), `--traces` (int, **per worker**; ignored if `--duration` is set, so no `--duration` here and `--workers 1` makes the per-service count exact), and `--service` (string, default `telemetrygen`; sets the `service.name` resource attribute — the exact attribute `routing_key: service` hashes on). No flag invents a value.
        
        **What proves it worked:** count which `service.name`s appear in each backend's debug log:
        
        ```bash
        for c in lb-backend-a lb-backend-b; do
          echo "--- $c ---"
          docker logs "$c" 2>&1 | grep -oE 'service.name: Str\((alpha|beta|gamma|delta)\)' | sort | uniq -c
        done
        ```
        
        Each service must appear in **exactly one** backend — never both:
        
        ```
        --- lb-backend-a ---
          30 service.name: Str(alpha)
          30 service.name: Str(beta)
        --- lb-backend-b ---
          61 service.name: Str(delta)
          30 service.name: Str(gamma)
        ```
        
        `alpha`/`beta` reached only `lb-backend-a`, `gamma`/`delta` only `lb-backend-b` — clean per-service affinity, with the four services split across both backends. (The counts are per-export-batch occurrences, not span totals — they vary with batching; what matters is which backend each service appears in.) The repeated `delta` batch landed on `lb-backend-b` again, confirming the routing is a deterministic hash of `service.name`, not round-robin. Which service maps to which backend depends on the hash and the backend list, so the exact split varies; what is invariant is that **a given service never appears in more than one backend**.
        
        Tear down:
        
        ```bash
        docker rm -f lb-front lb-backend-a lb-backend-b
        docker network rm lbnet
        ```
        
    • log_dedup
      • advanced.md 2.8 KB
        # `log_dedup`: advanced use-cases
        
        ## Exclude volatile fields
        
        ```yaml
        processors:
          log_dedup:
            interval: 30s
            exclude_fields:
              - body.timestamp
              - attributes.request_id
              - attributes.trace_id
              - attributes.span_id
        ```
        
        Records that differ only in those fields are treated as duplicates.
        
        ## Deduplicate only errors
        
        ```yaml
        processors:
          log_dedup:
            interval: 60s
            conditions:
              - log.severity_number >= SEVERITY_NUMBER_ERROR
        ```
        
        INFO/WARN/DEBUG logs pass through immediately. Only ERROR+ are buffered.
        
        ## Whitelist match keys
        
        ```yaml
        processors:
          log_dedup:
            interval: 60s
            include_fields:
              - attributes.error_code
              - attributes.service.name
              - body.error_message
        ```
        
        Groups all errors with the same code from the same service, regardless of every other attribute.
        
        ## Per-source named instances
        
        ```yaml
        processors:
          log_dedup/access:
            interval: 10s
            conditions:
              - 'log.attributes["log.type"] == "access"'
          log_dedup/health:
            interval: 30s
            conditions:
              - 'log.attributes["log.type"] == "health_check"'
        
        service:
          pipelines:
            logs:
              processors: [memory_limiter, log_dedup/access, log_dedup/health]
        ```
        
        ## Multi-tenant buckets with `metadata_keys`
        
        In gateway deployments where one pipeline carries logs for many tenants, aggregating across tenants would merge their counts and lose the routing metadata. `metadata_keys` partitions aggregation per unique combination of client-metadata values (gRPC/HTTP headers), and each emitted log keeps a context carrying its original metadata so a downstream `headers_setter` can re-attach the header.
        
        ```yaml
        processors:
          log_dedup:
            interval: 30s
            metadata_keys: [x-scope-orgid]
            metadata_cardinality_limit: 1000
        ```
        
        Always set `metadata_cardinality_limit` when using `metadata_keys` in production: `0` (the default) means the number of tenant buckets is unbounded and memory can grow without limit — the processor logs a startup warning in that case. When the limit is reached, logs with a new metadata combination are rejected with a permanent error rather than silently merged.
        
        Requires the receiver to propagate client metadata (e.g. the OTLP receiver's `include_metadata: true`).
        
        ## OTTL context for `conditions`
        
        `conditions` runs in the [OTTL log context](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/contexts/ottllog). Prefix paths with their context name: `log.body`, `log.attributes["…"]`, `resource.attributes["…"]`, `log.severity_number`, `log.severity_text`, plus all OTTL converters. Un-prefixed paths (`attributes["…"]`, `severity_number`) still work but are **deprecated** since v0.153.0 — the processor rewrites and logs them at startup, and support may be removed. See the `otel-ottl` skill for full path and function inventories.
        
      • configuration.md 3.7 KB
        # `log_dedup`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          log_dedup:
        
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [memory_limiter, log_dedup]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `interval` | duration | `10s` | Aggregation window. Must be `> 0`. Counter resets after each emission. |
        | `log_count_attribute` | string | `log_count` | Attribute name for the duplicate count. Must be non-empty. |
        | `timezone` | string | `UTC` | IANA timezone for `first_observed_timestamp`/`last_observed_timestamp` (e.g., `America/New_York`). |
        | `conditions` | []string | `[]` | OTTL log-context expressions. Empty = deduplicate all logs. Non-empty = only deduplicate logs matching at least one condition; non-matching logs pass through unchanged. Prefix paths with their context name (`log.attributes["x"]`, `resource.attributes["y"]`, `log.severity_number`). |
        | `include_fields` | []string | `[]` | Whitelist of fields used to compute log identity. Mutually exclusive with `exclude_fields`. |
        | `exclude_fields` | []string | `[]` | Fields removed before hashing **and** stripped from emitted logs. Mutually exclusive with `include_fields`. |
        | `metadata_keys` | []string | `[]` | Client-metadata keys (e.g. gRPC/HTTP headers like `x-scope-orgid`) that partition aggregation into separate buckets. Logs with different values for these keys are counted independently, and each emitted log keeps a context carrying its original metadata so downstream extensions (e.g. `headers_setter`) can route it. Case-insensitive; duplicates rejected. Empty = one shared bucket. Added in v0.155.0. |
        | `metadata_cardinality_limit` | uint32 | `0` | Caps the number of distinct `metadata_keys` combinations tracked at once. `0` = unbounded (a startup warning is logged when `metadata_keys` is set but this stays `0`). Once the cap is hit, new combinations are rejected with a permanent error. Added in v0.155.0. |
        
        ## Validation rules
        
        - `include_fields` and `exclude_fields` cannot both be set.
        - Field paths must start with `body.` or `attributes.`.
        - The entire `body` cannot be included or excluded — only nested fields when `body` is a map.
        - Use `.` as the nesting delimiter. Escape literal dots in keys with `\` (e.g., `attributes.host\.name`).
        - Duplicate entries in `include_fields`, `exclude_fields`, or `metadata_keys` (case-insensitive for the latter) are rejected.
        - `timezone` must resolve in the IANA database.
        
        ## Fallback behavior
        
        If `include_fields` is set but none of the listed fields exist on a record, the processor falls back to comparing all fields (body, attributes, severity).
        
        ## What gets matched
        
        Two log records are considered identical when **all** of these match exactly: resource attributes, instrumentation scope (name, version, attributes), log body (content and type), log attributes, and severity (number and text). The processor stores a 64-bit hash per unique combination.
        
        `include_fields` / `exclude_fields` customize which parts of the record feed the hash. When `metadata_keys` is set, matching happens **within** each metadata bucket — two otherwise-identical logs arriving with different metadata values are never merged (see [advanced](advanced.md#multi-tenant-buckets-with-metadata_keys)).
        
        ## What gets emitted
        
        At the end of each `interval`, one log per unique hash is emitted with:
        - The original body, attributes, severity, resource.
        - `log_count` (or the configured name) — duplicate count, int.
        - `first_observed_timestamp` — RFC3339 string in the configured timezone.
        - `last_observed_timestamp` — RFC3339 string in the configured timezone.
        - `Timestamp` and `ObservedTimestamp` set to **emission time** (not original log time).
        
        Counter resets after emission.
        
      • quirks.md 3.5 KB
        # `log_dedup`: known quirks
        
        ## Memory model
        
        - One entry per unique hash for the duration of the interval.
        - High-cardinality fields (`request_id`, `trace_id`, timestamps) blow up memory — exclude them or use `conditions` to scope what's deduplicated.
        - Long intervals accumulate more unique entries; short intervals emit faster with smaller buffers.
        - `metadata_keys` multiplies memory: one full bucket of hashes per unique metadata combination. Leaving `metadata_cardinality_limit` at `0` (unbounded) is a memory-growth trap — cap it, and expect a startup warning if you don't.
        
        ## Telemetry
        
        The processor emits a histogram `otelcol_dedup_processor_aggregated_logs` with the count of logs aggregated per emission. Useful for tuning. The metric is currently **Development** stability — the name or shape can change between releases.
        
        ## Troubleshooting
        
        **No logs are deduplicated.**
        - `conditions` may exclude everything — comment them out to confirm.
        - Every record may have a unique identifier (`request_id`, body timestamp). Add those to `exclude_fields`.
        - Records may arrive slower than `interval` allows duplicates to form — increase `interval`.
        
        **Memory grows without bound.**
        - High-cardinality unique fields in the hash input — narrow with `exclude_fields` or `include_fields`.
        - Interval too long for the volume — reduce it.
        - Scope what's deduplicated with `conditions`.
        
        **Logs arrive in bursts matching the interval.**
        - Expected. The processor emits at interval boundaries. Reduce `interval` for lower latency, or move repetitive logs into a dedicated pipeline so other logs aren't held back.
        
        **Different resources/severities aren't being grouped.**
        - Resource attributes and severity are always part of the hash — there is no option to ignore them. Use a `resource` or `transform` processor upstream to normalize them first if that's the goal.
        
        ## Validation errors
        
        | Error | Fix |
        |-------|-----|
        | `cannot define both exclude_fields and include_fields` | Choose one. |
        | `an excludefield must start with body or attributes` | Prefix every path with `body.` or `attributes.`. |
        | `cannot exclude the entire body` | Only nested fields are exclud-able (e.g., `body.timestamp`). |
        | `duplicate metadata_key "…"` | Remove the repeated key (comparison is case-insensitive). |
        | `too many batcher metadata-value combinations` (runtime, permanent error) | `metadata_cardinality_limit` reached — raise it, or narrow `metadata_keys`. |
        
        ## Anti-patterns
        
        **Indiscriminate dedup.**
        
        ```yaml
        # BAD — critical events get hidden behind log_count
        processors:
          log_dedup:
            interval: 60s
        ```
        
        Scope with `conditions` so audit, security, and one-off error logs pass through individually:
        
        ```yaml
        processors:
          log_dedup:
            conditions:
              - 'log.attributes["log.type"] == "health_check"'
              - 'log.attributes["log.type"] == "heartbeat"'
        ```
        
        **`include_fields` that includes unique IDs.**
        
        ```yaml
        # BAD — request_id makes every record unique; nothing dedupes
        processors:
          log_dedup:
            include_fields:
              - attributes.error_code
              - attributes.request_id
        ```
        
        Include only identifying fields, never per-record identifiers.
        
        **Very long intervals.**
        
        ```yaml
        # BAD — 30 minutes of in-memory buffering, large bursty emissions
        processors:
          log_dedup:
            interval: 30m
        ```
        
        Prefer shorter intervals plus aggressive `exclude_fields` for cardinality control.
        
        **Forgetting timezone for ops teams.**
        
        ```yaml
        # Confusing if the team operates in PST
        processors:
          log_dedup:
            timezone: UTC  # default
        ```
        
        Set the timezone your operators read timestamps in.
        
      • README.md 2.2 KB
        # `log_dedup` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `log_dedup` |
        | Signals | logs |
        | Stability | Alpha (processor); Development (telemetry metric `otelcol_dedup_processor_aggregated_logs`) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/logdedupprocessor> |
        
        **Rename in v0.151.0:** the type was renamed `logdedup` → `log_dedup` to match snake_case. The legacy name is a deprecated alias. New configs should use `log_dedup`; existing `logdedup` configs keep working.
        
        ## Description
        
        Aggregates identical log records over a time window and emits a single log with a `log_count` attribute. Instead of forwarding every occurrence of a repeated message, the processor counts duplicates seen within an `interval` and emits one representative record per unique combination at the end of the window.
        
        ## Main use-cases
        
        Use it when:
        - The pipeline carries high-volume repetitive logs (health checks, polling, retry storms, connection errors).
        - You want to reduce backend storage cost while preserving frequency information.
        - You care about first/last observed timestamps of a recurring event, not every individual occurrence.
        
        Avoid it when:
        - Every entry must be preserved (audit, compliance, security logs).
        - Sub-second precision per occurrence is required.
        - The backend already deduplicates — you'll deduplicate twice.
        - Each log carries a unique identifier or timestamp that makes every record unique (use `exclude_fields` to fix this, or skip the processor).
        
        ## Related components
        
        - `groupbyattrs` — groups by attribute, does not deduplicate.
        - `transform` — can rewrite/strip fields via OTTL, does not aggregate.
        - `filter` — drops records, does not aggregate.
        
        ## Details
        
        - [Configuration](configuration.md) — config keys, defaults, validation, and how records are matched/emitted.
        - [Verification](verification.md) — telemetrygen recipe to confirm dedup works.
        - [Advanced use-cases](advanced.md) — scoping with conditions, field whitelists, per-source instances.
        - [Known quirks](quirks.md) — memory model, troubleshooting, anti-patterns.
        
      • verification.md 1.1 KB
        # `log_dedup`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `log_dedup` ships in the `contrib` and `k8s` distributions, so a stock contrib collector can run this.
        
        Config (`dedup-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          log_dedup:
            interval: 5s
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [log_dedup]
              exporters: [debug]
        ```
        
        Generate many identical log records (see the `otel-telemetrygen` skill):
        
        ```bash
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 50 --body "health check ok"
        ```
        
        Don't add `--duration` here: telemetrygen ignores `--logs` when `--duration` is set, so the count would be whatever its default rate happens to emit instead of 50.
        
        **What proves it worked:** the `debug` exporter prints **one** aggregated log per 5s interval carrying a `log_count` attribute of 50 (plus `first_observed_timestamp` / `last_observed_timestamp`), not 50 separate records.
        
    • memory_limiter
      • advanced.md 3.8 KB
        # `memory_limiter`: advanced use-cases
        
        ## Pair with `GOMEMLIMIT`
        
        Set the `GOMEMLIMIT` environment variable to roughly **80% of the hard limit**. It tunes Go's garbage collector to target that heap level, so the runtime reclaims memory *before* the limiter has to start refusing — the two work together rather than the limiter being the only brake:
        
        ```yaml
        # Container env
        env:
          - name: GOMEMLIMIT
            value: "3200MiB"   # ~80% of limit_mib
        
        processors:
          memory_limiter:
            check_interval: 1s
            limit_mib: 4000
            spike_limit_mib: 800
        ```
        
        `GOMEMLIMIT` is a soft target for the GC pacer; `memory_limiter` is the hard backstop that refuses data. Use both. See <https://pkg.go.dev/runtime#hdr-Environment_Variables>.
        
        ## Percentage mode for Kubernetes
        
        When the pod's memory limit varies across environments, use `limit_percentage` so the collector adapts without a config change. It reads the cgroup memory limit (v1/v2) or falls back to `/proc/meminfo`:
        
        ```yaml
        # Pod spec: resources.limits.memory: 2Gi
        processors:
          memory_limiter:
            check_interval: 1s
            limit_percentage: 75
            spike_limit_percentage: 15
        # Hard limit = 1.5 GiB (75% of 2 GiB), soft limit = 1.2 GiB (60%)
        ```
        
        `limit_mib` takes precedence if both are set. Percentage mode is most reliable on Linux with cgroups; on macOS/Windows it reads total *system* memory, which is rarely what you want (see [Known quirks](quirks.md)).
        
        ## Size `spike_limit_mib` for the traffic shape
        
        `spike_limit_mib` must cover the largest heap increase that can occur in a single `check_interval`. The buffer between soft and hard limit is the recovery headroom:
        
        | Traffic | Config |
        |---------|--------|
        | Steady | `check_interval: 1s`, `spike_limit_mib: 800` (20% of 4000) |
        | Bursty (2× spikes) | `check_interval: 500ms`, `spike_limit_mib: 1200` (30%) — check more often *and* widen the buffer |
        | Low-latency, predictable | `check_interval: 1s`, `spike_limit_mib: 400` (10%) — tight control |
        
        If the collector still OOMs, the spike outran the buffer between two checks: lower `check_interval` and/or raise `spike_limit_mib`.
        
        ## Multi-pipeline: one config, one checker
        
        A single `memory_limiter` definition referenced from every pipeline shares one background checker (the [singleton](quirks.md), keyed on identical config), so there is no redundant GC:
        
        ```yaml
        processors:
          memory_limiter:
            check_interval: 1s
            limit_mib: 4000
            spike_limit_mib: 800
        
        service:
          pipelines:
            traces:  { processors: [memory_limiter, batch] }
            metrics: { processors: [memory_limiter, batch] }
            logs:    { processors: [memory_limiter, batch] }
        ```
        
        ## Tuning the GC intervals
        
        Four keys shape forced GC so it does not pin the CPU when memory hovers near the limit. The `min_gc_interval_*` keys are the **floor** between forced GCs; the `max_gc_interval_*` keys **cap the exponential backoff** the processor applies when a GC reclaims `< 5%` of heap (memory pinned by live references — see [Known quirks](quirks.md)). Keep the soft-limited interval `>=` the hard-limited one on both pairs (the hard limit is more urgent, so it should GC at least as eagerly):
        
        ```yaml
        processors:
          memory_limiter:
            check_interval: 1s
            limit_mib: 4000
            min_gc_interval_when_soft_limited: 10s   # default — opportunistic floor
            min_gc_interval_when_hard_limited: 0s    # default — GC every over-hard-limit check
            max_gc_interval_when_soft_limited: 30s   # default — backoff cap
            max_gc_interval_when_hard_limited: 30s   # default — backoff cap
        ```
        
        Reach for these only if debug logs show `Forcing a GC` firing so often that CPU suffers (raise the `min`), or the backoff messages (`Forced GC did not reclaim enough memory…`) show GC still burning CPU during a downstream outage (lower the `max`, or set it to `0` only if you want to keep GC pinned at the `min`). The defaults are right for most deployments.
        
      • configuration.md 6.1 KB
        # `memory_limiter`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          memory_limiter:
            check_interval: 1s
            limit_mib: 4000
            spike_limit_mib: 800
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [memory_limiter, batch]
              exporters: [otlp]
        ```
        
        Hard limit 4000 MiB, soft limit 3200 MiB, checked every second. `memory_limiter` is first in the processor list (see [Pipeline placement](#pipeline-placement)).
        
        ## Configuration reference
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `check_interval` | duration | `0s` (**required**, must be `> 0`) | Time between memory measurements. `1s` is recommended; `500ms` for spiky traffic. |
        | `limit_mib` | uint32 | `0` | Hard limit, in MiB of **heap**. Required unless `limit_percentage` is set; **takes precedence** over `limit_percentage` if both are set. |
        | `spike_limit_mib` | uint32 | 20% of `limit_mib` | Expected max heap increase between checks. Must be `< limit_mib`. Soft limit = `limit_mib - spike_limit_mib`. |
        | `limit_percentage` | uint32 | `0` | Hard limit as a percentage of total memory (`> 0`, `<= 100`). Used **only when `limit_mib` is unset/0**. On Linux reads the cgroup limit, falling back to `/proc/meminfo`; on non-Linux it reads total *system* memory (rarely a container limit). See [Known quirks](quirks.md). |
        | `spike_limit_percentage` | uint32 | 20% of `limit_percentage` | Spike as a percentage of total memory. Must be `< limit_percentage` and `<= 100`. Only used with `limit_percentage`. |
        | `min_gc_interval_when_soft_limited` | duration | `10s` | Minimum (floor) time between forced GCs while above the soft limit. Must be `>= min_gc_interval_when_hard_limited`. |
        | `min_gc_interval_when_hard_limited` | duration | `0s` | Minimum (floor) time between forced GCs while above the hard limit (`0s` = GC on every over-limit check). Should be `<=` the soft-limited interval. |
        | `max_gc_interval_when_soft_limited` | duration | `30s` | Caps the exponential backoff between forced GCs on the soft-limit path. When a forced GC reclaims `< 5%` of heap the interval doubles (from the min floor, or 95% of `check_interval`, whichever is larger) up to this cap, then resets once a GC is effective or heap drops. `0` disables backoff (GC stays at the min). Must be `>= min_gc_interval_when_soft_limited` unless `0`; and `>= max_gc_interval_when_hard_limited` when both caps are set. |
        | `max_gc_interval_when_hard_limited` | duration | `30s` | Same backoff mechanism for the hard-limit path. Must be `>= min_gc_interval_when_hard_limited` unless `0`. |
        
        One of `limit_mib` or `limit_percentage` is mandatory; `check_interval` is always mandatory.
        
        ## Soft limit vs hard limit
        
        The processor derives two thresholds and acts on the current heap allocation:
        
        | State | Threshold | Action |
        |-------|-----------|--------|
        | Below soft limit | heap `< limit - spike` | Normal operation; data accepted. |
        | Above soft limit | heap `>= limit - spike` | Refuse data (`ErrDataRefused`); opportunistic GC throttled between `min_gc_interval_when_soft_limited` and `max_gc_interval_when_soft_limited`. Logs *"Memory usage is above soft limit. Refusing data."* |
        | Above hard limit | heap `>= limit` | Refuse data; aggressive GC throttled between `min_gc_interval_when_hard_limited` and `max_gc_interval_when_hard_limited`. Logs *"Memory usage is above hard limit. Forcing a GC."* |
        
        If a forced GC reclaims `< 5%` of heap (e.g. the memory is held by live references in exporter queues during a downstream outage), the processor treats it as ineffective and **exponentially backs off** the GC interval on that path — doubling up to the `max_gc_interval_*` cap — to avoid burning CPU on futile collections during recovery. The interval resets as soon as a GC frees memory or heap drops on its own.
        
        Refusal is a **non-permanent** error, so receivers retry with backoff and clients can rate-limit. Recovery is automatic: once heap falls below the soft limit it logs *"Memory usage back within limits. Resuming normal operation."* The processor also reports its health via `componentstatus` — a **recoverable-error** status while refusing, and `StatusOK` once back within limits — visible through status-aware extensions (e.g. `healthcheckv2`).
        
        ## Validation rules
        
        Startup fails (config error) when:
        
        | Rule | Error message |
        |------|---------------|
        | `check_interval` not `> 0` | `'check_interval' must be greater than zero` |
        | Neither limit set | `'limit_mib' or 'limit_percentage' must be greater than zero` |
        | `spike_limit_mib >= limit_mib` | `'spike_limit_mib' must be smaller than 'limit_mib'` |
        | `spike_limit_percentage >= limit_percentage` (percentage mode) | `'spike_limit_percentage' must be smaller than 'limit_percentage'` |
        | `limit_percentage > 100` or `spike_limit_percentage > 100` | `'limit_percentage' and 'spike_limit_percentage' must be greater than zero and less than or equal to hundred` |
        | `min_gc_interval_when_soft_limited < min_gc_interval_when_hard_limited` | `'min_gc_interval_when_soft_limited' should be larger than 'min_gc_interval_when_hard_limited'` |
        | `max_gc_interval_when_soft_limited` set but `< min_gc_interval_when_soft_limited` | `'max_gc_interval_when_soft_limited' must be greater than or equal to 'min_gc_interval_when_soft_limited' (or 0 to disable)` |
        | `max_gc_interval_when_hard_limited` set but `< min_gc_interval_when_hard_limited` | `'max_gc_interval_when_hard_limited' must be greater than or equal to 'min_gc_interval_when_hard_limited' (or 0 to disable)` |
        | both max caps set and `max_gc_interval_when_soft_limited < max_gc_interval_when_hard_limited` | `'max_gc_interval_when_soft_limited' should be larger than or equal to 'max_gc_interval_when_hard_limited' (when both are set)` |
        
        ## Pipeline placement
        
        `memory_limiter` must be the **first** processor in every pipeline so it refuses data before it enters allocating processors (`batch`, `transform`, `tail_sampling`) and so backpressure reaches receivers early:
        
        ```yaml
        # Good — refuse before anything buffers
        processors: [memory_limiter, tail_sampling, batch]
        ```
        
        ```yaml
        # Bad — memory limiting and sampling happen after batching
        processors: [batch, memory_limiter, tail_sampling]
        ```
        
      • quirks.md 6.1 KB
        # `memory_limiter`: known quirks
        
        ## It measures heap, not RSS
        
        The limit is compared against Go **heap allocation** (`runtime.MemStats.Alloc`), not process RSS. Total process memory typically runs **50–100 MiB higher** (Go runtime, stacks, CGO, OS page caches, mmap'd files). So `limit_mib: 4000` means a process that sits around ~4050 MiB. Set the container memory limit **10–15% above** `limit_mib`, or the kernel OOM-kills the process before the limiter ever refuses. Seeing RSS exceed `limit_mib` is expected, not a bug.
        
        ## `check_interval` and one of the limits are required
        
        There are no usable defaults for the two load-bearing keys. `check_interval` defaults to `0s`, which is a validation error, and you must set `limit_mib` **or** `limit_percentage` (`> 0`). A config with only one of these fails at startup:
        
        ```
        'check_interval' must be greater than zero
        'limit_mib' or 'limit_percentage' must be greater than zero
        ```
        
        ## `limit_mib` wins over `limit_percentage`
        
        If both are set, `limit_mib` is used and `limit_percentage` is ignored — silently. Set only the one you mean.
        
        ## `spike_limit` must be smaller than the limit
        
        `spike_limit_mib < limit_mib` (and `spike_limit_percentage < limit_percentage`) is validated. If the spike limit equals or exceeds the limit, the soft limit would be `<= 0` and startup fails with `'spike_limit_mib' must be smaller than 'limit_mib'`.
        
        ## GC-interval ordering is enforced
        
        `min_gc_interval_when_soft_limited` must be `>=` `min_gc_interval_when_hard_limited` (the hard limit is more urgent, so it cannot GC *less* often). Inverting them fails startup with `'min_gc_interval_when_soft_limited' should be larger than 'min_gc_interval_when_hard_limited'`. The same ordering holds for the `max_gc_interval_*` caps when both are set, and each `max` (unless `0`) must be `>=` its own `min` — see the [validation rules](configuration.md#validation-rules).
        
        ## GC backs off when it stops helping
        
        Since v0.156.0, forced GC no longer fires unconditionally on every over-limit check. When a GC reclaims `< 5%` of heap — typically because the memory is pinned by live references such as exporter sending queues during a downstream outage — the processor treats it as ineffective and **doubles** the interval before the next forced GC, up to `max_gc_interval_when_soft_limited` / `max_gc_interval_when_hard_limited` (both default `30s`). This prevents a CPU-burning GC loop that would starve recovery. The interval resets the moment a GC frees memory or heap drops. Set the relevant `max_gc_interval_*` to `0` to opt out and keep GC pinned at the `min` interval.
        
        ## It reports component health
        
        Since v0.156.0 the processor publishes health events via `componentstatus.ReportStatus`: a **recoverable-error** event while it is refusing data and `StatusOK` once heap is back within limits. Status-aware extensions (e.g. `healthcheckv2`) surface this, so a collector under memory pressure can report unhealthy without you scraping logs or refusal metrics.
        
        ## `limit_percentage` needs cgroups to be useful
        
        Percentage mode reads the container's memory limit from cgroups v2 (`/sys/fs/cgroup/memory.max`) or v1 (`/sys/fs/cgroup/memory/memory.limit_in_bytes`), and treats an "unlimited" cgroup value (`9223372036854771712`) as absent, falling back to `/proc/meminfo`. On macOS/Windows it reads **total system memory**, not a container limit, so the percentage is rarely meaningful there. Prefer `limit_mib` on non-Linux hosts. If total-memory detection fails entirely the collector refuses to start — switch to `limit_mib`.
        
        ## It is a safety net, not a sizing tool
        
        The limiter does not make a small collector handle more load — below capacity it simply **refuses data constantly** (refusal metrics, persistent *"Refusing data"* logs, receivers seeing backpressure). Memory-limiter metric names have changed across Collector releases — v0.156.0 renamed them to a component-specific prefix (`otelcol_processor_memory_limiter_accepted_*` / `otelcol_processor_memory_limiter_refused_*`) — so check the exact Collector version before writing alerts. The fix is to size the collector for the workload or scale out, then keep `memory_limiter` as the backstop. Treat sustained refusal as an under-provisioning signal, not a tuning problem.
        
        ## Refusal is non-permanent — receivers must retry
        
        When refusing, the processor returns a **non-permanent** error (gRPC `Unavailable`, *"data refused due to high memory usage"*). Well-behaved receivers (otlp, prometheus, …) retry with backoff and clients rate-limit. A receiver or client that does **not** retry will lose that data. This is by design: the limiter applies backpressure rather than buffering.
        
        ## Singleton checker across identical configs
        
        Pipelines referencing the same `memory_limiter` config share one background goroutine and timer (keyed on the config), so GC isn't triggered redundantly per pipeline. Two *differently named* instances with different limits run independent checkers — intentional, but watch that their limits don't sum past the container budget.
        
        ## The extension variant
        
        Since v0.142.0 the memory limiter is also available as an **extension** that acts as gRPC/HTTP middleware on a receiver (v0.147.0 added streaming support and fixed a multi-interceptor startup panic):
        
        ```yaml
        extensions:
          memory_limiter:
            check_interval: 1s
            limit_mib: 4000
        receivers:
          otlp:
            protocols:
              grpc:
                middlewares: [{id: memory_limiter}]
        service:
          extensions: [memory_limiter]
        ```
        
        The extension reached **Beta** stability in v0.158.0. It is still **not compiled into the stock core or contrib distributions** (`metadata.yaml` lists no distributions), so it requires a custom build. The processor form remains the bundled pipeline component; choose between them based on whether refusal should happen as receiver middleware or in a pipeline. Since core v0.162.0 the extension emits `otelcol_memorylimiter_refused_requests` (attribute `transport`) for refused network requests.
        
        ## Stability caveats
        
        Traces/metrics/logs are Beta; profiles are Alpha. The profiles path can still change between releases — confirm against the upstream README for the exact collector version before relying on it.
        
      • README.md 3.6 KB
        # `memory_limiter` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `memory_limiter` |
        | Signals | traces (Beta), metrics (Beta), logs (Beta), profiles (Alpha) |
        | Distributions | core, contrib, k8s |
        | Go module | `go.opentelemetry.io/collector/processor/memorylimiterprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor/memorylimiterprocessor> |
        
        ## Description
        
        A safety valve that keeps the Collector from being OOM-killed. A background goroutine reads Go heap allocation (`runtime.MemStats.Alloc`) every `check_interval` and compares it against two thresholds derived from the configured limit: a **soft limit** (`limit_mib - spike_limit_mib`) and a **hard limit** (`limit_mib`). Above the soft limit the processor sets a `mustRefuse` flag and every data path returns a **non-permanent** `ErrDataRefused` to the preceding component — backpressure that well-behaved receivers retry with backoff. Above the hard limit it additionally forces a `runtime.GC()` (with exponential backoff when a GC proves ineffective, capped by the `max_gc_interval_*` keys). It also reports its health via `componentstatus` — recoverable-error while refusing, OK once recovered. When heap drops back below the soft limit it resumes normally; no manual intervention.
        
        It measures **heap**, not process RSS — total process memory runs 50–100 MiB higher, so container limits should sit above `limit_mib`. The processor does not mutate data (`MutatesData: false`); it only accepts or refuses. It is a safety net, not a sizing or performance tool — a chronically undersized collector will simply refuse data constantly. Use it together with the `GOMEMLIMIT` environment variable so Go's GC paces toward the same target.
        
        ## Main use-cases
        
        Use it when:
        - **Always** — on every production Collector, as the **first** processor in every pipeline.
        - You run in a memory-capped environment (Kubernetes pod, Docker container) and must avoid OOM kills under traffic spikes.
        - You want early backpressure to receivers so clients rate-limit at the source before buffers fill downstream.
        
        Avoid it when:
        - You are reaching for it to fix throughput on an undersized collector — size the collector first, then add this as a safety net (see [Known quirks](quirks.md)).
        - A minimal verification/repro pipeline where you are isolating another component on purpose (the skill's verification configs omit it deliberately).
        
        ## Related components
        
        - `batch` / exporter `sending_queue.batch` — batching allocates buffers; `memory_limiter` must sit **before** it so refusal happens before data is buffered.
        - `tail_sampling`, `transform`, `log_dedup` — stateful/allocating processors; place them after `memory_limiter` so memory pressure refuses data before it reaches them.
        - The **`memory_limiter` extension** — same checker exposed as gRPC/HTTP middleware (Beta since v0.158.0, but not included in stock distributions); see [Known quirks](quirks.md).
        
        ## Details
        
        - [Configuration](configuration.md) — full config table (`check_interval`, `limit_mib` vs `limit_percentage`, spike limits, min/max GC intervals), validation rules, and the soft/hard-limit mechanism with GC backoff.
        - [Verification](verification.md) — telemetrygen recipe that forces refusal with a deliberately tiny limit and shows `ErrDataRefused` in the logs.
        - [Advanced use-cases](advanced.md) — `GOMEMLIMIT` pairing, percentage mode for Kubernetes, spike sizing for bursty traffic, multi-pipeline singleton behavior.
        - [Known quirks](quirks.md) — heap-vs-RSS, required fields, `limit_percentage` cgroup detection, GC-interval ordering, the singleton checker, the extension variant, stability caveats.
        
      • verification.md 2.4 KB
        # `memory_limiter`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `memory_limiter` ships in the `core`, `contrib`, and `k8s` distributions, so a stock contrib collector can run this.
        
        Because the processor reacts to **real heap pressure**, the trick to verifying it deterministically is to set the limit *below the collector's own idle heap* so it refuses from the first check — no need to actually exhaust memory.
        
        Config (`memlimit-verify.yaml`) — limit far below the idle heap so the soft limit is breached immediately:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          memory_limiter:
            check_interval: 1s
            limit_mib: 20
            spike_limit_mib: 5
        exporters:
          debug:
            verbosity: detailed
        service:
          telemetry:
            logs:
              level: debug
          pipelines:
            traces:
              receivers: [otlp]
              processors: [memory_limiter]
              exporters: [debug]
        ```
        
        Wait ~5s for the first memory check, then send a few traces (see the `otel-telemetrygen` skill):
        
        ```bash
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 --traces 10 --workers 1
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure`, `--otlp-endpoint`, `--traces` (int, traces **per worker**; ignored if `--duration` is set, so no `--duration` here), and `--workers` (default 1). The exact count does not matter here — every export is refused regardless.
        
        **What proves it worked** (verified run, contrib 0.152.0, idle heap ~17–20 MiB > the 15 MiB soft limit):
        
        1. The collector logs the limit being breached:
           ```
           warn  Memory usage is above hard limit. Forcing a GC.   {"cur_mem_mib": 20}
           info  Memory usage after GC.                            {"cur_mem_mib": 17}
           warn  Memory usage is above soft limit. Refusing data.  {"cur_mem_mib": 17}
           ```
        2. telemetrygen's export fails with the non-permanent gRPC error (it retries with backoff, which is exactly the intended backpressure):
           ```
           traces export: ... rpc error: code = Unavailable desc = data refused due to high memory usage
           ```
        3. **Zero** spans reach the `debug` exporter (`grep -c '^Span #'` in the collector logs returns `0`) — the data was refused before the pipeline, not dropped silently downstream.
        
        To watch normal operation instead, raise `limit_mib` well above the idle heap (e.g. `limit_mib: 4000`) and the same send passes through with spans printed by `debug`.
        
    • otlp
      • advanced.md 3.3 KB
        # `otlp` receiver: advanced use-cases
        
        ## Enabling only one protocol
        
        Once `protocols:` is written, only the sub-blocks present are active. To accept gRPC only (and refuse to even open the HTTP port):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        ```
        
        HTTP-only is the mirror image — list only `http:`. Listing neither fails startup (`must specify at least one protocol when using the OTLP receiver`).
        
        ## `include_metadata` for downstream metadata consumers
        
        Per-request gRPC/HTTP headers (and other client metadata) are **not propagated into the downstream pipeline** by default. Downstream features that read them need the receiver to carry them into the pipeline. Server authenticator extensions are different: they read the transport headers directly and do not require `include_metadata`.
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
                include_metadata: true     # headers survive into the pipeline
        ```
        
        `include_metadata: true` is the prerequisite for:
        - [`routing`](../routing/README.md) by request metadata.
        - `attributes` / `resource` actions that use `from_context: metadata.*`.
        
        Without it, those downstream consumers see no metadata and behave as if the headers were absent. The [`load_balancing`](../load_balancing/README.md) exporter's `routing_key: attributes` reads telemetry resource/scope/record attributes, not transport metadata; copy a header into a telemetry attribute first if it must become a load-balancing key.
        
        ## CORS for browser OTLP
        
        Browser RUM agents send OTLP/HTTP cross-origin and require CORS preflight handling:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              http:
                endpoint: 0.0.0.0:4318
                cors:
                  allowed_origins:
                    - https://app.example.com
                  allowed_headers:
                    - "*"
                  max_age: 7200
        ```
        
        ## mTLS (require client certificates)
        
        Set `client_ca_file` on the protocol's `tls` block to require and verify client certificates:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
                tls:
                  cert_file: /certs/server.crt
                  key_file: /certs/server.key
                  client_ca_file: /certs/client-ca.crt   # mutual TLS
        ```
        
        ## Large-payload tuning
        
        Reject-on-size limits differ per protocol. Raise them when senders batch aggressively:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
                max_recv_msg_size_mib: 16        # gRPC message cap (default 4 MiB)
              http:
                endpoint: 0.0.0.0:4318
                max_request_body_size: 67108864  # 64 MiB (default 20 MiB)
        ```
        
        ## Named instances
        
        The `type/name` pattern lets one Collector run several OTLP listeners — e.g. an internal-only plaintext listener and an external mTLS one:
        
        ```yaml
        receivers:
          otlp/internal:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
          otlp/external:
            protocols:
              grpc:
                endpoint: 0.0.0.0:5317
                tls:
                  cert_file: /certs/server.crt
                  key_file: /certs/server.key
                  client_ca_file: /certs/client-ca.crt
        
        service:
          pipelines:
            traces/internal:
              receivers: [otlp/internal]
            traces/external:
              receivers: [otlp/external]
        ```
        
        Give each named instance a distinct `endpoint` — two listeners cannot share a port.
        
      • configuration.md 6.9 KB
        # `otlp` receiver: configuration
        
        All keys live under the receiver instance (`receivers: { otlp: { … } }`). The only top-level key is `protocols:`, which holds two optional sub-blocks. Facts below trace to the core **v1.66.0 / v0.160.0** source (`receiver/otlpreceiver/config.go`, `config/configgrpc/configgrpc.go` `ServerConfig`, `config/confighttp/server.go` `ServerConfig`, and `config/configtls`).
        
        ## Top-level
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `protocols.grpc` | object | enabled by factory default | OTLP over gRPC (see [grpc](#protocolsgrpc)). |
        | `protocols.http` | object | enabled by factory default | OTLP over HTTP — protobuf and JSON (see [http](#protocolshttp)). |
        
        **Factory default** enables **both** protocols: gRPC on `localhost:4317`, HTTP on `localhost:4318`. Once you write a `protocols:` block, only the sub-blocks you include are active; the common idiom
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc: {}     # accept per-protocol defaults
              http: {}
        ```
        
        enables both at their defaults. Listing only `grpc:` disables HTTP, and vice versa.
        
        **Validation:** if neither `grpc` nor `http` is set, startup fails with `must specify at least one protocol when using the OTLP receiver`.
        
        > The default endpoints bind to `localhost`. In a container or pod you must set `endpoint: 0.0.0.0:4317` / `0.0.0.0:4318` — see [quirks.md](quirks.md).
        
        ## `protocols.grpc`
        
        `configgrpc.ServerConfig`. Common keys:
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `endpoint` | string | `localhost:4317` | Listen address. Set `0.0.0.0:4317` to accept non-loopback traffic. |
        | `transport` | string | `tcp` | Socket type. gRPC servers accept only `tcp` or `unix`. |
        | `tls` | object | — (plaintext) | Server TLS (see [TLS](#tls-server)). |
        | `max_recv_msg_size_mib` | int | 4 (gRPC default) | Max received message size, in MiB. Raise for large batches. |
        | `max_concurrent_streams` | uint32 | 0 (unlimited) | Max concurrent HTTP/2 streams per connection. |
        | `read_buffer_size` | int | `524288` (512 KiB) | gRPC read buffer. |
        | `write_buffer_size` | int | 0 (gRPC default) | gRPC write buffer. |
        | `keepalive` | object | — | Server keepalive (see [keepalive](#grpc-keepalive)). |
        | `auth` | object | — | `authenticator:` referencing an auth extension. |
        | `include_metadata` | bool | `false` | Propagate per-request gRPC metadata (headers) into the downstream pipeline. Required for metadata routing and `from_context: metadata.*`; server authenticator extensions read transport headers directly. |
        | `middlewares` | list | — | gRPC server middleware extensions. |
        
        ### grpc keepalive
        
        | Key | Type | Meaning |
        |-----|------|---------|
        | `server_parameters.max_connection_idle` | duration | Close a connection after this much idle time. |
        | `server_parameters.max_connection_age` | duration | Max total connection lifetime. |
        | `server_parameters.max_connection_age_grace` | duration | Grace period after `max_connection_age` before forced close. |
        | `server_parameters.time` | duration | Ping interval on idle connections. |
        | `server_parameters.timeout` | duration | Ping ack timeout. |
        | `enforcement_policy.min_time` | duration | Minimum client ping interval the server tolerates. |
        | `enforcement_policy.permit_without_stream` | bool | Allow client pings with no active stream. |
        
        ## `protocols.http`
        
        `confighttp.ServerConfig` (squashed) plus three OTLP URL-path overrides from `receiver/otlpreceiver/config.go`. Common keys:
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `endpoint` | string | `localhost:4318` | Listen address. Set `0.0.0.0:4318` to accept non-loopback traffic. |
        | `traces_url_path` | string | `/v1/traces` | Path for trace export. |
        | `metrics_url_path` | string | `/v1/metrics` | Path for metric export. |
        | `logs_url_path` | string | `/v1/logs` | Path for log export. |
        | `tls` | object | — (plaintext) | Server TLS (see [TLS](#tls-server)). |
        | `cors` | object | — | Browser CORS (see [cors](#cors)). |
        | `auth` | object | — | `authenticator:` referencing an auth extension. |
        | `max_request_body_size` | int | `20971520` (20 MiB) | Max accepted request body. Raise for large batches. |
        | `include_metadata` | bool | `false` | Propagate per-request HTTP headers into the pipeline (see grpc note above). |
        | `response_headers` | map | — | Static headers added to every response. |
        | `compression_algorithms` | list | none, gzip, zstd, zlib, snappy, deflate, lz4, x-snappy-framed | Accepted request `Content-Encoding`s. |
        | `read_timeout` | duration | 0 (none) | Max time to read the full request. |
        | `read_header_timeout` | duration | 0 (none) | Max time to read request headers. |
        | `write_timeout` | duration | 0 (none) | Max time to write the response. |
        | `keepalive.idle_timeout` | duration | `1m` | Max idle time on a keep-alive connection. |
        | `keepalive.enabled` | bool | `true` | Whether to allow HTTP keep-alives. |
        | `middlewares` | list | — | HTTP server middleware extensions. |
        
        Core v0.160.0 deprecates the flat HTTP `idle_timeout` and `keep_alives_enabled` keys. Non-no-op
        legacy values cannot be configured with the new `keepalive` block: `idle_timeout` conflicts when
        nonzero, and `keep_alives_enabled` conflicts when `false`. The no-op legacy values
        `idle_timeout: 0` and `keep_alives_enabled: true` may coexist with `keepalive`.
        
        There is **no profiles URL-path field** — the HTTP config exposes only traces/metrics/logs override keys. Profiles (Alpha) are still served over HTTP, but on a fixed, non-configurable path: `/v1development/profiles`.
        
        The HTTP endpoint accepts both **OTLP/protobuf** and **OTLP/JSON** on the same paths; the encoding is selected by `Content-Type`.
        
        ### cors
        
        | Key | Type | Meaning |
        |-----|------|---------|
        | `allowed_origins` | list of string | Origins permitted to make cross-origin OTLP requests (e.g. browser RUM). |
        | `allowed_headers` | list of string | Additional request headers allowed cross-origin. |
        | `exposed_headers` | list of string | Response headers browsers may expose to the caller (`Access-Control-Expose-Headers`). |
        | `max_age` | int | Seconds browsers may cache the CORS preflight. |
        
        ## TLS (server)
        
        Both `grpc.tls` and `http.tls` use `configtls.ServerConfig`. Common keys:
        
        | Key | Type | Meaning |
        |-----|------|---------|
        | `cert_file` | string | Server certificate (enables TLS). |
        | `key_file` | string | Server private key. |
        | `ca_file` | string | CA bundle used to verify client certificates. |
        | `client_ca_file` | string | CA bundle for mutual TLS — require and verify client certs. |
        
        See `config/configtls` for the full set (min/max version, cipher suites, reload interval).
        
        ## Validation summary
        
        | Condition | Error | When |
        |-----------|-------|------|
        | neither `grpc` nor `http` configured | `must specify at least one protocol when using the OTLP receiver` | config validation |
        | `max_recv_msg_size_mib` out of range | `invalid max_recv_msg_size_mib value, must be between 1 and …` | startup |
        
      • quirks.md 3 KB
        # `otlp` receiver: known quirks
        
        ## The default endpoint is `localhost`, not `0.0.0.0`
        
        This is the single most common "the receiver gets no data in Docker/k8s" problem. Each protocol defaults to `localhost:4317` / `localhost:4318` — a deliberate security-hardening default so a Collector started with no explicit endpoint doesn't expose an open OTLP port on every interface. The consequence: inside a container or pod, traffic arrives on a **non-loopback** interface, and a `localhost`-bound listener silently accepts nothing from outside — no error, just no data.
        
        To receive external traffic, set the endpoint explicitly:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
              http:
                endpoint: 0.0.0.0:4318
        ```
        
        If a containerized receiver "gets no data," check this first.
        
        ## Must specify at least one protocol
        
        A `protocols:` block with neither `grpc` nor `http` fails at config validation with `must specify at least one protocol when using the OTLP receiver`. Note the inverse trap: once you write `protocols:`, only the sub-blocks you include are active — listing only `grpc:` **disables** the HTTP listener that the bare factory default would have enabled. Use `grpc: {}` / `http: {}` (empty maps) to keep a protocol on at its defaults.
        
        ## gRPC is 4317, HTTP is 4318
        
        The two protocols listen on different ports: gRPC `4317`, HTTP `4318`. Sending OTLP/HTTP to `:4317` (or gRPC to `:4318`) fails. With `telemetrygen`, gRPC is the default and `--otlp-http` switches to the HTTP exporter — point each at the matching port.
        
        ## OTLP/JSON works on the HTTP port
        
        The HTTP endpoint accepts both OTLP/protobuf and **OTLP/JSON** on the same paths (`/v1/traces`, `/v1/metrics`, `/v1/logs`); the encoding is chosen by `Content-Type`. There is no separate JSON port or path. gRPC is protobuf only.
        
        ## `include_metadata` is off by default
        
        Per-request headers/metadata are not propagated into downstream consumers unless `include_metadata: true`. Metadata [`routing`](../routing/README.md) and `attributes` / `resource` actions using `from_context: metadata.*` silently see no metadata without it. Server authenticator extensions still read transport headers directly, and [`load_balancing`](../load_balancing/README.md) with `routing_key: attributes` hashes telemetry attributes rather than transport metadata.
        
        ## No configurable profiles URL path
        
        The HTTP config exposes `traces_url_path`, `metrics_url_path`, and `logs_url_path` overrides only. There is no `profiles_url_path` key — profiles (Alpha) are still served over HTTP, but on a fixed path the receiver hardcodes: `/v1development/profiles`. (Note: the upstream README mentions a `profiles_url_path` and a `/v1/profiles` default; neither exists in the config struct or the handler — the path is fixed.)
        
        ## Stability is per signal
        
        Traces, metrics, and logs are **Stable**; **profiles** are **Alpha**. Treat profile ingestion as still-evolving and subject to breaking change, even though the same receiver serves it.
        
      • README.md 3.8 KB
        # `otlp` receiver
        
        | | |
        |-|-|
        | Kind | receiver |
        | Type | `otlp` |
        | Signals | traces (Stable), metrics (Stable), logs (Stable), profiles (Alpha) |
        | Distributions | core, contrib, k8s, otlp |
        | Go module | `go.opentelemetry.io/collector/receiver/otlpreceiver` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector/tree/main/receiver/otlpreceiver> |
        
        ## Description
        
        Receives telemetry in the **OpenTelemetry Protocol (OTLP)** over gRPC and/or HTTP — the native wire format of the OpenTelemetry SDKs, the Collector's own `otlp` exporter, and most other OTLP-speaking agents. It is the default, canonical ingress for a Collector pipeline and supports traces, metrics, and logs at **Stable** stability (profiles are **Alpha**). The single config block is `protocols:`, holding optional `grpc:` and `http:` sub-blocks; you must enable at least one. The gRPC side listens on `4317`, the HTTP side on `4318` (accepting both OTLP/protobuf and OTLP/JSON on the same endpoint).
        
        The most important operational fact is the **default endpoint**: each protocol binds to `localhost` (`localhost:4317` / `localhost:4318`), not `0.0.0.0`. That is a deliberate security default — inside a container or Kubernetes pod, traffic arrives on a non-loopback interface, so a `localhost`-bound receiver silently accepts nothing from outside. To receive external traffic you must set `endpoint: 0.0.0.0:4317` (and `:4318`). This is the single most common "the receiver gets no data in Docker/k8s" gotcha — see [quirks.md](quirks.md).
        
        ## Main use-cases
        
        Use when:
        - You need to ingest OTLP from OpenTelemetry SDKs, the OTel Collector `otlp` exporter, or any OTLP-native agent — the default and recommended Collector ingress.
        - You want one receiver to handle traces, metrics, and logs over gRPC, HTTP/protobuf, and HTTP/JSON.
        - You front a tier that needs per-request metadata **inside the downstream pipeline** (metadata routing, or `from_context: metadata.*` actions) — set `include_metadata: true`. Server authenticator extensions read transport headers directly and do not require it.
        
        Avoid when:
        - The source speaks a non-OTLP protocol (Prometheus scrape, Jaeger, Zipkin, Kafka, syslog, filelog, …) — use the matching receiver instead.
        - You only need OTLP over HTTP and want to be explicit — still this receiver, just configure only the `http:` sub-block.
        
        ## Related components
        
        - [`otlp` exporter](../otlp_exporter/README.md) — the sending counterpart; a Collector-to-Collector hop is this receiver on one end and the `otlp_grpc`/`otlp` exporter on the other.
        - [`memory_limiter`](../memory_limiter/README.md) — belongs **first** in every pipeline fed by this receiver, to apply backpressure before buffers grow.
        - [`routing`](../routing/README.md) — to route by request metadata, this receiver must run with `include_metadata: true` so the headers survive into the pipeline.
        - [`load_balancing`](../load_balancing/README.md) — hashes telemetry resource/scope/record attributes (not transport metadata) when `routing_key: attributes` is selected.
        
        ## Details
        
        - [Configuration](configuration.md) — the `protocols.grpc` and `protocols.http` sub-blocks with every key, default, the URL-path overrides, and the "at least one protocol" validation rule.
        - [Verification](verification.md) — telemetrygen recipe sending OTLP/gRPC (and OTLP/HTTP) into a single collector with a `debug` exporter, binding `0.0.0.0` so containerized traffic is accepted.
        - [Advanced use-cases](advanced.md) — enabling a single protocol, `include_metadata` for downstream metadata consumers, CORS for browser OTLP, mTLS, large-payload tuning, and named instances.
        - [Known quirks](quirks.md) — the `localhost` vs `0.0.0.0` default (top item), the "at least one protocol" error, the 4317/4318 ports, OTLP/JSON on the HTTP port, `include_metadata` default, and per-signal stability.
        
      • verification.md 2.9 KB
        # `otlp` receiver: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        The `otlp` receiver ships in every distribution (`core`, `contrib`, `k8s`, `otlp`). This recipe runs **one** collector — the `otlp` receiver feeding a `debug` exporter — and sends spans into it with `telemetrygen`, first over gRPC (`:4317`) then over HTTP (`:4318`). Verified on `otel/opentelemetry-collector-contrib:0.154.0`.
        
        > **Why `0.0.0.0`, not `localhost`?** The default endpoint binds to `localhost`, which inside a container only accepts loopback traffic from *within* the container. `telemetrygen` runs on the host and reaches the container through a published port, i.e. a non-loopback interface — so the receiver must bind `0.0.0.0` or it silently accepts nothing. This is the receiver's most common gotcha; the recipe sets it explicitly.
        
        Config (`otlp.yaml`) — both protocols bound to `0.0.0.0`, `debug` at detailed verbosity so each span is printed:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
              http:
                endpoint: 0.0.0.0:4318
        exporters:
          debug:
            verbosity: detailed           # basic verbosity prints no per-span lines
        service:
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [debug]
        ```
        
        Start the collector, publishing both OTLP ports to the host:
        
        ```bash
        IMG=otel/opentelemetry-collector-contrib:0.154.0
        docker run -d --name otlp-recv -p 4317:4317 -p 4318:4318 \
          -v "$PWD/otlp.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        ```
        
        Send 5 spans over **gRPC** (port 4317) — see the `otel-telemetrygen` skill:
        
        ```bash
        telemetrygen traces --otlp-endpoint localhost:4317 --otlp-insecure \
          --traces 5 --workers 1
        ```
        
        Then send 5 more over **HTTP** (port 4318) with `--otlp-http`:
        
        ```bash
        telemetrygen traces --otlp-http --otlp-endpoint localhost:4318 --otlp-insecure \
          --traces 5 --workers 1
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-endpoint` (string), `--otlp-insecure` (bool, plaintext — matches the `tls`-less receiver), `--otlp-http` (bool, switches to the HTTP exporter and the `:4318` path), and `--traces` (int, **per worker**; ignored when `--duration` is set, so no `--duration` here and `--workers 1` makes the count exact). No flag invents a value.
        
        **What proves it worked:** count the spans the `debug` exporter logged:
        
        ```bash
        docker logs otlp-recv 2>&1 | grep -c 'Span #'
        ```
        
        `--traces N` counts **traces, not spans** — `telemetrygen` emits **2 spans per trace** (a parent and a child), so each `--traces 5 --workers 1` run produces **10 span records**, and the two runs (gRPC + HTTP) together yield **20**. Verified on `otel/opentelemetry-collector-contrib:0.154.0`:
        
        ```
        20
        ```
        
        If the count is 0, the most likely cause is a `localhost`-bound endpoint (data never reached the receiver) — confirm `endpoint: 0.0.0.0:…` in the config.
        
        Tear down:
        
        ```bash
        docker rm -f otlp-recv
        ```
        
    • otlp_exporter
      • advanced.md 3.6 KB
        # `otlp_grpc` exporter: advanced use-cases
        
        ## Tuning the queue for throughput vs. latency
        
        The queue defaults are `num_consumers: 10` and `queue_size: 1000`. If you opt into queue batching,
        its defaults are `batch.min_size: 8192` and `batch.flush_timeout: 200ms`. The levers:
        
        - **Throughput** — raise `num_consumers` (more parallel senders) and `queue_size` (deeper buffer to absorb bursts). Larger `batch.min_size` packs more per request, reducing per-RPC overhead.
        - **Latency** — when batching is enabled, lower `batch.flush_timeout` (flush sooner) and `batch.min_size` (smaller batches leave the queue faster). The trade-off is more, smaller RPCs.
        
        ```yaml
        exporters:
          otlp_grpc:
            endpoint: gateway:4317
            tls:
              insecure: true
            sending_queue:
              num_consumers: 20
              queue_size: 10000
              batch:
                flush_timeout: 5s        # tolerate latency for fewer, fuller batches
                min_size: 16384
        ```
        
        `batch.min_size` must be ≤ `queue_size` when their sizers match — see [quirks.md](quirks.md).
        
        ## Persistent queue via `storage`
        
        By default the queue is in-memory: data buffered at a crash or restart is lost. Point `sending_queue.storage` at a `file_storage` extension to persist the queue to disk so it survives restarts:
        
        ```yaml
        extensions:
          file_storage:
            directory: /var/lib/otelcol/queue
        
        exporters:
          otlp_grpc:
            endpoint: backend:4317
            sending_queue:
              storage: file_storage      # queue persists across restarts
        
        service:
          extensions: [file_storage]
        ```
        
        > The [`file_storage`](../file_storage/README.md) extension backs persistent queues. Persistence trades durability for disk I/O. **`wait_for_result` is not supported with a persistent `storage` queue** (see below).
        
        ## `wait_for_result` trade-off
        
        `sending_queue.wait_for_result: true` makes the caller block until the export result is known, so the pipeline learns about failures synchronously instead of fire-and-forget. It costs throughput (the producer waits) and is **incompatible with a persistent `storage` queue** — enabling both is a config error. Use it only when you need synchronous delivery confirmation and an in-memory queue.
        
        ## mTLS to a secure backend
        
        Drop `insecure` and supply client credentials to authenticate to a TLS backend:
        
        ```yaml
        exporters:
          otlp_grpc:
            endpoint: backend.example.com:4317
            tls:
              ca_file: /certs/backend-ca.crt
              cert_file: /certs/client.crt        # mTLS
              key_file: /certs/client.key
            headers:
              x-tenant-id: team-a                 # static per-request header
        ```
        
        ## Multiple named exporters
        
        The `type/name` pattern fans a pipeline out to several backends, each with its own queue/retry/TLS:
        
        ```yaml
        exporters:
          otlp_grpc/primary:
            endpoint: primary:4317
            tls: { insecure: true }
          otlp_grpc/backup:
            endpoint: backup:4317
            tls: { insecure: true }
        
        service:
          pipelines:
            traces:
              exporters: [otlp_grpc/primary, otlp_grpc/backup]
        ```
        
        ## Relationship to `load_balancing`
        
        [`load_balancing`](../load_balancing/README.md) wraps a per-backend `otlp` exporter under `protocol.otlp` — one sub-exporter per resolved backend. Everything on this page (TLS, `headers`, `compression`, `sending_queue`, `retry_on_failure`, `timeout`) applies **per backend** there. The one difference: under `load_balancing` you do **not** set `protocol.otlp.endpoint` — the load balancer populates it from the resolver. Reach for `load_balancing` when you need trace-ID / service affinity across a pool (to scale `tail_sampling`/`span_metrics`); reach for a plain `otlp_grpc` exporter when you just send to one backend (or fan out to a fixed few via named instances).
        
      • configuration.md 7 KB
        # `otlp_grpc` exporter: configuration
        
        All keys live under the exporter instance — `exporters: { otlp_grpc: { … } }` or, via the deprecated alias, `exporters: { otlp: { … } }`. Facts below trace to core **v1.66.0 / v0.160.0** source (`exporter/otlpexporter/config.go` + `factory.go`, `config/configgrpc/configgrpc.go` `ClientConfig`, `config/configtls`, `config/configretry/backoff.go`, and `exporter/exporterhelper/internal/queuebatch/config.go` + `queue_sender.go`); `sending_queue.batch.partition` rows re-checked at v1.68.0 / v0.162.0.
        
        ## Top-level (gRPC client) keys
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `endpoint` | string | — (**required**) | gRPC target (`host:port`, gRPC naming syntax). Accepts `http://`, `https://`, `dns://` prefixes (stripped internally for validation). |
        | `compression` | string | `gzip` | `gzip`, `snappy`, `zstd`, or none/empty to disable. Default set by the factory. |
        | `tls` | object | — | gRPC client TLS (see [tls](#tls)). |
        | `headers` | map[string]string | — | Static headers added to every gRPC request. |
        | `timeout` | duration | `5s` | Per-attempt send timeout. |
        | `keepalive` | object | — | gRPC client keepalive: `time`, `timeout`, `permit_without_stream`. |
        | `read_buffer_size` | int | 0 (gRPC default) | gRPC read buffer. The factory leaves this unset (the exporter reads almost nothing). |
        | `write_buffer_size` | int | `524288` (512 KiB) | gRPC write buffer. |
        | `wait_for_ready` | bool | `false` | Block RPCs until the connection is ready instead of failing fast. Core v0.157.0 fixed this option being parsed but not applied on gRPC client calls. |
        | `balancer_name` | string | `round_robin` | gRPC client-side load-balancing policy across resolved addresses. |
        | `authority` | string | — | Overrides the `:authority` pseudo-header. |
        | `user_agent` | string | — (build-info default) | Overrides the default gRPC user-agent header. Empty keeps the build-derived default. |
        | `auth` | object | — | `authenticator:` referencing an auth extension (e.g. bearer/OAuth2). |
        | `middlewares` | list | — | gRPC client middleware extensions. |
        
        **Validation:** an empty `endpoint` fails with `requires a non-empty "endpoint"`.
        
        ## `tls`
        
        `configtls.ClientConfig` (squashed). Common keys:
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `insecure` | bool | `false` | Use **plaintext** gRPC (no TLS). Set `true` for an internal/test backend with no TLS. |
        | `insecure_skip_verify` | bool | `false` | Use TLS but skip server-cert verification. |
        | `ca_file` | string | — | CA bundle to verify the server certificate. |
        | `cert_file` | string | — | Client certificate (for mTLS). |
        | `key_file` | string | — | Client private key (for mTLS). |
        | `server_name_override` | string | — | Override the SNI / verified server name. |
        
        `insecure` (plaintext) and `insecure_skip_verify` (TLS without verification) are different: the first disables TLS entirely, the second keeps TLS but trusts any certificate.
        
        ## `retry_on_failure`
        
        `configretry.BackOffConfig` — exponential backoff with jitter. After `max_elapsed_time` the data is **dropped**.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `enabled` | bool | `true` | Whether to retry failed sends. |
        | `initial_interval` | duration | `5s` | Wait before the first retry. |
        | `randomization_factor` | float | `0.5` | Jitter applied to each interval. |
        | `multiplier` | float | `1.5` | Growth factor per retry. |
        | `max_interval` | duration | `30s` | Cap on the per-retry interval. |
        | `max_elapsed_time` | duration | `5m` | Total retry budget; after this, the data is dropped. Set `0` to retry forever. |
        
        ## `sending_queue`
        
        The buffer between the pipeline and the gRPC sender. **Enabled by default.** Its optional `batch`
        sub-block uses defaults from `NewDefaultQueueConfig`, but batching itself is disabled by default
        unless the `pkg.exporterhelper.queueBatchEnabled` feature gate is enabled.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `enabled` | bool | `true` | Whether to enqueue before sending. |
        | `num_consumers` | int | `10` | Concurrent senders draining the queue. |
        | `queue_size` | int | `1000` | Max items buffered, in `sizer` units. Must be > 0. |
        | `sizer` | string | `requests` | Unit for `queue_size`: `requests`, `items`, or `bytes`. |
        | `block_on_overflow` | bool | `false` | `false` → overflow returns a retryable error immediately; `true` → enqueue blocks until space frees. |
        | `wait_for_result` | bool | `false` | Block the caller until the export result is known. **Not supported with a persistent `storage` queue.** |
        | `storage` | component ID | — | A `file_storage` extension ID; turns the queue **persistent** (survives restarts). |
        | `batch` | object | disabled | Built-in batching (see [batch](#sending_queuebatch)). Add `batch: {}` to enable it with defaults, or enable the migration gate globally. |
        
        > `storage` references a [`file_storage`](../file_storage/README.md) extension. Persistence survives Collector restarts at the cost of disk I/O; see [advanced.md](advanced.md).
        
        ### `sending_queue.batch`
        
        When enabled, flushes at `flush_timeout` or when `min_size` is reached, whichever comes first.
        Add `batch: {}` beneath `sending_queue` to opt in with these defaults. The alpha
        `pkg.exporterhelper.queueBatchEnabled` feature gate also enables batching by default for exporters
        using `NewDefaultQueueConfig`; this is phase 1 of the Collector batching migration. A separate
        `batch` processor remains supported for pipeline-level batching.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `flush_timeout` | duration | `200ms` | Max time a partial batch waits before flushing. Must be > 0. |
        | `sizer` | string | `items` | Unit for the batch sizes: only `items` or `bytes`. |
        | `min_size` | int | `8192` | Flush once the batch reaches this many items (the soft trigger). |
        | `max_size` | int | `0` (unlimited) | Hard cap; when > 0, a batch is split to never exceed it. |
        | `partition.metadata_keys` | list of string | — | One batcher per distinct combination of these client-metadata values. |
        | `partition.cache_size` | int | `10000` | Max active partition batchers (LRU); must be > 0. Exposed as `otelcol_exporter_queue_batch_partition_cache_size` / `_capacity`. Core v0.162.0+. |
        | `partition.idle_timeout` | duration | `90s` | How long an empty partition lives before removal; must be > 0. Configurable (and default raised to 90s) in core v0.162.0. |
        
        ## Validation summary
        
        | Condition | Error / rule | When |
        |-----------|--------------|------|
        | empty `endpoint` | `requires a non-empty "endpoint"` | config validation |
        | `sending_queue.batch.flush_timeout` ≤ 0 | rejected | config validation |
        | `batch.sizer` not `items`/`bytes` | rejected | config validation |
        | `batch.min_size` > `queue_size` (matching sizers) | rejected | config validation |
        | `batch.max_size` < `min_size` (when `max_size` > 0) | rejected | config validation |
        | `queue_size` ≤ 0 | `` `queue_size` must be positive `` | config validation |
        | `num_consumers` ≤ 0 | `` `num_consumers` must be positive `` | config validation |
        
      • quirks.md 3.6 KB
        # `otlp_grpc` exporter: known quirks
        
        ## The type is now `otlp_grpc` — and there's a separate `otlphttp`
        
        The canonical type was renamed from `otlp` to **`otlp_grpc`** in core **v1.50.0** (≈ contrib v0.144.0). The old name **`otlp` still works** as a deprecated alias (it emits a deprecation warning) and is what most existing configs use, so both `exporters: { otlp: … }` and `exporters: { otlp_grpc: … }` configure this same gRPC exporter. The rename disambiguates it from the **`otlphttp`** exporter, which is a *different* component speaking OTLP over **HTTP**. If your backend has no gRPC endpoint, you want `otlphttp`, not this one — they are not interchangeable.
        
        ## Exporter batching vs. the `batch` processor
        
        This exporter's `sending_queue` is enabled by default, but queue batching is **disabled by default**.
        Add `sending_queue.batch: {}` to activate the 200ms / 8192-item defaults, or enable the
        alpha `pkg.exporterhelper.queueBatchEnabled` migration gate. The separate `batch` processor is still
        Beta and supported; use it when batching must happen at pipeline level (for example, once before
        fan-out to several exporters), and place it after processors that may drop data. If both layers are
        enabled, tune them deliberately because double batching can add latency.
        
        ## gzip compression is on by default
        
        The factory sets `compression: gzip`. To send uncompressed gRPC (e.g. a CPU-constrained agent, or a backend that rejects gzip), set `compression: none` (or empty) explicitly. The other accepted values are `snappy` and `zstd`.
        
        ## `insecure: true` is needed for plaintext
        
        A backend with no TLS (a sibling Collector on a trusted network, a local test) requires `tls: { insecure: true }`. Without it the exporter attempts a TLS handshake and the connection fails. Note the distinction from `insecure_skip_verify: true`, which keeps TLS but skips certificate verification — different setting, different behavior.
        
        ## `wait_for_result` is incompatible with a persistent `storage` queue
        
        `sending_queue.wait_for_result: true` and `sending_queue.storage` (a `file_storage`-backed persistent queue) cannot both be set. Pick synchronous delivery confirmation **or** restart-durable persistence, not both.
        
        ## `wait_for_ready` only takes effect on v0.157.0 and newer
        
        Before core v0.157.0, `wait_for_ready: true` was accepted in the gRPC client config but was not added to the RPC call options, so sends still failed fast while the connection was not ready. v0.157.0 applies `grpc.WaitForReady` correctly. Do not rely on this setting for outage behavior on older Collectors.
        
        ## `min_size` must be ≤ `queue_size`
        
        When batching is enabled and the sizers match, `sending_queue.batch.min_size` (default 8192) must not exceed `sending_queue.queue_size` (default 1000 in `requests` units — but if you switch `queue_size` to `items`, the 8192 default `min_size` can exceed it and fail validation). If you set `queue_size` in items, raise it above `min_size`, or lower `min_size`. Also: `batch.flush_timeout` must be > 0, and `batch.max_size` (when > 0) must be ≥ `min_size`.
        
        ## Data is dropped after `max_elapsed_time`
        
        `retry_on_failure` retries with exponential backoff, but only until `max_elapsed_time` (default **5m**). After that budget is exhausted the data is **dropped** — retries are not infinite by default. Set `max_elapsed_time: 0` to retry forever (at the risk of unbounded queue growth), or pair with a persistent `storage` queue to survive longer outages.
        
        ## Stability is per signal
        
        Traces, metrics, and logs are **Stable**; **profiles** are **Alpha**. Treat profile export as experimental and subject to breaking change, even though the same exporter serves it.
        
      • README.md 4.2 KB
        # `otlp_grpc` exporter
        
        | | |
        |-|-|
        | Kind | exporter |
        | Type | `otlp_grpc` (deprecated alias `otlp`) |
        | Signals | traces (Stable), metrics (Stable), logs (Stable), profiles (Alpha) |
        | Distributions | core, contrib, k8s, otlp |
        | Go module | `go.opentelemetry.io/collector/exporter/otlpexporter` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector/tree/main/exporter/otlpexporter> |
        | Rename | type renamed from `otlp` to `otlp_grpc` in core **v1.50.0** (≈ contrib v0.144.0); the old name `otlp` is preserved as a deprecated alias and still works, emitting a deprecation warning. |
        
        ## Description
        
        Sends telemetry in **OTLP over gRPC** to a downstream OTLP endpoint — another Collector's `otlp` receiver, or any OTLP/gRPC backend. It is the standard egress for a pipeline and supports traces, metrics, and logs at **Stable** stability (profiles are **Alpha**). The canonical type is now **`otlp_grpc`**, renamed from `otlp` in core v1.50.0 to disambiguate it from the separate `otlphttp` exporter (OTLP over HTTP). The old name **`otlp` still works** as a deprecated alias — and is what the vast majority of existing configs use — so both `exporters: { otlp: … }` and `exporters: { otlp_grpc: … }` configure this component. Only `endpoint` is required; gRPC requests are **gzip-compressed by default**.
        
        The exporter's `sending_queue` and `retry_on_failure` are enabled by default. Queue **batching is
        opt-in**: add a `sending_queue.batch` block, or enable the alpha
        `pkg.exporterhelper.queueBatchEnabled` migration gate to make the factory's 200ms / 8192-item
        batch defaults active. The separate `batch` processor remains Beta and supported for pipeline-level
        batching (for example, once before fan-out to several exporters). See
        [configuration.md](configuration.md) and [quirks.md](quirks.md).
        
        ## Main use-cases
        
        Use when:
        - You send telemetry from one Collector to another (a gateway hop, an agent→gateway tier) over OTLP/gRPC.
        - You export to an OTLP/gRPC-native backend or vendor endpoint.
        - You want built-in queuing and retry, with optional exporter-side batching.
        
        Avoid when:
        - The backend only speaks OTLP over **HTTP** — use the `otlphttp` exporter instead.
        - You need trace-ID / service affinity across a pool of backends (to scale `tail_sampling`/`span_metrics`) — use [`load_balancing`](../load_balancing/README.md), which wraps this exporter per backend.
        - The destination isn't OTLP at all (Prometheus remote-write, Kafka, a vendor's proprietary protocol) — use that exporter.
        
        ## Related components
        
        - [`otlp` receiver](../otlp/README.md) — the receiving counterpart; a Collector-to-Collector hop is this exporter on one end and the `otlp` receiver on the other.
        - `otlphttp` exporter — the sibling that speaks OTLP over **HTTP** instead of gRPC; pick it when the backend has no gRPC endpoint.
        - [`load_balancing`](../load_balancing/README.md) — wraps a per-backend `otlp` exporter under `protocol.otlp`; its queue/retry/TLS semantics are this exporter's.
        - [`memory_limiter`](../memory_limiter/README.md) — the back-pressure guard at the **front** of the pipeline this exporter terminates.
        - The **`batch` processor** — still supported for pipeline-level batching; distinct from the exporter's opt-in `sending_queue.batch`.
        
        ## Details
        
        - [Configuration](configuration.md) — `endpoint`, `compression`, `tls`, `headers`, `timeout`, the `retry_on_failure` block, and the full `sending_queue` (+ `batch`) reference with every default and validation rule.
        - [Verification](verification.md) — a two-collector chain (A exports to B) proving spans sent to A arrive in B's `debug` log.
        - [Advanced use-cases](advanced.md) — tuning the queue for throughput vs. latency, the persistent queue via `storage`, `wait_for_result`, mTLS to a secure backend, named instances, and the relationship to `load_balancing`.
        - [Known quirks](quirks.md) — the `otlp`→`otlp_grpc` rename and `otlphttp` distinction (top item), exporter batching vs. the `batch` processor, default gzip, `insecure: true` for plaintext, the pre-v0.157.0 `wait_for_ready` bug, `wait_for_result` vs. persistent `storage`, `min_size` ≤ `queue_size`, drop-after-`max_elapsed_time`, and per-signal stability.
        
      • verification.md 3.4 KB
        # `otlp_grpc` exporter: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        The exporter ships in every distribution (`core`, `contrib`, `k8s`, `otlp`). The cleanest proof is a **two-collector chain**: collector **A** receives OTLP and exports it to collector **B**; collector **B** receives it and prints it with `debug`. If spans sent to A appear in B's log, the export hop worked. Verified on `otel/opentelemetry-collector-contrib:0.159.0` (2026-08-27).
        
        > The config uses the deprecated alias `otlp` for the exporter (still the most common form). The canonical `otlp_grpc` name is identical in behavior. This recipe is a **minimal repro** — it omits `memory_limiter` and other production scaffolding on purpose, to isolate the export hop.
        
        Collector **A** config (`a.yaml`) — receives, then exports to B over plaintext gRPC:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          otlp:                            # the exporter under test (alias of otlp_grpc)
            endpoint: otlp-b:4317
            tls:
              insecure: true               # plaintext gRPC to B
        service:
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [otlp]
        ```
        
        Collector **B** config (`b.yaml`) — receives and prints:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          debug:
            verbosity: detailed            # basic verbosity prints no per-span lines
        service:
          pipelines:
            traces:
              receivers: [otlp]
              exporters: [debug]
        ```
        
        Start both on one Docker network (distinct names; only A publishes a host port):
        
        ```bash
        docker network create otlpnet
        IMG=otel/opentelemetry-collector-contrib:0.159.0
        docker run -d --name otlp-b --network otlpnet \
          -v "$PWD/b.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        docker run -d --name otlp-a --network otlpnet -p 4317:4317 \
          -v "$PWD/a.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        ```
        
        Send 5 spans into **A** — see the `otel-telemetrygen` skill:
        
        ```bash
        go run github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@v0.159.0 \
          traces --otlp-endpoint localhost:4317 --otlp-insecure --traces 5 --workers 1
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-endpoint` (string), `--otlp-insecure` (bool, plaintext to A's TLS-less receiver), `--traces` (int, **per worker**; ignored when `--duration` is set, so no `--duration` and `--workers 1` makes the count exact). No flag invents a value.
        
        **What proves it worked:** the spans must appear in **B's** debug log, having traversed A's `otlp` exporter:
        
        ```bash
        docker logs otlp-b 2>&1 | grep -c 'Span #'
        ```
        
        `--traces N` counts **traces, not spans** — `telemetrygen` emits **2 spans per trace** (a parent and a child), so `--traces 5 --workers 1` produces **10 span records**, all of which should reach B:
        
        ```
        10
        ```
        
        Collector A also logs, at startup, the exporter's deprecation warning when the `otlp` alias is used:
        
        ```
        warn  builders/builders.go:40  "otlp" alias is deprecated; use "otlp_grpc" instead  {"otelcol.component.id": "otlp", "otelcol.component.kind": "exporter", ...}
        ```
        
        A count of 0 in B (but spans visible in A's own log, if you add a `debug` exporter to A) points at the export hop — usually a wrong `endpoint`, or missing `tls.insecure: true` against a plaintext B.
        
        Tear down:
        
        ```bash
        docker rm -f otlp-a otlp-b
        docker network rm otlpnet
        ```
        
    • probabilistic_sampler
      • advanced.md 3.8 KB
        # `probabilistic_sampler`: advanced use-cases
        
        ## Choosing a mode
        
        | Goal | Mode |
        |------|------|
        | A predictable, reproducible ratio across multiple tiers/collectors | `proportional` — uses W3C 56-bit randomness and ignores prior decisions, so a 1-in-N stays 1-in-N everywhere. |
        | Enforce a sampling **floor** without re-cutting already-sampled data | `equalizing` — factors in prior decisions and only lowers probability to the target, so a downstream sampler does not double-sample. |
        | Sample **logs by a record attribute**, or stay on the legacy seed-based hash | `hash_seed` — the only mode that supports `attribute_source: record`. |
        
        ```yaml
        processors:
          probabilistic_sampler:
            mode: proportional
            sampling_percentage: 10
        ```
        
        ## Consistent `hash_seed` across a fleet (hash_seed mode)
        
        In `hash_seed` mode the keep/drop decision depends on `hash_seed`. Every collector in the same sampling tier must use the **same** seed, or two collectors will make different decisions for the same TraceID and the effective rate becomes unpredictable. Pin the seed explicitly rather than relying on the `0` default when more than one collector samples the same stream:
        
        ```yaml
        processors:
          probabilistic_sampler:
            mode: hash_seed
            hash_seed: 22
            sampling_percentage: 25
        ```
        
        Use a **different** seed at each tier if you intentionally want independent decisions (e.g. cascading samplers); use the same seed when you want them to agree.
        
        ## Sampling logs by attribute
        
        Sample logs on a record attribute instead of the TraceID — useful when logs carry a high-cardinality key (such as a request or session ID) that should drive a consistent decision:
        
        ```yaml
        processors:
          probabilistic_sampler:
            mode: hash_seed
            sampling_percentage: 20
            attribute_source: record
            from_attribute: request.id
        ```
        
        `attribute_source: record` requires `from_attribute`; with `attribute_source: traceID` (the default) the log's TraceID is hashed.
        
        ## `sampling_priority` overrides (logs)
        
        Set `sampling_priority` to the name of an attribute that overrides the per-record decision. When a record carries that attribute, its value is read as a sampling percentage (`0`–`100`) that replaces `sampling_percentage` for that record — `0` never samples, `>= 100` always samples — handy for always keeping flagged records (set the attribute to `100`):
        
        ```yaml
        processors:
          probabilistic_sampler:
            sampling_percentage: 5
            sampling_priority: log.priority
        ```
        
        For traces, the equivalent override is the fixed `sampling.priority` attribute (not configurable), and it is binary rather than a percentage: `0` drops the span, any non-zero value keeps it.
        
        ## Combining head + tail sampling
        
        Probabilistic head sampling at the **edge** and `tail_sampling` at a **gateway** compose well: cut bulk volume cheaply and statelessly near the source, then keep the survivors that matter (errors, slow traces) at the gateway. Prefer `proportional`/`equalizing` at the edge so the W3C threshold is carried in `tracestate` and the gateway stays consistent rather than re-cutting blindly.
        
        ```yaml
        # Edge collector — cheap stateless head sampling
        processors:
          probabilistic_sampler:
            mode: proportional
            sampling_percentage: 30
        ```
        
        ```yaml
        # Gateway collector — content-aware tail sampling on the survivors
        processors:
          tail_sampling:
            decision_wait: 10s
            policies:
              - name: errors
                type: status_code
                status_code:
                  status_codes: [ERROR]
        ```
        
        ## The `fail_closed` trade-off
        
        `fail_closed: true` (default) **drops** items that have sampling-related errors — for example, missing or zero randomness (no usable TraceID / W3C randomness). This is the safe choice: it never lets un-decidable data skew the rate. Set `fail_closed: false` only when you would rather keep such items than lose them, accepting that the effective sampling rate becomes less precise.
        
      • configuration.md 3.1 KB
        # `probabilistic_sampler`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          probabilistic_sampler:
            sampling_percentage: 25
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [probabilistic_sampler]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `sampling_percentage` | float32 | (required) | Percentage sampled. `>= 100` keeps all; `0` keeps none. |
        | `mode` | string | `hash_seed` | Sampling algorithm: `proportional`, `equalizing`, or `hash_seed`. See [Modes](#modes). |
        | `hash_seed` | uint32 | `0` | Seed for the FNV hash. Only used in `hash_seed` mode. All collectors in a tier must share the same value (see [Known quirks](quirks.md)). |
        | `fail_closed` | bool | `true` | Reject items with sampling-related errors (e.g. missing or zero randomness). When `false`, such items pass through. |
        | `sampling_precision` | int | `4` | Number of hex digits used to encode the sampling threshold (range `1`–`14`). Higher precision is more exact but capped per mode (see [Known quirks](quirks.md)). |
        | `attribute_source` | string | `traceID` | **Logs only.** Randomness source: `traceID` or `record`. |
        | `from_attribute` | string | `""` | **Logs only.** Log attribute used for sampling when `attribute_source: record`. |
        | `sampling_priority` | string | `""` | **Logs only.** Name of a log-record attribute read as a per-record sampling percentage (0–100) that overrides `sampling_percentage`: `0` never samples, `>= 100` always samples. |
        
        ## Modes
        
        | Mode | Randomness | Behavior |
        |------|------------|----------|
        | `hash_seed` (default) | FNV hash of TraceID (or attribute), 14-bit | Hashes the source value with `hash_seed` and compares against a 14-bit threshold. The only mode that supports sampling logs by a record attribute. |
        | `proportional` | W3C 56-bit randomness | Ignores any prior sampling decision and samples a predictable 1-in-N fraction. Best for a consistent ratio across tiers. |
        | `equalizing` | W3C 56-bit randomness | Factors in prior sampling decisions and lowers probability to a floor across a pipeline, so a downstream sampler does not re-cut already-sampled data below the target. |
        
        The 56-bit modes use the W3C Trace Context randomness (`tracestate` `ot=rv:`/`th:`), which makes their decisions composable across multiple samplers; `hash_seed` uses its own seed-dependent FNV hash and does not compose. All three modes still record the effective threshold on the outgoing item.
        
        ## Traces vs logs
        
        | Aspect | Traces | Logs |
        |--------|--------|------|
        | Randomness source | Always the TraceID | TraceID (`attribute_source: traceID`) or a record attribute (`attribute_source: record` + `from_attribute`) |
        | Per-item priority override | Fixed `sampling.priority` attribute (binary: `0` drops, non-zero keeps) | Configurable via `sampling_priority` (percentage: `0`–`100`) |
        | Threshold encoding (downstream) | W3C `tracestate` (`ot=th:...`) | Log attributes (`sampling.threshold` / `sampling.randomness`) |
        | Stability | Beta | Alpha |
        
        `attribute_source`, `from_attribute`, and `sampling_priority` are logs-only and have no effect on traces.
        
      • quirks.md 3.2 KB
        # `probabilistic_sampler`: known quirks
        
        ## `fail_closed` drops zero/invalid-randomness items
        
        With `fail_closed: true` (the default), any item whose randomness source is missing or zero — for example a span with no usable TraceID, or a log without the configured `from_attribute` — is **dropped**, because the sampler cannot make a valid decision. If you see fewer items than the configured percentage would predict, check for items lacking randomness before assuming the rate is wrong. Setting `fail_closed: false` lets these items through at the cost of a less precise effective rate.
        
        `hash_seed` mode treats an item that **already carries** W3C randomness or threshold (`ot=rv:`/`th:` in `tracestate`) as an error too, since it does not compose with prior consistent sampling. With `fail_closed: true` those items are also dropped — use `proportional`/`equalizing` at any tier that sits downstream of an already-sampling collector or SDK.
        
        ## `hash_seed` must match across collectors
        
        In `hash_seed` mode the decision is `FNV(source, hash_seed) vs threshold`. Two collectors with **different** seeds make different keep/drop choices for the same TraceID, so chaining them (or load-balancing across them) yields an unpredictable combined rate. Every collector in one sampling tier must share the same `hash_seed`. The 56-bit `proportional`/`equalizing` modes avoid this by using W3C randomness instead of a seed.
        
        ## Precision is capped per mode
        
        `sampling_precision` (1–14 hex digits) controls how finely the threshold is encoded, but each mode has a practical ceiling: roughly 4 for `hash_seed` (it is only 14-bit), about 5 for `equalizing`, and higher for `proportional` though still bounded by float32 rounding of `sampling_percentage`. Asking for more precision than a mode supports does not improve accuracy.
        
        ## Threshold persists downstream via tracestate
        
        For traces, the sampling threshold is written into the W3C `tracestate` (`ot=th:...`); for logs it is written into `sampling.threshold` / `sampling.randomness` log attributes. This persistence is intentional — it lets a downstream sampler stay consistent — but it means the threshold travels with the data and a later component can read or act on it. Stripping `tracestate` between tiers breaks `proportional`/`equalizing` consistency.
        
        ## Child spans/logs are sampled with probability ≥ parent
        
        To preserve trace completeness, a child item is kept with probability at least that of its parent — the sampler will not drop a child of a kept parent purely by chance. This reduces (but does not fully eliminate) orphaned-child situations; combined with consistent per-TraceID hashing, whole traces tend to be kept or dropped together.
        
        ## Counts are statistical, not exact
        
        The kept fraction only **approaches** `sampling_percentage`% as the number of items grows. For small N the observed ratio is noisy and varies run to run; never assert an exact count from a single run.
        
        ## Stability caveats
        
        Traces are Beta and logs are Alpha — log-side config keys (`attribute_source`, `from_attribute`, `sampling_priority`) and behavior can still change between releases. Confirm against the upstream README for the exact collector version before relying on log sampling in production.
        
      • README.md 3.9 KB
        # `probabilistic_sampler` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `probabilistic_sampler` |
        | Signals | traces (Beta), logs (Alpha) |
        | Distributions | core, contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/probabilisticsamplerprocessor> |
        
        ## Description
        
        Head sampling: makes a deterministic keep/drop decision **per item** by hashing the TraceID (or, for logs, a configurable attribute) and comparing it against a threshold derived from `sampling_percentage`. The decision is stateless and content-blind — no buffering, no waiting — so the same TraceID is always kept or always dropped, and all collectors that share the same `hash_seed` agree on the outcome. Because the hash is uniform over trace IDs, sampling roughly `sampling_percentage`% of items.
        
        This is the opposite of **tail sampling** (`tail_sampling`), which buffers the **whole trace** and makes one decision after a wait window using trace-wide signals (errors, latency, span count). Head sampling is cheap and immediate but cannot react to span content; tail sampling is content-aware but stateful, requires buffering, adds latency, and needs all spans of a trace to land on the same instance. In **all three modes** the sampler records the effective threshold on the outgoing item — the trace's `tracestate` (`ot=th:...`) for traces, and log attributes (`sampling.threshold` / `sampling.randomness`) for logs. The W3C-randomness modes (`proportional` / `equalizing`) additionally *read* the incoming threshold and compose with it, so chained samplers stay consistent; the default `hash_seed` mode decides locally with its own FNV hash and does not compose (it ignores — and, with `fail_closed`, drops — items that already carry W3C randomness/threshold).
        
        ## Main use-cases
        
        Use it when:
        - You want cheap, stateless volume reduction at the edge with no buffering or co-location requirement.
        - You want a predictable, reproducible keep/drop decision per TraceID across a fleet of collectors (consistent `hash_seed`).
        - You want to sample logs by TraceID or by a record attribute.
        - You want to combine head sampling at the edge with `tail_sampling` at a gateway (see [Advanced use-cases](advanced.md)).
        
        Avoid it when:
        - You need a content-aware decision over a **whole trace** — keep all errors, keep slow traces — use `tail_sampling`. See its README for the head-vs-tail trade-off.
        - You need exact counts; sampling is statistical, so the kept fraction only approaches `sampling_percentage`% over many items.
        - Dropping would orphan child spans or break correlation — note the child-≥-parent completeness rule in [Known quirks](quirks.md) mitigates but does not eliminate this.
        
        ## Related components
        
        - `tail_sampling` — head's counterpart: trace-wide, content-aware keep/drop after buffering. Stateful and latency-adding, but can act on errors/latency.
        - `filter` — drops telemetry by OTTL condition rather than by a probability; use it for deterministic noise removal, not statistical sampling.
        - `transform` — mutates telemetry via OTTL instead of dropping it; run it before/after to set the attributes a record-based sampler reads.
        
        ## Details
        
        - [Configuration](configuration.md) — full config table, the three sampling modes explained, and the traces-vs-logs differences.
        - [Verification](verification.md) — telemetrygen recipe that sends many traces and shows roughly half reach `debug`.
        - [Advanced use-cases](advanced.md) — mode selection, consistent `hash_seed` across a fleet, logs sampling by attribute, `sampling_priority` overrides, head+tail combinations, the `fail_closed` trade-off.
        - [Known quirks](quirks.md) — `fail_closed` dropping zero/invalid randomness, hash-seed agreement, precision caps, tracestate persistence, child-≥-parent completeness, statistical counts, stability caveats.
        
      • verification.md 1.8 KB
        # `probabilistic_sampler`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `probabilistic_sampler` ships in the `core`, `contrib`, and `k8s` distributions, so a stock contrib collector can run this.
        
        Config (`probabilistic-verify.yaml`) — sample 50% of traces:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          probabilistic_sampler:
            sampling_percentage: 50
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [probabilistic_sampler]
              exporters: [debug]
        ```
        
        Send a good number of traces — each gets a fresh random TraceID, so roughly half clear the 50% threshold (see the `otel-telemetrygen` skill):
        
        ```bash
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 --traces 500 --workers 1
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure`, `--otlp-endpoint`, `--traces` (int, traces **per worker**; ignored if `--duration` is set, so no `--duration` here or the count would not be 500), and `--workers` (default 1). With `--workers 1` the total is exactly 500 — the denominator for the ratio below; raising `--workers` multiplies the total. telemetrygen assigns a random TraceID per trace, which is what makes the hash spread evenly.
        
        Use a large `N` (≥ 500) so the ratio is clearly readable — with only a handful of traces the kept fraction is noisy.
        
        **What proves it worked:** noticeably fewer traces reach the `debug` exporter than were sent — close to half. Because the decision is statistical, the exact fraction varies run to run, but at `N = 500` it lands near 50% (a verified run kept 257/500 ≈ 51%). The signal is "clearly fewer than sent, in the neighborhood of 50%", not an exact number.
        
    • prometheus
      • advanced.md 5.2 KB
        # `prometheus`: advanced use-cases
        
        ## Named instances
        
        Run more than one scrape configuration as independent receiver instances with the `prometheus/<name>` syntax:
        
        ```yaml
        receivers:
          prometheus/apps:
            config:
              scrape_configs:
                - job_name: 'apps'
                  static_configs:
                    - targets: ['app-a:9090', 'app-b:9090']
          prometheus/infra:
            config:
              scrape_configs:
                - job_name: 'node'
                  static_configs:
                    - targets: ['node-exporter:9100']
        
        service:
          pipelines:
            metrics:
              receivers: [prometheus/apps, prometheus/infra]
              exporters: [otlp]
        ```
        
        ## Target Allocator (OpenTelemetry Operator)
        
        Instead of static jobs, fetch a dynamically-assigned, sharded target list from the OpenTelemetry
        Operator's Target Allocator (or a compatible endpoint). The block embeds the full `confighttp`
        client configuration (so `tls`, `proxy_url`, etc. are available) plus the allocator-specific keys:
        
        ```yaml
        receivers:
          prometheus:
            target_allocator:
              endpoint: http://my-targetallocator-service
              interval: 30s
              collector_id: collector-1     # literal id — must NOT contain ${...}
        ```
        
        `endpoint` must be a valid request URI, `interval` must be a positive duration, and `collector_id`
        is required and must not contain `${` (see [quirks.md](quirks.md)). Optional `http_sd_config` and
        `http_scrape_config` tune the discovery and scrape HTTP clients respectively.
        
        The Target Allocator is also how you avoid duplicate scrapes when running multiple collector
        replicas — it shards targets across the collectors rather than every replica scraping everything.
        
        ## Native histograms
        
        The receiver auto-converts scraped Prometheus native histograms to OTel exponential histograms.
        This is now unconditional — the old `receiver.prometheusreceiver.EnableNativeHistograms` gate
        graduated to stable and has been removed, so there is no gate to toggle. You still need **two**
        things in the Prometheus scrape config:
        
        1. `scrape_native_histograms: true` (globally under `global`, or per-job)
        2. `PrometheusProto` included in `scrape_protocols` (required until Prometheus supports native histograms over text formats)
        
        ```yaml
        receivers:
          prometheus:
            config:
              global:
                scrape_native_histograms: true
              scrape_configs:
                - job_name: 'native-hist'
                  scrape_protocols: [PrometheusProto, OpenMetricsText1.0.0, OpenMetricsText0.0.1, PrometheusText0.0.4]
                  static_configs:
                    - targets: ['app:9090']
        ```
        
        If a metric has both classic buckets and native histogram buckets, only the native histogram buckets are used.
        
        ## Debug API server (`api_server`)
        
        Host a local Prometheus agent-mode API server to inspect targets, config, and service discovery for
        debugging. The `server_config` is the standard Collector `confighttp` server config:
        
        ```yaml
        receivers:
          prometheus:
            api_server:
              enabled: true
              server_config:
                endpoint: "localhost:9090"   # defaults to 127.0.0.1:9090 if omitted
        ```
        
        `server_config` is the standard confighttp server config (defaults: endpoint `127.0.0.1:9090`,
        `read_timeout: 10m`), and `max_connections` (default `512`) caps simultaneous HTTP connections.
        
        Endpoints mirror the Prometheus agent-mode API:
        
        - `/api/v1/targets`
        - `/api/v1/targets/metadata`
        - `/api/v1/status/config`
        - `/api/v1/scrape_pools`
        - `/metrics`
        
        ## Relabeling and metric filtering
        
        `relabel_configs` and `metric_relabel_configs` behave exactly as in Prometheus — do most of your
        target selection and metric dropping at the source rather than downstream. Note the `$$` escaping
        rule for literal `$` in regex/replacement values (see [quirks.md](quirks.md)).
        
        ```yaml
        receivers:
          prometheus:
            config:
              scrape_configs:
                - job_name: 'k8s-pods'
                  kubernetes_sd_configs:
                    - role: pod
                  relabel_configs:
                    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                      regex: "true"
                      action: keep
                  metric_relabel_configs:
                    - source_labels: [__name__]
                      regex: "(request_duration_seconds.*|response_duration_seconds.*)"
                      action: keep
        ```
        
        ## Splitting scrape configs into files (`scrape_config_files`)
        
        Keep large or shared scrape definitions out of the main config with Prometheus's
        `scrape_config_files` (glob paths to files holding `scrape_configs`). This counts as a valid scrape
        source for validation (you do not also need inline `scrape_configs`):
        
        ```yaml
        receivers:
          prometheus:
            config:
              scrape_config_files:
                - /etc/otel/scrape_configs/*.yaml
        ```
        
        ## Feature gates
        
        - `receiver.prometheusreceiver.EnableCreatedTimestampZeroIngestion` (from v0.113.0) — **alpha, off by default**. Injects created-timestamps as 0-valued samples. Off by default due to higher CPU cost at high metric volume.
        - `receiver.prometheusreceiver.IgnoreScopeInfoMetric` (from v0.148.0) — **stable since v0.162.0** (beta and on by default v0.156.0–v0.161.0). `otel_scope_info` is no longer used for scope-attribute extraction and is converted like any other metric; scope attributes come from `otel_scope_<name>` labels. The gate can no longer be disabled and is slated for removal in v0.164.0, after which referencing it in `--feature-gates` fails startup.
        
      • configuration.md 6.3 KB
        # `prometheus`: configuration
        
        ## Typical config
        
        ```yaml
        receivers:
          prometheus:
            config:
              scrape_configs:
                - job_name: 'my-service'
                  scrape_interval: 5s
                  static_configs:
                    - targets: ['my-service:9090']
        
        service:
          pipelines:
            metrics:
              receivers: [prometheus]
              processors: [memory_limiter]
              exporters: [otlp]
        ```
        
        ## Configuration reference
        
        These are the receiver's **own** top-level keys. The `config:` block holds the standard Prometheus
        configuration — `global`, `scrape_configs`, `scrape_config_files`, service discovery, relabeling —
        documented in the upstream [Prometheus configuration reference][promcfg]; it is not reproduced here.
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `config` | embedded Prometheus config | — | The Prometheus configuration, same YAML structure as `prometheus.yml`. Holds `global`, `scrape_configs`, `scrape_config_files`, and related sections. Any field you omit takes Prometheus's own default for that setting. |
        | `trim_metric_suffixes` | bool | `false` | [**Experimental**] Trims unit and counter-type suffixes from metric names, e.g. `singing_duration_seconds_total` → `singing_duration`. Useful to restore OpenTelemetry-style names. |
        | `target_allocator` | object | — | Optional. Client config to fetch dynamically-assigned scrape targets from the OpenTelemetry Operator's Target Allocator. See table below and [advanced.md](advanced.md). |
        | `api_server` | object | — | Optional. Hosts a local Prometheus agent-mode API server for debugging. See table below and [advanced.md](advanced.md). |
        | `scrape_on_shutdown` | bool | `false` | Perform one final scrape before the receiver closes. This last scrape ignores the configured scrape interval. (v0.155.0) |
        | `discovery_reload_on_startup` | bool | `false` | Discover targets immediately on start-up instead of waiting for the configured interval before initializing the scrape pools. (v0.155.0) |
        | `initial_scrape_offset` | duration | `0s` | Fixed delay added before the initial scrape of targets, on top of the receiver's internal load-balancing offset. Avoids readiness races and backend rate-limits right after start-up. (v0.155.0) |
        
        [promcfg]: https://prometheus.io/docs/prometheus/latest/configuration/configuration/
        
        ### `target_allocator` keys
        
        The block squashes the Collector `confighttp` [client configuration][confighttp] (so `endpoint`,
        `tls`, `proxy_url`, etc. are available) and adds:
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `endpoint` | string | — | **Required.** Target Allocator URL. Must be a valid request URI. |
        | `interval` | duration | — | How often to refresh the target list. Must be a **positive** duration. |
        | `collector_id` | string | — | **Required.** Identifies this collector instance to the allocator. Must be non-empty. A **literal** `${` is rejected, but `${ENV}` references are env-expanded by the Collector before validation (see [quirks.md](quirks.md)). |
        | `http_sd_config` | Prometheus HTTP SD config | — | HTTP service-discovery config used to fetch targets. |
        | `http_scrape_config` | Prometheus HTTP client config | — | HTTP client config applied when scraping the discovered targets. |
        | `tls`, `proxy_url`, … | (from `confighttp.ClientConfig`) | — | Standard confighttp client options. |
        
        [confighttp]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#client-configuration
        
        ### `api_server` keys
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `enabled` | bool | `false` | Turn the debug API server on. |
        | `max_connections` | int | `512` | Maximum number of simultaneous HTTP connections the server accepts. |
        | `server_config` | `confighttp.ServerConfig` | endpoint `127.0.0.1:9090`, `read_timeout: 10m` | Standard confighttp [server config][confighttp-server] (`endpoint`, `tls`, CORS, timeouts). The endpoint is populated by default, so enabling the server does not require setting it. |
        
        [confighttp-server]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md#server-configuration
        
        ## Validation rules
        
        | Rule | Error message |
        |------|---------------|
        | At least one of `config.scrape_configs`, `config.scrape_config_files`, or `target_allocator` must be set. | `no Prometheus scrape_configs or target_allocator set` |
        | Prometheus server-only features are rejected: `remote_write`, `remote_read`, `rule_files`, `alert_config.relabel_configs`, `alert_config.alertmanagers`. | `unsupported features: …` (the offending features, one per line) |
        | `target_allocator.endpoint` must parse as a valid request URI. | `TargetAllocator endpoint is not valid: …` |
        | `target_allocator.collector_id` must be non-empty and must not contain `${`. | `CollectorID is not a valid ID` |
        | `target_allocator.interval` must be a positive duration. | `interval must be a positive duration, got …` |
        
        ## Scrape protocols and exposition formats
        
        The receiver supports the protocols/exposition formats accepted by the Prometheus scrape config:
        
        - `PrometheusProto`
        - `OpenMetricsText1.0.0`
        - `OpenMetricsText0.0.1`
        - `PrometheusText1.0.0`
        - `PrometheusText0.0.4`
        
        Since Prometheus 3.0, scrapes need a proper `Content-Type` header. For back-compat, the receiver
        defaults each scrape config's `fallback_scrape_protocol` to `PrometheusText0.0.4` when it is unset,
        so targets that do not return a content type still parse.
        
        ## How scraped series map to OTel
        
        - `target_info` is dropped; its attributes populate the OTel **Resource**.
        - `otel_scope_name` / `otel_scope_version` labels are dropped and populate the Instrumentation **Scope** name/version.
        - Other `otel_scope_<name>` labels populate Scope **attributes**. `otel_scope_info` is not used for scope attributes (since v0.156.0 by default, unconditionally since v0.162.0) and is converted like any other metric.
        - The `job` target label (its value comes from `job_name`) maps to `service.name`, and `instance` maps to `service.instance.id` (plus `server.address`/`server.port` when discernible). Kubernetes SD meta-labels map to `k8s.*` resource attributes — see the upstream [resource attribute mapping][resmap].
        - Native histograms are converted to OTel exponential histograms (see [advanced.md](advanced.md)).
        
        [resmap]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/prometheusreceiver/resource_attribute_mapping.md
        
      • quirks.md 4.6 KB
        # `prometheus`: known quirks
        
        ## No auto-sharding — duplicate scrapes across replicas
        
        The receiver is **stateful** and the Collector **cannot auto-scale scraping** across replicas. If
        you run multiple collector replicas with the **same** config, each replica scrapes every target, so
        you get **duplicate metrics**. Options:
        
        - Use the **Target Allocator** (`target_allocator`) to shard targets across replicas — see [advanced.md](advanced.md).
        - Or **manually shard**: give each replica a different scrape configuration.
        
        This is the upstream README's headline warning; treat the receiver as work-in-progress for
        horizontal scaling.
        
        ## `$` must be escaped as `$$`
        
        The Collector interprets `$` in config as an environment-variable reference. To use a **literal**
        `$` inside the Prometheus config — common in relabel regexes and `replacement` values — escape it as
        `$$`. An unescaped `$foo` will be substituted (or blanked) at config load, silently breaking the
        relabel rule.
        
        ## `collector_id: collector-${HOSTNAME}` — expanded, not rejected
        
        You may see examples using a templated `collector_id` like `collector-${HOSTNAME}` for the Target
        Allocator. Contrary to what the "must not contain `${`" rule suggests, the Collector's own
        environment-variable substitution expands `${HOSTNAME}` **before** the receiver validates the
        config, so `collector-${HOSTNAME}` becomes `collector-<hostname>` and validates fine — this is
        actually a working way to get per-pod uniqueness when `HOSTNAME` is set (as it is in Kubernetes
        pods). If `HOSTNAME` is unset the value silently collapses to `collector-`.
        
        The `CollectorID is not a valid ID` validation error fires only when a **literal** `${` reaches the
        receiver — i.e. when you escape it as `$${...}` (the same `$$` escaping used for literal `$` in
        relabel rules) so it survives env-substitution. So the real trap is over-escaping: `$${HOSTNAME}`
        is rejected, plain `${HOSTNAME}` is not. An empty `collector_id` is also rejected.
        
        ## `use_start_time_metric` / `start_time_metric_regex` were removed
        
        These two keys used to live on this receiver but were **removed in v0.143.0** and are gone from the
        current config. To adjust metric start times from `process_start_time_seconds`, use the
        **`metric_start_time`** processor (renamed from `metricstarttime` in v0.148.0; the old name is kept
        as a deprecated alias). Putting `use_start_time_metric` in the receiver config now is an unknown-key error.
        
        ## Prometheus server features are rejected
        
        This is a *scrape-only* receiver, not a Prometheus server. These sections under `config:` are
        rejected with `unsupported features: …`:
        
        - `remote_write`
        - `remote_read`
        - `rule_files`
        - `alert_config.relabel_configs`
        - `alert_config.alertmanagers`
        
        For Remote Write ingestion use the `prometheus_remote_write` receiver; for rules/alerting run an
        actual Prometheus or use OTel processors/connectors.
        
        ## `fallback_scrape_protocol` defaulting (Prometheus 3.0)
        
        Since Prometheus 3.0, scrapes without a proper `Content-Type` header fail. The receiver
        auto-defaults each scrape config's `fallback_scrape_protocol` to `PrometheusText0.0.4` when unset,
        preserving the older lenient behavior. If you scrape native histograms you must still add
        `PrometheusProto` to `scrape_protocols` explicitly — the fallback does not cover that.
        
        ## Validation-error → fix
        
        | Error | Cause | Fix |
        |-------|-------|-----|
        | `no Prometheus scrape_configs or target_allocator set` | Neither inline `scrape_configs`, nor `scrape_config_files`, nor `target_allocator` provided. | Add at least one scrape source. |
        | `unsupported features: …` | A Prometheus server-only section is present under `config:`. | Remove `remote_write`/`remote_read`/`rule_files`/`alert_config.*`. |
        | `TargetAllocator endpoint is not valid: …` | `target_allocator.endpoint` is not a valid request URI. | Use a full URL like `http://my-ta-service`. |
        | `CollectorID is not a valid ID` | `collector_id` is empty, or a **literal** `${` reached the receiver (e.g. escaped as `$${`). Plain `${ENV}` is env-expanded before validation and does not trigger this. | Use a literal id (e.g. `collector-1`), or a `${ENV}` that expands to a valid id. |
        | `interval must be a positive duration, got …` | `target_allocator.interval` is zero/negative/unset. | Set a positive duration (e.g. `30s`). |
        | Empty/blanked relabel replacement | Unescaped `$` was treated as an env var. | Escape literal `$` as `$$`. |
        
        ## Stability
        
        Metrics are **Beta**. The receiver embeds `prometheus/prometheus`'s scrape manager, so Prometheus
        scrape behavior tracks the pinned upstream version. `trim_metric_suffixes` is flagged
        **Experimental** upstream.
        
      • README.md 4.5 KB
        # `prometheus` receiver
        
        | | |
        |-|-|
        | Kind | receiver |
        | Type | `prometheus` |
        | Signals | metrics (Beta) |
        | Distributions | core, contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/prometheusreceiver> |
        
        ## Description
        
        Scrapes metrics from Prometheus-format HTTP endpoints and converts them to OTLP metrics. It embeds the **scrape manager and parser from `prometheus/prometheus`**, so the configuration under the top-level `config:` key is the same YAML you would put in a `prometheus.yml` — `global`, `scrape_configs`, `scrape_config_files`, service discovery, and relabeling all behave exactly as in Prometheus. The receiver is a *scrape-only* drop-in: it pulls from targets, it does not run a Prometheus server. Anything omitted under `config:` falls back to Prometheus's own defaults for that setting.
        
        Beyond the embedded Prometheus config, the receiver exposes a handful of extra top-level keys: `trim_metric_suffixes` (restore OTel-style metric names), `target_allocator` (fetch dynamically-assigned targets from the OpenTelemetry Operator's Target Allocator), `api_server` (host a local Prometheus agent-mode debug API), and three start-up/shutdown scrape tuning knobs added in v0.155.0 (`scrape_on_shutdown`, `discovery_reload_on_startup`, `initial_scrape_offset`). Scraped `target_info`/`otel_scope_*` series are consumed to populate the OTel Resource and Instrumentation Scope, and native histograms are auto-converted to OTel exponential histograms.
        
        The two facts that catch people out: the receiver is **stateful and does not auto-shard** — running multiple replicas with the same config scrapes every target multiple times (duplicate metrics); use the Target Allocator or manual sharding instead. And the `$` character in the Prometheus config is interpreted by the Collector as an environment variable, so a literal `$` (in a relabel regex or replacement) must be escaped as `$$`. See [quirks.md](quirks.md).
        
        ## Main use-cases
        
        Use when:
        - You have applications, exporters, or infrastructure that expose metrics in Prometheus format and you want to pull them into an OTLP pipeline.
        - You are migrating an existing `prometheus.yml` into the Collector — paste your `scrape_configs` under `config:` largely unchanged.
        - You run on Kubernetes with the OpenTelemetry Operator and want dynamically-assigned, sharded scrape targets — use `target_allocator`.
        - You need to scrape native histograms and forward them as OTel exponential histograms.
        
        Avoid when:
        - The source already speaks OTLP — use the [`otlp`](../otlp/README.md) receiver.
        - The source pushes via Prometheus Remote Write — use the `prometheus_remote_write` receiver instead (push, not pull).
        - You need Prometheus *server* features (`remote_write`, `remote_read`, `rule_files`, alerting) — this receiver rejects them; it only scrapes.
        
        ## Related components
        
        - [`otlp` receiver](../otlp/README.md) — the OTLP-native ingress; `prometheus` is for Prometheus-format scrape targets.
        - [`interval` processor](../interval/README.md) — downsample chatty scrapes by emitting only the latest value per series per interval.
        - `prometheus_remote_write` receiver and `prometheus` / [`prometheus_remote_write`](../prometheus_remote_write/README.md) exporters — the Prometheus-family counterparts (push vs pull); pair a scrape ingress here with a Prometheus-format egress.
        - [`transform`](../transform/README.md) / [`filter`](../filter/README.md) — drop or mutate metrics after scrape, though Prometheus `metric_relabel_configs` does much of this at the source.
        
        ## Details
        
        - [Configuration](configuration.md) — every top-level key (`config`, `trim_metric_suffixes`, `target_allocator`, `api_server`, plus the v0.155.0 `scrape_on_shutdown` / `discovery_reload_on_startup` / `initial_scrape_offset` knobs), every validation rule, scrape protocols, and representative `scrape_config` examples.
        - [Verification](verification.md) — a self-scrape recipe (the collector scrapes its own `:8888` telemetry endpoint → `debug`), verified on contrib v0.154.0. Notes why `telemetrygen` cannot drive this receiver.
        - [Advanced use-cases](advanced.md) — named instances, the Target Allocator, native histograms, the `api_server`, relabeling, and `scrape_config_files`.
        - [Known quirks](quirks.md) — the no-auto-shard/duplicate-scrape warning, `$$` escaping, the `collector_id` `${...}` trap, removed `use_start_time_metric`, rejected server features, and stability.
        
      • verification.md 3.2 KB
        # `prometheus` receiver: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        The `prometheus` receiver is **scrape-based**, so `telemetrygen` does **not** apply — there is no
        OTLP endpoint to push into; the receiver pulls from a Prometheus-format HTTP target. The
        self-contained way to test it is to have the collector **scrape its own internal telemetry
        endpoint**: `service.telemetry.metrics` can expose a Prometheus `/metrics` endpoint, and the
        `prometheus` receiver scrapes that, proving the full scrape → parse → OTLP → `debug` path with no
        external target. Verified on `otel/opentelemetry-collector-contrib:0.154.0`.
        
        This is a deliberately minimal repro: no `memory_limiter`, single pipeline. Do not copy it verbatim
        into production — a real metrics pipeline should front the receiver with `memory_limiter`.
        
        Config (`prometheus-verify.yaml`) — the receiver scrapes the collector's own metrics port `:8888`,
        `debug` at detailed verbosity so each metric is printed:
        
        ```yaml
        receivers:
          prometheus:
            config:
              scrape_configs:
                - job_name: 'otel-self'
                  scrape_interval: 5s
                  static_configs:
                    - targets: ['0.0.0.0:8888']
        exporters:
          debug:
            verbosity: detailed
        service:
          telemetry:
            metrics:
              readers:
                - pull:
                    exporter:
                      prometheus:
                        host: 0.0.0.0
                        port: 8888
          pipelines:
            metrics:
              receivers: [prometheus]
              exporters: [debug]
        ```
        
        Start the collector, publishing the telemetry port so the self-scrape target resolves:
        
        ```bash
        IMG=otel/opentelemetry-collector-contrib:0.154.0
        docker run -d --name prom-recv -p 8888:8888 \
          -v "$PWD/prometheus-verify.yaml:/etc/otelcol-contrib/config.yaml" $IMG
        ```
        
        Wait ~12s so at least two 5s scrape intervals elapse (the first scrape fires while the collector is
        still warming up and reports fewer series), then read the per-scrape batch summary the `debug`
        exporter logs each time the receiver delivers a scrape:
        
        ```bash
        sleep 12
        docker logs prom-recv 2>&1 | grep '"otelcol.component.id": "debug"'
        ```
        
        **What proves it worked:** each line is one completed scrape → parse → OTLP → `debug` cycle. Once
        warmed up, every 5s scrape of the collector's own `/metrics` endpoint delivers a steady batch.
        Verified on `otel/opentelemetry-collector-contrib:0.154.0` — the first (warm-up) scrape reported
        `metrics: 12`, then each subsequent scrape was stable:
        
        ```
        ... "otelcol.component.id": "debug", ... "resource metrics": 1, "metrics": 17, "data points": 18}
        ```
        
        Spot-check that a well-known self-metric came through (it appears once per scrape):
        
        ```bash
        docker logs prom-recv 2>&1 | grep -c '\-> Name: otelcol_process_uptime'
        ```
        
        returns one hit per elapsed scrape, and the distinct `otelcol_*` series scraped is **11**
        (`docker logs prom-recv 2>&1 | grep '\-> Name: otelcol_' | sort -u | wc -l`).
        
        If `debug` logs no batch lines: the most likely cause is that no scrape interval has elapsed yet
        (wait longer), or the `:8888` target is unreachable — confirm `service.telemetry.metrics` exposes
        the Prometheus reader on the same host/port the scrape config targets.
        
        Tear down:
        
        ```bash
        docker rm -f prom-recv
        ```
        
    • prometheus_exporter
      • advanced.md 3.2 KB
        # `prometheus` exporter: advanced use-cases
        
        ## Resource attributes: `target_info` vs `resource_constant_labels`
        
        By default, resource attributes are **not** copied onto each metric's labels — they are exposed on a separate `target_info` series, following Prometheus convention. To use them in PromQL you join on the standard identity labels:
        
        ```promql
        my_metric * on (job, instance) group_left target_info
        ```
        
        Two ways to bring the attributes onto the metric labels directly instead:
        
        - **Copy the few you need** with the [`transform`](../transform/README.md) processor (datapoint context), so only the relevant attributes become labels and cardinality stays bounded:
        
          ```yaml
          processors:
            transform:
              metric_statements:
                - context: datapoint
                  statements:
                    - set(attributes["namespace"], resource.attributes["k8s.namespace.name"])
          ```
        
        - **Select resource labels** with wildcard `included` / `excluded` patterns:
        
          ```yaml
          exporters:
            prometheus:
              endpoint: 0.0.0.0:9464
              resource_constant_labels:
                included: ["k8s.*", "deployment.environment.name"]
                excluded: ["k8s.pod.uid"]
          ```
        
        The older `resource_to_telemetry_conversion` block is deprecated in v0.160.0.
        
        ## OpenMetrics and exemplars
        
        Exemplars are exported **only** in OpenMetrics format, and **only** for histograms and monotonic sums (counters). Enable OpenMetrics to get them:
        
        ```yaml
        exporters:
          prometheus:
            endpoint: 0.0.0.0:9464
            enable_open_metrics: true
        ```
        
        There is no exemplar support for native histograms (below).
        
        ## Native histograms
        
        OTel exponential histograms are automatically converted to the Prometheus **native histogram** format. To scrape them, the Prometheus server must scrape using the protobuf format and accept native histograms. (No exemplars are emitted for native histograms.)
        
        ## Choosing a `translation_strategy`
        
        `translation_strategy` supersedes the deprecated `add_metric_suffixes` when set (see [configuration.md](configuration.md#translation_strategy)). Pick by what your consumer expects:
        
        ```yaml
        exporters:
          prometheus:
            endpoint: 0.0.0.0:9464
            translation_strategy: UnderscoreEscapingWithSuffixes   # classic Prometheus compatibility
        ```
        
        - `UnderscoreEscapingWithSuffixes` / `UnderscoreEscapingWithoutSuffixes` — escape names to classic Prometheus form, with or without type/unit suffixes.
        - `NoUTF8EscapingWithSuffixes` — keep UTF-8 names, still append suffixes.
        - `NoTranslation` — pass names/labels through unaltered.
        
        Note the content-negotiation caveat: Prometheus may still apply underscore escaping on the wire even when you keep UTF-8. The `exporter.prometheusexporter.DisableAddMetricSuffixes` feature gate (beta, since v0.132.0) forces `translation_strategy` to always be used and the deprecated `add_metric_suffixes` to be ignored.
        
        ## The `sending_queue` caveat
        
        This exporter accumulates in memory and is scraped, so the standard `exporterhelper` `sending_queue` has far less meaning here than for a pushing exporter — there is no downstream send to queue against. Don't over-tune it. If you do need the full queue reference, it is documented on the [`otlp_grpc` exporter](../otlp_exporter/configuration.md#sending_queue).
        
      • configuration.md 6 KB
        # `prometheus` exporter: configuration
        
        All keys live under the exporter instance — `exporters: { prometheus: { … } }`. Facts below trace to contrib **v0.160.0** source (`exporter/prometheusexporter/config.go` + `factory.go`, with the embedded `confighttp.ServerConfig`).
        
        ## Top-level keys
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `endpoint` | string | — (**required**) | Address the HTTP server binds to; metrics are exposed at the fixed path `/metrics`. From the embedded `confighttp.ServerConfig`. |
        | `namespace` | string | — | If set, prefixes every exported series (e.g. `namespace: testns` → `testns_<metric>`). |
        | `const_labels` | map[string]string | — | Key/value labels applied to **every** exported metric. |
        | `send_timestamps` | bool | `false` | When `true`, appends the underlying sample timestamp to each exposed line. |
        | `metric_expiration` | duration | `5m` | How long a series stays exposed without an update; after this it is dropped from `/metrics`. |
        | `resource_constant_labels` | object | — | Select resource attributes to add as labels with `included` / `excluded` wildcard patterns. Added in v0.160.0. The legacy `enabled` and `exclude_service_attributes` fields are rejected in this block. |
        | `resource_to_telemetry_conversion` | object | — | **Deprecated** in v0.160.0; use `resource_constant_labels`. Its legacy `enabled` / `exclude_service_attributes` fields remain accepted unless the migration gate is enabled. |
        | `enable_open_metrics` | bool | `false` | When `true`, exposes using OpenMetrics format. Exemplars are exported **only** in OpenMetrics format, and **only** for histograms and monotonic sums (counters). |
        | `without_scope_info` | bool | `false` | When `true`, omits instrumentation-scope labels (`otel_scope_name`, `otel_scope_version`, `otel_scope_schema_url`, and scope attributes). |
        | `add_metric_suffixes` | bool | `true` | **Deprecated** (use `translation_strategy`). When `true`, appends type/unit suffixes (e.g. counters get `_total`). **Ignored** when `translation_strategy` is explicitly set. |
        | `translation_strategy` | string (enum) | — | When set, takes precedence over `add_metric_suffixes`; controls how OTLP metric/attribute names are translated to Prometheus names. See [translation_strategy](#translation_strategy). |
        | `sending_queue` | object | — | The standard `exporterhelper` queue block. Far less meaningful here than for a pushing exporter (see [advanced.md](advanced.md) and [quirks.md](quirks.md)). |
        
        The exporter also accepts all standard HTTP **server** options from the embedded `confighttp.ServerConfig` (`tls`, `cors`, `auth`, `response_headers`, `compression_algorithms`, `max_request_body_size`, and others — cross-reference the confighttp documentation rather than enumerating exhaustively). Server defaults from `NewDefaultServerConfig()`:
        
        | Server key | Default |
        |-----|---------|
        | `write_timeout` | `30s` |
        | `read_header_timeout` | `1m` |
        | `keepalive.idle_timeout` | `1m` |
        | `keepalive.enabled` | `true` |
        
        The former flat `idle_timeout` and `keep_alives_enabled` fields are deprecated in core v0.160.0.
        Nonzero `idle_timeout` and `keep_alives_enabled: false` cannot be set with the new `keepalive` block;
        the no-op values `idle_timeout: 0` and `keep_alives_enabled: true` may coexist with it.
        
        ## `translation_strategy`
        
        When set, this enum takes precedence over the deprecated `add_metric_suffixes`. `Validate()` rejects any value other than the four below with `invalid translation_strategy: <v>`.
        
        | Value | Meaning |
        |-------|---------|
        | `UnderscoreEscapingWithSuffixes` | Fully escape names for classic Prometheus compatibility **and** append type/unit suffixes. |
        | `UnderscoreEscapingWithoutSuffixes` | Escape special characters to `_`, but no suffixes. |
        | `NoUTF8EscapingWithSuffixes` | Keep UTF-8 (no `_` escaping); still append unit / `_total` suffixes. |
        | `NoTranslation` | Bypass all translation; names/labels pass through unaltered. |
        
        > **Content-negotiation caveat** (from the upstream README): Prometheus negotiates the exposition format when scraping, so underscore escaping may still be applied on the wire even when this is set to keep UTF-8.
        
        ## `sending_queue`
        
        Optional. The standard `exporterhelper` queue block. Because this is a **pull** exporter that accumulates in memory and is scraped, queue/retry semantics are far less meaningful here than for a pushing exporter. Tune it only if you have a specific reason; for the full queue reference see the [`otlp_grpc` exporter's configuration](../otlp_exporter/configuration.md#sending_queue).
        
        ## Validation summary
        
        | Condition | Error / rule | When |
        |-----------|--------------|------|
        | `translation_strategy` set to anything other than the four valid values | `invalid translation_strategy: <v>` | config validation |
        | both resource-conversion blocks configured | rejected; use only `resource_constant_labels` | config validation |
        | `resource_constant_labels.enabled` or `.exclude_service_attributes` set | rejected; use `included` / `excluded` | config validation |
        
        An empty `endpoint` is not caught by `Config.Validate()`; `endpoint` has no default, so the missing-address error surfaces one step later, at exporter construction during pipeline build — which the `validate` subcommand triggers, reporting `expecting a non-blank address to run the Prometheus metrics handler`.
        
        ## Feature gate
        
        | Gate | Stage | Since | Effect |
        |------|-------|-------|--------|
        | `exporter.prometheusexporter.DisableAddMetricSuffixes` | `beta` | v0.132.0 | When enabled, the deprecated `add_metric_suffixes` is ignored and `translation_strategy` is always used. (Spec PR 4533.) |
        | `exporter.prometheus.DisableResourceToTelemetryConversion` | `alpha` | v0.160.0 | Rejects deprecated `resource_to_telemetry_conversion`; use `resource_constant_labels`. |
        | `pkg.translator.prometheus.PermissiveLabelSanitization` | `beta` (since v0.162.0) | v0.55.0 | Shared with `prometheus_remote_write`. Enabled by default: labels with a single leading underscore (`_foo`) are kept instead of being rewritten to `key_foo`. Disable to restore the prefixing. |
        
      • quirks.md 3.4 KB
        # `prometheus` exporter: known quirks
        
        ## It's a server (pull), not a pusher
        
        This exporter **hosts** an HTTP server and exposes metrics at `/metrics` for a scraper to pull. It does **not** push metrics anywhere. The common mistake is reaching for it when you actually want to **send** metrics out via Prometheus Remote Write — that is the separate [`prometheus_remote_write`](../prometheus_remote_write/README.md) exporter. They are not interchangeable: this one waits to be scraped; that one initiates pushes.
        
        ## Same `prometheus` type string as the receiver
        
        The `prometheus` **receiver** (a scrape ingress) and this `prometheus` **exporter** share the type string `prometheus` but are different component classes. They coexist in one config — `receivers: { prometheus: … }` and `exporters: { prometheus: … }` are unrelated components — so don't assume a reference to "the prometheus component" means either one.
        
        ## `endpoint` is required, and the path is fixed at `/metrics`
        
        `endpoint` has **no default**; without it the HTTP server has nothing to bind to. The exposition path is always `/metrics` — it is not configurable. `Config.Validate()` does not catch a missing `endpoint` (it only validates `translation_strategy`), so the failure surfaces when the exporter is **constructed during pipeline build**, not from `Config.Validate()`. Because the `validate` subcommand builds pipelines, it does report this — `failed to build pipelines: failed to create "prometheus" exporter for data type "metrics": expecting a non-blank address to run the Prometheus metrics handler` — rather than waiting for server startup.
        
        ## Stale series after `metric_expiration`; counters reset on restart
        
        The exporter holds an in-memory accumulator. A series stays exposed only as long as it keeps getting updates within `metric_expiration` (default **5m**); after that it is dropped from `/metrics`. Because the state is in-memory, all accumulated values — including counters — **reset when the collector restarts**, which a scraping Prometheus will see as a counter reset.
        
        ## `add_metric_suffixes` is deprecated — prefer `translation_strategy`
        
        `add_metric_suffixes` (default `true`) appends type/unit suffixes (counters get `_total`), but it is **deprecated** and is **ignored** whenever `translation_strategy` is explicitly set. The `exporter.prometheusexporter.DisableAddMetricSuffixes` feature gate (stage `beta`, since v0.132.0) makes the collector ignore `add_metric_suffixes` entirely and always use `translation_strategy`. Migrate to `translation_strategy` for new configs.
        
        ## Exemplars need OpenMetrics, and only on histograms/counters
        
        Exemplars are exported **only** when `enable_open_metrics: true`, and even then **only** for histograms and monotonic sums (counters). Other metric types never carry exemplars, and native histograms get none at all.
        
        ## Horizontal scaling: scrape every replica, no aggregation
        
        Each collector replica exposes only the metrics it itself received — there is no cross-replica aggregation. If you run several replicas, the scraping Prometheus must target **every** replica to get the full picture, and you must handle the resulting per-replica series accordingly. There is no built-in sharding or merging on the exposition side.
        
        ## Stability is per signal
        
        Metrics are **Beta** — and metrics is the **only** signal this exporter supports (no traces, logs, or profiles).
        
      • README.md 3.9 KB
        # `prometheus` exporter
        
        | | |
        |-|-|
        | Kind | exporter |
        | Type | `prometheus` |
        | Signals | metrics (Beta) |
        | Distributions | core, contrib |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusexporter` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/prometheusexporter> |
        
        ## Description
        
        This is a **pull / expose** exporter — it does **not push anywhere**. It hosts an HTTP server that exposes your pipeline's metrics in Prometheus exposition format at the fixed path `/metrics`, for a Prometheus server (or any scraper) to scrape. This is the single most common point of confusion: if you want to **push** metrics out via Prometheus Remote Write, you want the [`prometheus_remote_write`](../prometheus_remote_write/README.md) exporter, not this one. It embeds `confighttp.ServerConfig`, so it accepts the standard HTTP server options (`tls`, `cors`, `auth`, `max_request_body_size`, and so on); `MutatesData` is `true`.
        
        Internally it keeps an in-memory **accumulator**: it stores the latest aggregated value per series and re-exposes that value on every scrape, dropping a series after `metric_expiration` (default `5m`) elapses with no update. Metric naming is controlled by `add_metric_suffixes` (default `true`, **deprecated**) or the newer `translation_strategy` enum, which takes precedence when set. See the detail files below.
        
        > **Same type string as the `prometheus` receiver.** The `prometheus` *receiver* (a scrape ingress) and this `prometheus` *exporter* share the type string `prometheus` but are different component classes; they coexist in one config without conflict. Don't confuse the two.
        
        ## Main use-cases
        
        Use when:
        - You want to expose OTLP-pipeline metrics for a Prometheus server (or any scraper) to pull.
        - You want a local, human-scrapable `/metrics` endpoint for debugging a metrics pipeline.
        - You are bridging an OTLP pipeline into a pull-based Prometheus world.
        
        Avoid when:
        - You need to **push** metrics out — use the [`prometheus_remote_write`](../prometheus_remote_write/README.md) exporter.
        - The backend speaks OTLP — use the [`otlp_grpc`](../otlp_exporter/README.md) or `otlphttp` exporter.
        - You run many horizontally-scaled collector replicas — each exposes only its own subset of the data, so the scraper must target every replica and there is no cross-replica aggregation.
        
        ## Related components
        
        - [`prometheus` receiver](../prometheus/README.md) — the scrape-ingress counterpart (same type string, different class); pulls Prometheus-format metrics *into* a pipeline.
        - [`prometheus_remote_write`](../prometheus_remote_write/README.md) exporter — the **push** sibling; pick it when you need to send metrics out via Remote Write rather than be scraped.
        - [`otlp_grpc`](../otlp_exporter/README.md) / `otlphttp` exporter — OTLP egress when the backend speaks OTLP.
        - [`transform`](../transform/README.md) — copy selected resource attributes onto datapoint attributes so they become Prometheus labels for grouping.
        
        ## Details
        
        - [Configuration](configuration.md) — `endpoint`, naming, `resource_constant_labels`, deprecated resource conversion, OpenMetrics, the embedded HTTP server options, and validation rules.
        - [Verification](verification.md) — drive an OTLP pipeline with `telemetrygen` and `curl` the exporter's own `/metrics`, proving namespace, suffixing, `const_labels`, timestamps, and latest-value accumulation on contrib v0.154.0. It does not exercise `resource_constant_labels`, added in v0.160.0.
        - [Advanced use-cases](advanced.md) — `target_info` vs `resource_constant_labels`, OpenMetrics + exemplars, native histograms, `translation_strategy` choices, and the queue caveat.
        - [Known quirks](quirks.md) — pull-not-push, the shared `prometheus` type string, required `endpoint`, stale series and counter resets, `add_metric_suffixes` deprecation, exemplar limits, horizontal-scaling behavior, and per-signal stability.
        
      • verification.md 3.3 KB
        # `prometheus` exporter: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        Unlike most exporters, you observe the result by **scraping the exporter's own `/metrics` endpoint with `curl`** — there is no downstream backend to watch, because this exporter exposes rather than pushes (the `debug` exporter below is included only to confirm what entered the pipeline). The recipe drives an OTLP pipeline with `telemetrygen` and reads the exposed series back. This is a **minimal repro** — it omits `memory_limiter` and other production scaffolding on purpose, to isolate the exporter's behavior. Verified on `otel/opentelemetry-collector-contrib:0.154.0`.
        
        Config (`config.yaml`) — a `metrics` pipeline from the `otlp` receiver into both `prometheus` and `debug`:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          prometheus:
            endpoint: 0.0.0.0:9464
            namespace: testns
            const_labels:
              verified_by: ollygarden
            send_timestamps: true
          debug:
            verbosity: detailed
        service:
          pipelines:
            metrics:
              receivers: [otlp]
              exporters: [prometheus, debug]
        ```
        
        Run it, publishing both the OTLP receiver and the `/metrics` server to the host:
        
        ```bash
        docker run -d --name prom-exp \
          -p 14317:4317 -p 19464:9464 \
          -v "$PWD/config.yaml:/etc/otelcol-contrib/config.yaml" \
          otel/opentelemetry-collector-contrib:0.154.0
        ```
        
        Drive it with `telemetrygen` — see the `otel-telemetrygen` skill. This emits one cumulative Sum metric named `gen` with **3 datapoints, values 0, 1, 2** (confirmed in the `debug` exporter's output as three `Metric #0 Name: gen` lines with `Value: 0/1/2`):
        
        ```bash
        telemetrygen metrics --otlp-insecure --otlp-endpoint localhost:14317 \
          --metrics 3 --workers 1 --metric-type Sum \
          --telemetry-attributes 'tg_label="v1"'
        ```
        
        **What proves it worked:** scrape the exporter's own endpoint:
        
        ```bash
        curl -s localhost:19464/metrics
        ```
        
        produces exactly:
        
        ```text
        # HELP testns_gen_total 
        # TYPE testns_gen_total counter
        testns_gen_total{job="telemetrygen",otel_scope_name="",otel_scope_schema_url="",otel_scope_version="",tg_label="v1",verified_by="ollygarden"} 2 1781369164374
        ```
        
        Each piece confirms a specific behavior:
        
        - **`namespace: testns`** → the `testns_` series prefix.
        - **`_total` suffix and `# TYPE … counter`** → `add_metric_suffixes` defaults to `true`, and this is a counter, so the type/unit suffix is appended.
        - **`const_labels`** → the `verified_by="ollygarden"` label on the series.
        - **the telemetry attribute** → the `tg_label="v1"` label.
        - **scope labels present** (`otel_scope_name`/`otel_scope_version`/`otel_scope_schema_url`, empty here) → `without_scope_info` defaults to `false`.
        - **`send_timestamps: true`** → the trailing millisecond timestamp `1781369164374`.
        - **latest-value accumulation** → the three datapoints had values 0, 1, 2 at increasing timestamps; the exposed value is **2**. The exporter keeps and exposes the **latest** aggregated value per series (its in-memory accumulator picks the most recent timestamp), not a sum or a stream.
        
        No `target_info` series appeared with `telemetrygen`'s minimal resource — that mechanism is covered in [advanced.md](advanced.md), not claimed here.
        
        Tear down:
        
        ```bash
        docker rm -f prom-exp
        ```
        
    • prometheus_remote_write
      • advanced.md 5.7 KB
        # `prometheus_remote_write` exporter: advanced use-cases
        
        ## Write-Ahead Log (WAL)
        
        The WAL persists outgoing metrics to disk before sending, so a crash or backend outage doesn't lose in-flight data. It is **off** unless `wal.directory` is set:
        
        ```yaml
        exporters:
          prometheus_remote_write:
            http:
              endpoint: https://my-cortex:7900/api/v1/push
            wal:
              directory: ./prom_rw     # setting this enables the WAL
              buffer_size: 100         # default 300
              truncate_frequency: 45s  # default 1m
              lag_record_frequency: 15s
        ```
        
        When the WAL is enabled, `max_batch_size_bytes` is **ignored** — the WAL `buffer_size` / `truncate_frequency` govern how much is read and how often it is truncated.
        
        This WAL is the **exporter's own** on-disk buffer (backed by a `wal` library, stored under `<directory>/prom_remotewrite`). It is **not** the [`file_storage`](../file_storage/README.md) extension and does not reference a `storage` ID — don't confuse the two.
        
        ## RW2 / `protobuf_message` and the feature gate
        
        `protobuf_message` selects the wire format:
        
        - `prometheus.WriteRequest` — Remote Write 1.0 (the default).
        - `io.prometheus.write.v2.Request` — Remote Write 2.0: more efficient, always includes metadata, and adds created-timestamp and native-histogram support.
        
        RW2 is only honored when the feature gate `exporter.prometheusremotewritexporter.enableSendingRW2` (alpha, since v0.125.0) is enabled — otherwise `Validate()` errors. Per upstream, **PRW 2.0 is "In Development", only partially implemented, and not ready for usage.** Your backend must also support PRW 2.0.
        
        RW2 also unlocks the UTF-8 `translation_strategy` values (`NoUTF8EscapingWithSuffixes`, `NoTranslation`), which are rejected under RW1.
        
        ## `resource_constant_labels` vs `target_info`
        
        By default, resource attributes are **not** copied onto each series — they are emitted on a separate `target_info` metric (`target_info.enabled` defaults to `true`). To use them in PromQL, join on the identity labels:
        
        ```promql
        my_metric * on (job, instance) group_left target_info{k8s_namespace_name="my-namespace"}
        ```
        
        Two ways to bring attributes onto the metric labels directly:
        
        - **Copy the few you need** with the [`transform`](../transform/README.md) processor (datapoint context), keeping cardinality bounded:
        
          ```yaml
          processors:
            transform:
              metric_statements:
                - context: datapoint
                  statements:
                    - set(attributes["namespace"], resource.attributes["k8s.namespace.name"])
          ```
        
        - **Select resource labels** with wildcard `included` / `excluded` patterns:
        
          ```yaml
          exporters:
            prometheus_remote_write:
              http:
                endpoint: https://my-cortex:7900/api/v1/push
              resource_constant_labels:
                included: ["k8s.*", "deployment.environment.name"]
                excluded: ["k8s.pod.uid"]
          ```
        
        The legacy `resource_to_telemetry_conversion` block is deprecated in v0.160.0.
        
        ## `external_labels`
        
        `external_labels` attaches a fixed set of labels to **every** series — useful for tagging a cluster, region, or collector identity. Values may start with the reserved `__` prefix:
        
        ```yaml
        exporters:
          prometheus_remote_write:
            http:
              endpoint: https://my-cortex:7900/api/v1/push
            external_labels:
              cluster: prod-eu
              collector: gateway-1
        ```
        
        ## Multiple workers: the `EnableMultipleWorkers` gate
        
        The feature gate `exporter.prometheusremotewritexporter.EnableMultipleWorkers` (alpha, since v0.118.0) changes how `num_consumers` and `max_batch_request_parallelism` behave:
        
        - **Gate off (default):** `remote_write_queue.num_consumers` defaults to `5` and drives **request** parallelism. The factory logs a migration warning if you set it to anything other than `5`.
        - **Gate on:** `num_consumers` becomes the **queue-worker** count (defaulting to `1`), handling batches from the queue concurrently, while `max_batch_request_parallelism` drives parallelism for splitting a single batch larger than `max_batch_size_bytes`.
        
        > **Out-of-order caveat.** With the gate on and `num_consumers > 1` (or `max_batch_request_parallelism > 1`), the temporal ordering of samples is no longer guaranteed. Vanilla Prometheus rejects out-of-order samples — enable `tsdb.out_of_order_time_window` on the backend, or set `max_batch_request_parallelism: 1`. Other backends (Thanos / Mimir / VictoriaMetrics) have their own settings.
        
        ```yaml
        exporters:
          prometheus_remote_write:
            http:
              endpoint: https://my-cortex:7900/api/v1/push
            max_batch_request_parallelism: 1   # safe for backends that reject out-of-order samples
        ```
        
        ## Retrying on HTTP 429
        
        By default the exporter does **not** retry on `429 Too Many Requests` — RW backends (Mimir, Cortex, …) return it under rate limiting, and it is treated as a non-retryable error. The feature gate `exporter.prometheusremotewritexporter.RetryOn429` (alpha, since v0.101.0) makes the exporter retry `429` responses using the standard `retry_on_failure` backoff. Enable it if your backend rate-limits with `429` and you want the exporter to back off and retry rather than drop.
        
        ## Choosing a `translation_strategy`
        
        `translation_strategy` supersedes the deprecated `add_metric_suffixes` when set (see [configuration.md](configuration.md#translation_strategy)).
        
        ```yaml
        exporters:
          prometheus_remote_write:
            http:
              endpoint: https://my-cortex:7900/api/v1/push
            translation_strategy: UnderscoreEscapingWithSuffixes   # classic Prometheus compatibility
        ```
        
        - `UnderscoreEscapingWithSuffixes` / `UnderscoreEscapingWithoutSuffixes` — escape names to classic Prometheus form, with or without type/unit suffixes. Both work under RW1.
        - `NoUTF8EscapingWithSuffixes` / `NoTranslation` — keep UTF-8 / pass through unaltered. **Require PRW 2.0** (rejected under RW1).
        
      • configuration.md 10.3 KB
        # `prometheus_remote_write` exporter: configuration
        
        All keys live under the exporter instance — `exporters: { prometheus_remote_write: { … } }` (the deprecated alias `prometheusremotewrite` also works). Facts below trace to contrib **v0.160.0** source (`exporter/prometheusremotewriteexporter/config.go` + `factory.go` + `wal.go`, with `confighttp.ClientConfig`, `exporterhelper.TimeoutConfig`, and `configretry.BackOffConfig`).
        
        ## Top-level keys
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `http` | object | standard HTTP client defaults | Nested `confighttp.ClientConfig`. Added in v0.159.0; preferred over the deprecated flat HTTP client settings. |
        | `http.endpoint` | string | `http://some.url:9411/api/prom/push` (**must override**) | Remote-write URL to push samples to. The factory ships a placeholder — set your real backend URL. |
        | `namespace` | string | `""` | Prefix prepended to every exported metric name. |
        | `external_labels` | map[string]string | `{}` | Labels attached to **every** series. Values may start with the reserved `__` prefix. |
        | `add_metric_suffixes` | bool | `true` | **Deprecated** (use `translation_strategy`). When `false`, no type/unit suffixes are added; the factory logs a deprecation warning. **Ignored** when `translation_strategy` is explicitly set. See note below on the removal-version discrepancy. |
        | `translation_strategy` | string (enum) | `""` (unset) | When set, takes precedence over `add_metric_suffixes`. See [translation_strategy](#translation_strategy). When unset, `add_metric_suffixes` governs. |
        | `send_metadata` | bool | `false` | When `true`, generate and send Prometheus metadata. **Ignored under PRW 2.0**, which always includes metadata. |
        | `disable_scope_info` | bool | `false` | When `true`, omits instrumentation-scope labels (`otel_scope_name`, `otel_scope_version`, and `otel_scope_*` attributes). |
        | `max_batch_size_bytes` | int | `3000000` (≈ 2.86 MB) | Batches larger than this are split into multiple requests. **Ignored when the WAL is enabled** (the WAL `buffer_size` / `truncate_frequency` govern instead). |
        | `max_batch_request_parallelism` | int (pointer) | unset (documented as `5`) | Parallel requests when splitting a single oversized batch. When unset (the default), request parallelism is governed by `remote_write_queue.num_consumers`; this key only takes over once the `EnableMultipleWorkers` gate is on. Set to `1` if the backend cannot ingest out-of-order samples. See validation below. |
        | `protobuf_message` | string | `prometheus.WriteRequest` (RW1) | `prometheus.WriteRequest` = Remote Write 1.0; `io.prometheus.write.v2.Request` = Remote Write 2.0. RW2 requires the `enableSendingRW2` feature gate (see [advanced.md](advanced.md)). |
        | `include_metadata_keys` | []string | `[]` | Forward selected incoming client-metadata values as HTTP request headers. Reserved Remote Write protocol headers are rejected. WAL-replayed requests cannot forward this metadata because the WAL stores only serialized request bytes. Added in v0.158.0. |
        | `convert_explicit_histograms_to_nhcb` | bool | `false` | Convert explicit-bucket histograms to Native Histograms with Custom Buckets (schema -53), for RW1 or RW2. Added in v0.158.0. |
        | `keep_classic_histograms` | bool | `false` | Also emit classic histogram series during NHCB migration; no effect unless conversion is enabled. |
        | `remote_write_queue` | object | see below | Outgoing-request queue. This exporter does **not** use `sending_queue`. |
        | `resource_constant_labels` | object | see below | Select resource attributes to convert to labels. Added in v0.160.0. |
        | `resource_to_telemetry_conversion` | object | see below | **Deprecated** in v0.160.0; use `resource_constant_labels`. |
        | `wal` | object | off unless `directory` set | Write-ahead log. See [wal](#wal). |
        | `target_info` | object | `enabled: true` | Emit a `target_info` metric per resource for resource-attribute joins. |
        | `retry_on_failure` | object | `configretry.BackOffConfig` | Standard retry block; the factory overrides `InitialInterval` to `50ms`. Cross-reference the [exporterhelper retry docs](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md). |
        | `timeout` | duration | per exporterhelper | From the embedded `exporterhelper.TimeoutConfig` (squashed). Cross-reference exporterhelper. |
        
        The `http` block accepts the standard HTTP **client** options from `confighttp.ClientConfig`, such
        as `endpoint`, `headers`, `tls`, `compression`, and `auth`. Deprecated flat HTTP settings still
        work when `http` is absent; when `http` is present it takes precedence. The alpha
        `exporter.prometheusremotewritexporter.removeTopLevelHTTPSettings` gate rejects flat HTTP client
        settings so migrations can be tested before their eventual removal. (`timeout` remains the
        exporterhelper send timeout at top level.) Cross-reference rather than enumerating:
        
        - [HTTP client settings (confighttp)](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md) — note only `snappy` compression is accepted (required by the Remote Write protocol). Headers `Content-Encoding`, `Content-Type`, `X-Prometheus-Remote-Write-Version`, and `User-Agent` cannot be overridden via `headers`.
        - [TLS / mTLS settings (configtls)](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/configtls/README.md) — **TLS is on by default**; set `http.tls.insecure: true` for plaintext.
        - [Retry and timeout settings (exporterhelper)](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md) — note the exporter does **not** support `sending_queue`; it provides `remote_write_queue` instead.
        
        ## `remote_write_queue`
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `enabled` | bool | `true` | When `false`, export requests run synchronously (no queue). |
        | `queue_size` | int | `10000` | Maximum number of OTLP metric batches queued at once. Ignored if `enabled: false`. |
        | `num_consumers` | int | `5` (or `1` when `EnableMultipleWorkers` gate is on) | Workers used to fan out outgoing requests. See [advanced.md](advanced.md) for how the feature gate changes its meaning. |
        
        ## Resource labels
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `resource_constant_labels.included` | []string | `[]` | Wildcard patterns selecting resource attributes to convert to labels. |
        | `resource_constant_labels.excluded` | []string | `[]` | Wildcard patterns removed from the included set. |
        
        The deprecated `resource_to_telemetry_conversion` block retains `enabled` and
        `exclude_service_attributes` for migration. Do not configure both blocks. The alpha
        `exporter.prometheusremotewrite.DisableResourceToTelemetryConversion` gate rejects the deprecated
        block. `enabled` and `exclude_service_attributes` are not valid under `resource_constant_labels`.
        
        ## `wal`
        
        Optional and **off unless `directory` is set**. When enabled, `max_batch_size_bytes` is ignored.
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `directory` | string | `""` | Directory to store the WAL in. Setting this enables the WAL. |
        | `buffer_size` | int | `300` | Count of elements read from the WAL before truncating. |
        | `truncate_frequency` | duration | `1m` | How often the WAL is truncated. |
        | `lag_record_frequency` | duration | `15s` | How often the exporter records WAL lag. |
        
        ## `target_info`
        
        | Key | Type | Default | Meaning |
        |-----|------|---------|---------|
        | `enabled` | bool | `true` | When `true`, emits a `target_info` metric per resource metric, carrying resource attributes for PromQL joins. |
        
        ## `translation_strategy`
        
        When set, this enum takes precedence over the deprecated `add_metric_suffixes`. `Validate()` rejects any value other than the four below with `invalid translation_strategy: <v>`.
        
        | Value | Meaning |
        |-------|---------|
        | `UnderscoreEscapingWithSuffixes` | Escape special characters to `_` **and** append type/unit suffixes. |
        | `UnderscoreEscapingWithoutSuffixes` | Escape special characters to `_`, but no suffixes. |
        | `NoUTF8EscapingWithSuffixes` | Keep UTF-8 (no `_` escaping); still append unit / `_total` suffixes. **Requires PRW 2.0.** |
        | `NoTranslation` | Pass names/labels through unaltered. **Requires PRW 2.0.** |
        
        > The upstream README prose calls `UnderscoreEscapingWithSuffixes` the "default", but the **code** default is the empty string, with `add_metric_suffixes: true` governing when `translation_strategy` is unset. Be precise: unset means `add_metric_suffixes` decides.
        
        ## Validation summary
        
        | Condition | Error / rule |
        |-----------|--------------|
        | `max_batch_request_parallelism` set and `< 1` | `max_batch_request_parallelism can't be set to below 1` |
        | `remote_write_queue.queue_size < 0` | `remote write queue size can't be negative` |
        | `remote_write_queue.enabled` and `queue_size == 0` | `a 0 size queue will drop all the data` |
        | `remote_write_queue.num_consumers < 0` | `remote write consumer number can't be negative` |
        | `max_batch_size_bytes < 0` | `max_batch_size_bytes must be greater than 0` |
        | `max_batch_size_bytes == 0` | not an error — **reset to `3000000`** |
        | `http.compression` set and not `snappy` | `compression type must be snappy` (empty is allowed) |
        | `protobuf_message` = `io.prometheus.write.v2.Request` without the `enableSendingRW2` gate | `remote write v2 is only supported with the feature gate exporter.prometheusremotewritexporter.enableSendingRW2` |
        | `translation_strategy` not one of the four valid values | `invalid translation_strategy: <v>` |
        | `translation_strategy` = `NoUTF8EscapingWithSuffixes` or `NoTranslation` under RW1 | `translation strategy <v> requires Prometheus Remote Write 2.0 (UTF-8 support)` |
        | `include_metadata_keys` contains `Content-Encoding`, `Content-Type`, `User-Agent`, or `X-Prometheus-Remote-Write-Version` (case-insensitive) | rejected because it collides with a reserved Remote Write header |
        | both resource-label blocks configured | `cannot configure both resource_to_telemetry_conversion and resource_constant_labels; resource_to_telemetry_conversion is deprecated` |
        | legacy fields under `resource_constant_labels` | `enabled and exclude_service_attributes are not supported under resource_constant_labels; use included and excluded instead` |
        
        > Note: `Validate()` does **not** catch an unset `http.endpoint` — the factory ships a non-empty placeholder default, so the failure surfaces at send time, not config validation.
        
      • quirks.md 5.6 KB
        # `prometheus_remote_write` exporter: known quirks
        
        ## It pushes — it does not expose
        
        This exporter **initiates** outbound Remote Write requests to a backend URL. It does **not** host a `/metrics` endpoint to be scraped — that is the separate [`prometheus`](../prometheus_exporter/README.md) exporter. The common mistake is reaching for one when you want the other: "push to a remote URL" is this component; "expose for a scraper" is the other. They are not interchangeable.
        
        ## The type was renamed in v0.154.0
        
        The type string changed from `prometheusremotewrite` (no underscores) to `prometheus_remote_write` in **v0.154.0**. The old form still works as a **deprecated alias** and logs a deprecation warning at startup. Use the snake_case `prometheus_remote_write` in new configs.
        
        ## Non-cumulative monotonic, histogram, and summary metrics are dropped
        
        Per upstream: **"Non-cumulative monotonic, histogram, and summary OTLP metrics are dropped by this exporter."** This is a Remote Write 1.0 limitation. If your pipeline produces delta sums, OTLP histograms, or summaries and you need them in the backend, convert them upstream (e.g. cumulative-ize deltas) or move to RW2 where applicable — under RW1 they silently disappear.
        
        ## TLS is on by default; snappy is the only compression
        
        TLS is **enabled by default** — a plaintext backend needs `http.tls.insecure: true`, otherwise the connection fails. Compression must be `snappy` (the only value `Validate()` accepts; empty is allowed): the Remote Write protocol requires it, so you cannot switch to gzip/zstd.
        
        ## `remote_write_queue`, not `sending_queue`
        
        This exporter does **not** use the standard `exporterhelper` `sending_queue`. Queueing is configured under `remote_write_queue` (`enabled`, `queue_size`, `num_consumers`). Configuring `sending_queue` has no effect here.
        
        | Validation error | Fix |
        |------------------|-----|
        | `a 0 size queue will drop all the data` | Set `remote_write_queue.queue_size` > 0, or disable the queue (`enabled: false`). |
        | `remote write queue size can't be negative` | Use a non-negative `queue_size`. |
        | `remote write consumer number can't be negative` | Use a non-negative `num_consumers`. |
        | `compression type must be snappy` | Remove the `compression` key, or set it to `snappy`. |
        | `max_batch_size_bytes must be greater than 0` | Use a non-negative `max_batch_size_bytes` (0 is auto-reset to `3000000`). On v0.156.0 and earlier this error misspelled the field as `max_batch_byte_size`. |
        | `max_batch_request_parallelism can't be set to below 1` | Set it to `1` or higher. |
        | `remote write v2 is only supported with the feature gate …` | Enable the `enableSendingRW2` gate, or keep `protobuf_message: prometheus.WriteRequest`. |
        | `invalid translation_strategy: <v>` | Use one of the four valid enum values. |
        | `translation strategy <v> requires Prometheus Remote Write 2.0 (UTF-8 support)` | Switch to an escaping strategy, or enable RW2. |
        
        ## `http.endpoint` ships a placeholder, not a real default
        
        The factory default for `http.endpoint` is `http://some.url:9411/api/prom/push` — a non-functional placeholder. `Validate()` does **not** catch it, so if you forget to override it the failure surfaces only when the exporter tries to send. Always set your real backend URL.
        
        ## HTTP client settings belong under `http`
        
        Put `endpoint`, `tls`, `headers`, `compression`, and the other `confighttp.ClientConfig` fields under
        `http`. Deprecated flat settings still work by default when `http` is absent, and a configured
        `http` block takes precedence. Enabling the alpha
        `exporter.prometheusremotewritexporter.removeTopLevelHTTPSettings` gate makes flat HTTP settings a
        startup error, which is useful for validating a migration.
        
        ## `add_metric_suffixes` deprecation — version discrepancy
        
        `add_metric_suffixes` (default `true`) is **deprecated** in favor of `translation_strategy`. The factory logs a deprecation warning when it is `false` (`add_metric_suffixes is deprecated. Please use translation_strategy: UnderscoreEscapingWithoutSuffixes instead.`). The `config.go` comment claims it would be removed in **v0.153.0**, but the key remains present. Treat it as live-but-deprecated; migrate to `translation_strategy` for new configs.
        
        ## RW2 is not production-ready
        
        `protobuf_message: io.prometheus.write.v2.Request` (Remote Write 2.0) requires the `exporter.prometheusremotewritexporter.enableSendingRW2` feature gate, and per upstream is **"In Development", only partially implemented, and not ready for usage.** Stay on RW1 (`prometheus.WriteRequest`, the default) for production.
        
        ## The WAL is not the `file_storage` extension
        
        The exporter's `wal` block is its **own** on-disk write-ahead log, written under `wal.directory`. It is unrelated to the [`file_storage`](../file_storage/README.md) extension and takes no `storage` ID — don't try to wire it to a storage extension.
        
        ## Name and label normalization mutates data
        
        The exporter normalizes OTLP metric names and attributes to Prometheus naming rules (so `MutatesData` is effectively true). Details of the normalization are in the [`pkg/translator/prometheus`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/translator/prometheus) module. Since v0.162.0 the shared `pkg.translator.prometheus.PermissiveLabelSanitization` gate is Beta (on by default), so labels with a single leading underscore (e.g. `_foo`) are no longer rewritten to `key_foo`; disable it with `--feature-gates=-pkg.translator.prometheus.PermissiveLabelSanitization` to restore the old prefixing.
        
        ## Stability is per signal
        
        Metrics are **Beta** — and metrics is the **only** signal this exporter supports (no traces, logs, or profiles).
        
      • README.md 4.2 KB
        # `prometheus_remote_write` exporter
        
        | | |
        |-|-|
        | Kind | exporter |
        | Type | `prometheus_remote_write` |
        | Signals | metrics (Beta) |
        | Distributions | core, contrib |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/exporter/prometheusremotewriteexporter` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/prometheusremotewriteexporter> |
        
        > **Renamed in v0.154.0.** The type string was `prometheusremotewrite` (no underscores); it is now `prometheus_remote_write`. The old `prometheusremotewrite` still works as a deprecated alias and logs a deprecation warning — migrate to the snake_case type.
        
        ## Description
        
        This is a **push** exporter — it sends OTLP metrics out as Prometheus Remote Write requests to any Remote-Write-compatible backend (Cortex, Mimir, Thanos, Prometheus itself with the receiver enabled, and similar). This is the mirror image of the [`prometheus`](../prometheus_exporter/README.md) exporter, which **hosts** a `/metrics` endpoint to be scraped: this one **initiates** outbound writes. If you are unsure which you want, "push to a remote URL" is this component; "expose for a scraper" is the other.
        
        By default it speaks **Remote Write 1.0** (`protobuf_message: prometheus.WriteRequest`), **requires TLS** (set `http.tls.insecure: true` for plaintext), and uses **snappy** compression (the only value the protocol — and this exporter's `Validate()` — accepts). HTTP client settings belong under `http`; deprecated flat settings remain accepted by default for migration. Outgoing requests are queued through `remote_write_queue` (this exporter does **not** use the standard `sending_queue`). The full key list, defaults, and validation rules are in [configuration.md](configuration.md).
        
        ## Main use-cases
        
        Use when:
        - Your metrics backend speaks Prometheus Remote Write (Cortex, Mimir, Thanos, VictoriaMetrics, Prometheus with `--web.enable-remote-write-receiver`).
        - You want the collector to **push** metrics out rather than be scraped.
        - You are bridging an OTLP pipeline into a Prometheus-protocol world without running a separate scraper.
        
        Avoid when:
        - You want to be **scraped** — use the [`prometheus`](../prometheus_exporter/README.md) exporter (hosts `/metrics`).
        - The backend speaks OTLP — use the [`otlp_grpc`](../otlp_exporter/README.md) or `otlphttp` exporter.
        - You rely heavily on non-cumulative monotonic sums, OTLP histograms, or summaries under RW1 — these are **dropped** by this exporter (see [quirks.md](quirks.md)).
        
        ## Related components
        
        - [`prometheus` exporter](../prometheus_exporter/README.md) — the **pull / expose** sibling (hosts `/metrics`); same family, opposite direction. This one pushes.
        - [`otlp_grpc`](../otlp_exporter/README.md) / `otlphttp` exporter — OTLP egress when the backend speaks OTLP.
        - [`prometheus` receiver](../prometheus/README.md) — scrape **ingress** that pulls Prometheus-format metrics into a pipeline.
        - [`resource`](../resource/README.md) / [`transform`](../transform/README.md) — shape resource attributes before `resource_constant_labels` or `target_info` export.
        - [`file_storage`](../file_storage/README.md) — only loosely related: the WAL here is the exporter's **own** on-disk buffer, **not** the `file_storage` extension (see [quirks.md](quirks.md)).
        
        ## Details
        
        - [Configuration](configuration.md) — `http.endpoint`, naming/metadata options, histogram conversion, `remote_write_queue`, resource conversion, WAL, RW version, HTTP/TLS/retry options, and validation rules.
        - [Verification](verification.md) — push to a Prometheus container with the remote-write receiver enabled and query the series back, proving `namespace` and `external_labels`. Verified end-to-end on contrib v0.159.0.
        - [Advanced use-cases](advanced.md) — the WAL, RW2, `resource_constant_labels` vs `target_info`, external labels, worker/retry feature gates, and translation choices.
        - [Known quirks](quirks.md) — push-not-pull, the type rename, `http.endpoint`, nested-vs-flat HTTP migration, TLS-on-by-default, snappy-only, dropped metric types, the `remote_write_queue` (not `sending_queue`) distinction, the `add_metric_suffixes` deprecation discrepancy, RW2 readiness, and per-signal stability.
        
      • verification.md 4.3 KB
        # `prometheus_remote_write` exporter: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        Unlike the pull-based [`prometheus`](../prometheus_exporter/verification.md) exporter (where you scrape the exporter's own `/metrics`), here you **push** to a real backend and query the series back from it. The recipe runs two containers on a shared Docker network: a Prometheus server with the remote-write receiver enabled, and a collector that pushes into it. This is a **minimal repro** — it omits `memory_limiter` and `batch` on purpose, to isolate the exporter's behavior.
        
        **Verified end-to-end on `otel/opentelemetry-collector-contrib:0.159.0` (2026-08-27).** A
        `telemetrygen` v0.159.0 cumulative Sum named `gen` (datapoints 0/1/2) pushed through the nested
        `http` configuration via RW1 and landed in Prometheus as
        `testns_gen_total{job="telemetrygen", verified_by="ollygarden"} 2`.
        This recipe's schema is unchanged through v0.160.0; it does not exercise
        `resource_constant_labels`.
        
        Create a network:
        
        ```bash
        docker network create prw-net
        ```
        
        Start Prometheus with the remote-write receiver enabled (exposes `/api/v1/write`, accepts RW1). The image ships a default config at `/etc/prometheus/prometheus.yml`:
        
        ```bash
        docker run -d --name prw-prom --network prw-net -p 19090:9090 \
          prom/prometheus:v3.1.0 \
          --config.file=/etc/prometheus/prometheus.yml \
          --web.enable-remote-write-receiver
        ```
        
        Collector config (`config.yaml`) — a `metrics` pipeline from the `otlp` receiver into `prometheus_remote_write`, pushing to the Prometheus container by its network alias (`debug` is added only to see what arrives):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        exporters:
          debug:
            verbosity: detailed
          prometheus_remote_write:
            http:
              endpoint: http://prw-prom:9090/api/v1/write
              tls:
                insecure: true
            namespace: testns
            external_labels:
              verified_by: ollygarden
        service:
          pipelines:
            metrics:
              receivers: [otlp]
              exporters: [prometheus_remote_write, debug]
        ```
        
        > The exporter `http.endpoint` host must match the Prometheus container's network alias (`prw-prom` here). A wrong host doesn't fail fast — the push hangs until the request timeout and the collector logs `Permanent error: context deadline exceeded`, `Dropping data`.
        
        Run the collector on the same network:
        
        ```bash
        docker run -d --name prw-col --network prw-net \
          -v "$PWD/config.yaml:/etc/otelcol-contrib/config.yaml" \
          otel/opentelemetry-collector-contrib:0.159.0
        ```
        
        Drive it with `telemetrygen` — see the `otel-telemetrygen` skill. Run it as a container **on the same network** targeting the collector's alias (this avoids a host-port-mapped gRPC stall some Docker Desktop setups hit). This emits one **cumulative Sum** named `gen`; `telemetrygen` already defaults to cumulative temporality, which is required here — the exporter **drops non-cumulative (delta) monotonic sums** (see [quirks](quirks.md)). `--metrics N` is **per worker** and is **ignored when `--duration` is set**, so use `--workers 1` and no `--duration`:
        
        ```bash
        docker run --rm --network prw-net \
          ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.159.0 \
          metrics --otlp-insecure --otlp-endpoint prw-col:4317 \
          --metrics 3 --workers 1 --metric-type Sum
        ```
        
        **What proves it worked:** query the series back from Prometheus. The counter gets the `_total` suffix (`add_metric_suffixes` defaults to `true`) and the `testns_` namespace prefix:
        
        ```bash
        curl -s 'http://localhost:19090/api/v1/query?query=testns_gen_total'
        ```
        
        ```json
        {"status":"success","data":{"resultType":"vector","result":[
          {"metric":{"__name__":"testns_gen_total","job":"telemetrygen","verified_by":"ollygarden"},
           "value":[1787819647.001,"2"]}]}}
        ```
        
        - **`namespace: testns`** → the `testns_` series prefix.
        - **`_total` suffix** → `add_metric_suffixes` defaults to `true`, and this is a counter.
        - **`external_labels`** → the `verified_by="ollygarden"` label on the series.
        - **`job="telemetrygen"`** → `service.name` is mapped to the `job` label.
        - **value `2`** → the latest cumulative value of the 0/1/2 sequence.
        - **`http.tls.insecure: true`** → plaintext push works against the receiver (TLS is on by default otherwise).
        
        Tear down:
        
        ```bash
        docker rm -f prw-col prw-prom
        docker network rm prw-net
        ```
        
    • redaction
      • advanced.md 2.8 KB
        # `redaction`: advanced use-cases
        
        ## Allowlist mode as a strict schema
        
        Set `allow_all_keys: false` and enumerate `allowed_keys` to define exactly which attribute keys may leave the boundary. Any key not on the list — including newly added, unanticipated attributes — is dropped by default. This is the strongest egress guarantee the processor offers.
        
        ```yaml
        processors:
          redaction:
            allow_all_keys: false
            allowed_keys:
              - http.method
              - http.route
              - http.status_code
              - service.version
            ignored_keys:
              - safe.attribute        # bypasses all checks
        ```
        
        ## Hashing for correlatable-but-masked values
        
        By default a masked value becomes a run of asterisks. Set `hash_function` when you need to **correlate** redacted values without revealing them — e.g. confirm two spans carry the same user id without storing the id. Use `hmac-sha256`/`hmac-sha512` with `hmac_key` so the hash is not a plain digest an attacker could rainbow-table.
        
        ```yaml
        processors:
          redaction:
            allow_all_keys: true
            blocked_values:
              - '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'   # email
            hash_function: hmac-sha256
            hmac_key: ${env:REDACTION_HMAC_KEY}
        ```
        
        ## URL and DB query sanitization
        
        URL sanitization strips high-cardinality path segments (UUIDs, timestamps, numeric IDs) and query content from URL-valued attributes, preserving routing structure for grouping. DB sanitization rewrites query/command text (and optionally span names) so literals don't leak; it only fires on spans carrying `db.system`/`db.system.name` with `CLIENT` or `SERVER` span kind.
        
        ```yaml
        processors:
          redaction:
            allow_all_keys: true
            url_sanitizer:
              enabled: true
              attributes: [url.full, http.url]
              sanitize_span_name: true
            db_sanitizer:
              sanitize_span_name: true
              sql:
                enabled: true
                attributes: [db.statement, db.query.text]
              redis:
                enabled: true
                attributes: [db.statement]
        ```
        
        ## `blocked_key_patterns` vs `blocked_values`
        
        - `blocked_key_patterns` matches **key names** (e.g. `.*token.*`); when a key matches, its value is masked but the key is kept.
        - `blocked_values` matches **value content** (e.g. a card-number regex) regardless of the key name.
        - To remove a key entirely rather than mask its value, leave it off `allowed_keys` (with key filtering enabled) instead of using `blocked_key_patterns`.
        
        ## The audit summary
        
        When `summary` is `debug` or `info`, the processor records what it did as attributes on each record (zero-count attributes are omitted); the default (empty) and `silent` emit nothing. See the [audit attributes table](configuration.md#audit-attributes) for the full list of `redaction.*` (and `redaction.body.*`) names and their verbosity levels. Use `summary: info` for counts in production; reserve `summary: debug` (which records key names) for short-lived investigation.
        
      • configuration.md 4.5 KB
        # `redaction`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          redaction:
            allow_all_keys: false
            allowed_keys:
              - http.method
              - http.route
              - http.status_code
            blocked_values:
              - '4[0-9]{12}(?:[0-9]{3})?'   # Visa-like card numbers
            summary: info
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [memory_limiter, redaction]
              exporters: [otlp]
        ```
        
        ## Configuration reference
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `allow_all_keys` | bool | `false` | When `true`, the `allowed_keys` list is disabled and every key is kept. `blocked_values`/`blocked_key_patterns` still apply. |
        | `allowed_keys` | []string | `[]` | Keys to retain. **Fails closed** — an empty list removes *all* attributes. Any key not listed is deleted. |
        | `ignored_keys` | []string | `[]` | Keys that bypass all redaction checks (never removed, never masked). |
        | `ignored_key_patterns` | []string | `[]` | Regex patterns for keys to bypass all redaction. |
        | `blocked_key_patterns` | []string | `[]` | Regex patterns; matching keys have their **values masked** (the key is kept). |
        | `blocked_values` | []string | `[]` | Regex patterns matched against attribute **values**; matches are masked or hashed. |
        | `allowed_values` | []string | `[]` | Regex patterns for values considered safe. **Takes precedence** over `blocked_values` when both match. |
        | `redact_all_types` | bool | `false` | When `true`, non-string values are checked via their `AsString()` form against `blocked_values`. When `false`, only string values are checked. |
        | `hash_function` | string | `""` | Replace masked values with a hash instead of asterisks. One of `md5`, `sha1`, `sha3`, `hmac-sha256`, `hmac-sha512`. Empty = asterisk masking. |
        | `hmac_key` | string | `""` | Secret key for `hmac-sha256` / `hmac-sha512`. Required and length-validated: ≥ 32 bytes for `hmac-sha256`, ≥ 64 bytes for `hmac-sha512`. Supports env expansion (`${env:REDACTION_HMAC_KEY}`). |
        | `summary` | string | `""` | Audit verbosity. Empty (the default) emits **no** audit attributes, same as `silent`. `debug` = key names + counts, `info` = counts only, `silent` = none. |
        | `url_sanitizer.enabled` | bool | `false` | Enable URL sanitization. |
        | `url_sanitizer.attributes` | []string | `[]` | Attribute keys whose URL values are sanitized. |
        | `url_sanitizer.sanitize_span_name` | bool | `true` | Sanitize span names containing `/`. |
        | `db_sanitizer.sanitize_span_name` | bool | `true` | Sanitize database query span names. |
        | `db_sanitizer.<engine>.enabled` | bool | `false` | Enable per-engine query sanitization. `<engine>` is one of `sql`, `redis`, `valkey`, `memcached`, `mongo`, `opensearch`, `es`. |
        | `db_sanitizer.<engine>.attributes` | []string | `[]` | Attributes holding the query/command text for that engine. |
        
        ## Processing order and precedence
        
        Attributes are evaluated in a fixed order:
        
        1. **Ignored keys** (`ignored_keys`, `ignored_key_patterns`) — always pass through untouched.
        2. **Key allow-listing** — with `allowed_keys` set (and `allow_all_keys: false`), keys not on the list are deleted.
        3. **Value matching** — surviving attributes have `blocked_values`/`blocked_key_patterns` applied. If a value matches both `blocked_values` and `allowed_values`, **`allowed_values` wins** and the value is kept.
        4. **Mask or hash** — marked values become asterisks, or a hash when `hash_function` is set.
        
        Key allow-listing is fail-closed; value matching is not. Relying only on `blocked_values` (with `allow_all_keys: true`) is a much weaker guarantee — value regexes can miss formats they weren't written for.
        
        ## Audit attributes
        
        When `summary` is `debug` or `info`, the processor records what it did as attributes on each record (zero-count attributes are omitted). The default (empty) and `silent` emit nothing:
        
        | Attribute | Verbosity | Description |
        |-----------|-----------|-------------|
        | `redaction.redacted.keys` | debug | Names of keys removed by allow-listing |
        | `redaction.redacted.count` | info | Count of removed keys |
        | `redaction.masked.keys` | debug | Names of keys whose values were masked |
        | `redaction.masked.count` | info | Count of masked values |
        | `redaction.allowed.keys` | debug | Names of keys that passed through |
        | `redaction.allowed.count` | info | Count of allowed attributes |
        | `redaction.ignored.count` | info | Count of ignored attributes |
        
        For map-formatted log bodies, the same metrics are recorded under a `redaction.body.*` prefix. Key allow-listing and value masking apply to the body too.
        
      • quirks.md 2.7 KB
        # `redaction`: known quirks
        
        ## `allowed_keys` fails closed — the footgun
        
        With `allow_all_keys: false` (the default) and an empty `allowed_keys`, **every** attribute is dropped — allow-list semantics, not block-list. If attributes vanish unexpectedly, this is almost always why. Either enumerate the keys you want to keep in `allowed_keys`, or set `allow_all_keys: true` to disable key filtering and rely on value masking alone.
        
        ## Evaluation order
        
        Attributes are evaluated in a fixed order:
        
        1. **Ignored keys** (`ignored_keys`, `ignored_key_patterns`) — always pass through untouched.
        2. **Key allow-listing** — with `allowed_keys` set (and `allow_all_keys: false`), keys not on the list are deleted.
        3. **Value matching** — surviving attributes have `blocked_values`/`blocked_key_patterns` applied. If a value matches both `blocked_values` and `allowed_values`, **`allowed_values` wins** and the value is kept.
        4. **Mask or hash** — marked values become asterisks, or a hash when `hash_function` is set.
        
        Key allow-listing is fail-closed; value matching is not. Relying only on `blocked_values` (with `allow_all_keys: true`) is a much weaker guarantee — value regexes can miss formats they weren't written for. See [Configuration](configuration.md#processing-order-and-precedence) for the full precedence detail.
        
        ## Masking vs. removal
        
        `blocked_key_patterns` masks the **value** of a matching key but keeps the key itself. To drop a key entirely, leave it off `allowed_keys` with key filtering enabled. `blocked_values` masks values, never removes keys.
        
        ## Only string values by default
        
        `blocked_values` matches only string values unless `redact_all_types: true` is set, which checks non-string values via their `AsString()` form. Secrets stored in numeric or boolean values slip through otherwise.
        
        ## `summary: debug` writes key names into telemetry
        
        `debug` verbosity records the redacted/masked/allowed **key names** as attributes. If a key name is itself sensitive, keep `summary: info` (counts only) in production and reserve `debug` for short-lived investigation.
        
        ## DB sanitizer firing conditions
        
        DB sanitization only fires on spans carrying `db.system`/`db.system.name` with `CLIENT` or `SERVER` span kind. Query text on spans without those attributes — or with a different span kind — is not sanitized. Enable per-engine sanitization explicitly (`db_sanitizer.<engine>.enabled: true`) and point it at the attributes holding the query text.
        
        ## Per-signal stability
        
        Traces are Beta; logs and metrics are Alpha. The URL sanitizer and DB sanitizer are newer surface area. Verify masking against representative log/metric data before relying on the processor as your only PII control on those signals.
        
      • README.md 3.2 KB
        # `redaction` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `redaction` |
        | Signals | traces, logs, metrics |
        | Stability | Beta (traces); Alpha (logs, metrics) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/redactionprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/redactionprocessor> |
        
        The trace path is Beta (production viable, breaking changes rare). The log and metric paths are Alpha — configuration and behavior may change between releases. The URL sanitizer and DB sanitizer are newer surface area; verify masking against representative data before relying on this processor as your only PII control.
        
        ## Description
        
        An allow/block-list redaction processor that enforces a data-governance policy on the attributes flowing through a pipeline. It masks or removes sensitive attribute keys and values across span, log, and metric data points, and can additionally sanitize URLs and database query strings. Two mechanisms compose: allow-listing keys (any key not on `allowed_keys` is deleted, failing closed) and masking values (values matching `blocked_values`, or whose key matches `blocked_key_patterns`, are masked with asterisks or replaced by a hash). On top of attribute redaction it can sanitize URLs (strip high-cardinality path segments and query content) and database queries (SQL, Redis, Valkey, Memcached, MongoDB, OpenSearch, Elasticsearch). It **modifies in place** — it never drops or reroutes telemetry.
        
        ## Main use-cases
        
        Use it when:
        - Only an explicit allow list of attribute keys is permitted to leave a trust boundary (compliance, multi-tenant egress).
        - Known-sensitive values (credit cards, emails, tokens) appear in attribute values and must be masked or hashed before export.
        - URLs or DB query strings carry high-cardinality or sensitive content (IDs, tokens, literals) you want sanitized.
        - You want an audit trail of what was redacted, masked, or allowed through.
        
        Avoid it when:
        - You need general-purpose attribute editing (add/upsert/convert) — use `attributes` or `transform`.
        - You want to drop entire spans/logs/metrics — use `filter` (redaction never drops).
        - Your redaction logic needs arbitrary conditional transformation — OTTL in `transform` is more expressive (but does not fail closed).
        
        ## Related components
        
        - `attributes` — general add/update/delete/hash of attributes (no fail-closed allow list).
        - `transform` — OTTL-based conditional transformation, including `replace_pattern` masking (does not fail closed).
        - `filter` — drops entire records (redaction never drops).
        - `resource` — manipulate resource-level attributes.
        
        ## Details
        
        - [Configuration](configuration.md) — open when you need config keys, defaults, processing order/precedence, or the audit-attribute names.
        - [Verification](verification.md) — open to confirm masking works with a telemetrygen recipe.
        - [Advanced use-cases](advanced.md) — open for allowlist-as-schema, hashing for correlation, URL/DB sanitization, and audit summary usage.
        - [Known quirks](quirks.md) — open when attributes vanish unexpectedly, masking behaves differently than expected, or per-signal stability matters.
        
      • verification.md 1.4 KB
        # `redaction`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `redaction` ships in the `contrib` and `k8s` distributions.
        
        Config (`redaction-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          redaction:
            allow_all_keys: true
            blocked_values:
              - '4[0-9]{12}(?:[0-9]{3})?'   # Visa-like card numbers
            summary: info
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [redaction]
              exporters: [debug]
        ```
        
        Generate spans carrying an attribute value that matches the blocked pattern. The span-level attribute flag is `--telemetry-attributes` (resource-level `--otlp-attributes` would attach to the Resource, not the span); the value must be quoted (`key="value"`):
        
        ```bash
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 \
          --traces 5 --telemetry-attributes 'cc_number="4111111111111111"'
        ```
        
        **What proves it worked:** the `debug` exporter shows the matching value masked (replaced with asterisks, or hashed if `hash_function` is set) while non-matching attributes pass through; with `summary: info` the span carries a `redaction.masked.count` audit attribute (and, because nothing is removed under `allow_all_keys: true`, an `redaction.allowed.count`).
        
    • resource
      • advanced.md 5.1 KB
        # `resource`: advanced use-cases
        
        All actions here operate on **resource** attributes and use the same grammar as the [`attributes`](../attributes/README.md) processor. Actions run top-to-bottom; order matters (see [quirks](quirks.md)).
        
        ## Normalize `service.name`
        
        Force a canonical service name regardless of what the SDK set, or backfill one when it's missing:
        
        ```yaml
        processors:
          resource/canonical-name:
            attributes:
              - key: service.name        # always overwrite
                value: checkout
                action: upsert
          resource/default-name:
            attributes:
              - key: service.name        # only if the SDK left it unset
                value: unknown-service
                action: insert
        ```
        
        ## Set `deployment.environment.name`
        
        Tag all telemetry from a collector instance with its environment (semantic conventions renamed `deployment.environment` → `deployment.environment.name`):
        
        ```yaml
        processors:
          resource:
            attributes:
              - key: deployment.environment.name
                value: production
                action: upsert
              - key: cloud.region
                value: us-east-1
                action: upsert
        ```
        
        ## Drop high-cardinality / sensitive resource keys
        
        Remove resource attributes that inflate cardinality or leak internal data — by exact key or regex:
        
        ```yaml
        processors:
          resource:
            attributes:
              - key: process.command_line     # noisy, often unbounded
                action: delete
              - pattern: ^internal\..*        # bulk delete by key regex
                action: delete
        ```
        
        ## Hash identifying resource attributes
        
        Anonymize identifiers while keeping them usable as a stable join key (SHA-256, deterministic):
        
        ```yaml
        processors:
          resource:
            attributes:
              - key: host.id
                action: hash
              - pattern: ^container\.id$
                action: hash
        ```
        
        ## Rename legacy keys with ordered actions
        
        `extract`/`from_attribute` read; `insert`/`upsert` write; `delete`/`hash` clean up. Read before you delete:
        
        ```yaml
        processors:
          resource:
            attributes:
              # copy legacy key to the semantic-convention key
              - key: k8s.cluster.name
                from_attribute: k8s-cluster
                action: insert
              # parse a region out of the AZ before anything removes it
              - key: cloud.availability_zone
                pattern: ^(?P<cloud_region>[a-z]+-[a-z]+-\d+)[a-z]$
                action: extract
              # only now drop the legacy key
              - key: k8s-cluster
                action: delete
        ```
        
        ## Named instances
        
        Like every component, `resource` supports the `type/name` form so the same type can be configured more than once and selected per pipeline:
        
        ```yaml
        processors:
          resource/prod-tags:
            attributes:
              - key: deployment.environment.name
                value: production
                action: upsert
          resource/strip-internal:
            attributes:
              - pattern: ^internal\..*
                action: delete
        
        service:
          pipelines:
            traces/prod:
              processors: [resource/prod-tags]
            traces/egress:
              processors: [resource/strip-internal]
        ```
        
        ## Combine with `resource_detection`
        
        The canonical pattern: `resource_detection` discovers infrastructure attributes from the environment, then `resource` normalizes, renames, and adds static values. Place `resource_detection` **first**:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [env, system, docker, ec2]
            override: false
          resource:
            attributes:
              - key: deployment.environment.name
                value: production
                action: upsert
              - key: k8s.cluster.name
                from_attribute: k8s-cluster
                action: insert
              - key: k8s-cluster
                action: delete
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [resource_detection, resource]
              exporters: [otlp]
        ```
        
        ## Combine with `k8s_attributes`
        
        Let `k8s_attributes` provide dynamic pod/namespace/node metadata from the k8s API, and use `resource` only for static cluster-level values that the API can't supply. Don't hard-code pod-level attributes in `resource` — they're per-pod and dynamic:
        
        ```yaml
        processors:
          k8s_attributes:
            extract:
              metadata: [k8s.pod.name, k8s.namespace.name, k8s.node.name]
          resource:
            attributes:
              - key: k8s.cluster.name        # static, not pod-specific
                value: prod-us-east
                action: upsert
        
        service:
          pipelines:
            traces:
              processors: [k8s_attributes, resource]
        ```
        
        ## Relationship to `attributes`
        
        `resource` and [`attributes`](../attributes/README.md) share an identical action set (`insert`/`update`/`upsert`/`delete`/`hash`/`extract`/`convert`) and the same `value`/`from_attribute`/`from_context`/`pattern`/`converted_type` fields. The only differences:
        
        - **Scope** — `resource` edits resource attributes; `attributes` edits span/log/datapoint attributes.
        - **Matching** — `attributes` has `include`/`exclude` (which can even match on resource attributes); `resource` has none.
        
        If you need to edit resource attributes *only for matching telemetry*, reach for `transform` (with a `where`/OTTL condition), not `resource`. `attributes` can *match* on resource attributes via its `resources:` matcher, but it only edits span/log/datapoint attributes — never the resource — so it can't do conditional resource edits either.
        
      • configuration.md 6.7 KB
        # `resource`: configuration
        
        The processor operates on **resource attributes only** — the attributes attached to the `Resource` that describes the entity producing the telemetry. It does **not** touch span, log-record, or metric-datapoint attributes (use the [`attributes`](../attributes/README.md) processor for those). Its only field is a required ordered list of `attributes` actions; there is **no `include`/`exclude` matching block** (unlike `attributes`), so every resource flowing through the pipeline is processed.
        
        ## Root configuration
        
        | Field | Type | Default | Required | Description |
        |-------|------|---------|----------|-------------|
        | `attributes` | list | none | Yes | Ordered list of actions to perform on the resource's attributes. At least one is required (an empty list is invalid); applied in the order specified. |
        
        There is no default configuration — the processor must be explicitly configured with at least one action.
        
        ```yaml
        processors:
          resource:
            attributes:
              - key: service.name
                value: my-service
                action: upsert
        ```
        
        ## Action fields
        
        Each entry in `attributes` is one operation. The fields are identical to the `attributes` processor's action fields, but `key`/`from_attribute` refer to **resource** attributes.
        
        | Field | Type | Required | Description |
        |-------|------|----------|-------------|
        | `key` | string | Conditional | Resource attribute key to act on. Required for all actions except `delete`/`hash` when only a `pattern` is given. |
        | `action` | string | Yes | One of `insert`, `update`, `upsert`, `delete`, `hash`, `extract`, `convert` (case-insensitive). |
        | `value` | any | Conditional | Literal value to set (string/int/double/bool). Required for `insert`/`update`/`upsert` unless `from_attribute`, `from_context`, or `default_value` is used. Mutually exclusive with `from_attribute`/`from_context`. |
        | `from_attribute` | string | Conditional | Copy the value from another **resource** attribute. Mutually exclusive with `value` and `from_context`. |
        | `from_context` | string | Conditional | Pull the value from request context. Mutually exclusive with `value` and `from_attribute`. See [Context values](#context-values). |
        | `default_value` | any | No | Fallback used when the chosen source yields nothing — a missing `from_attribute`/`from_context`, or a `value` that resolved to empty from an unset `${env:…}` expansion (v0.152.0). **Honored only by `insert`/`update`/`upsert`** (silently ignored on other actions). It counts as a value source, so an action with only `default_value` is valid; it prevents the action from being skipped. |
        | `pattern` | string | Conditional | Regex (Go syntax). For `delete`/`hash`, matches attribute **keys** to act on. For `extract`, the regex applied to the value (must use named capture groups). |
        | `converted_type` | string | Conditional | Target type for `convert`: `int`, `double`, or `string`. Required for `convert`. |
        
        Only **one** of `value`, `from_attribute`, or `from_context` may be set per action; setting more than one fails validation.
        
        ### Action types
        
        | Action | Requires | Behavior |
        |--------|----------|----------|
        | `insert` | `key` + one of `value`/`from_attribute`/`from_context` | Adds the resource attribute only if the key does **not** already exist. No-op if the key exists or the source is missing. |
        | `update` | `key` + one of `value`/`from_attribute`/`from_context` | Replaces the value only if the key **already** exists. No-op if the key is absent or the source is missing. |
        | `upsert` | `key` + one of `value`/`from_attribute`/`from_context` | Insert if absent, update if present (insert + update). No-op only if the source value is missing. The most common action for static configuration. |
        | `delete` | `key` and/or `pattern` | Removes the named key and/or every resource key matching `pattern`. |
        | `hash` | `key` and/or `pattern` | Replaces the value(s) of the named key and/or keys matching `pattern` with their SHA-256 hash (lowercase hex string). |
        | `extract` | `key` + `pattern` (named groups) | Applies the regex to the **string** value of `key` and creates one resource attribute per named capture group. Source value is left unchanged. Only acts on string values; overwrites existing attributes when group names collide. All groups must be named (`(?P<name>…)`). |
        | `convert` | `key` + `converted_type` | Converts the existing value of `key` to `int`/`double`/`string`. No-op if the key is absent; on a failed conversion the original value is kept. |
        
        Example covering several actions, all scoped to the resource:
        
        ```yaml
        processors:
          resource:
            attributes:
              - key: deployment.environment.name   # always set
                value: production
                action: upsert
              - key: k8s.cluster.name              # rename legacy key
                from_attribute: k8s-cluster
                action: insert
              - key: k8s-cluster                    # drop the old key
                action: delete
              - key: host.id                        # anonymize identifier
                action: hash
              - pattern: ^internal\..*              # bulk delete by key regex
                action: delete
              - key: service.port                   # coerce type
                action: convert
                converted_type: int
        ```
        
        ## No include / exclude matching
        
        Unlike the [`attributes`](../attributes/README.md) processor, `resource` has **no `include`/`exclude` block**. There is no way to scope its actions to a subset of resources by service, attribute, or any other matcher — every resource that passes through the pipeline is processed. To apply resource-attribute edits only to matching telemetry, use `transform` with an OTTL condition. The `attributes` processor can match on resource attributes, but its actions still edit only span/log/datapoint attributes, not the resource.
        
        ## Context values
        
        `from_context` pulls a value from request context into a resource attribute. Three source kinds:
        
        - **`client.address`** — the client IP (plain key, no prefix). Only available for network receivers (OTLP, Jaeger, …), not file/push ingestion.
        - **`metadata.*`** — gRPC metadata / HTTP headers, e.g. `metadata.x-trace-id`. Requires the receiver to set `include_metadata: true`. Multiple values are joined with `;`.
        - **`auth.*`** — data from a server authenticator extension, e.g. `auth.username`, `auth.tenant_id`. Requires an authenticator on the receiver; available keys depend on the authenticator. `auth.` and `metadata.` are separate namespaces — there is no fallback between them.
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                include_metadata: true   # required for metadata.* keys
        
        processors:
          resource/context:
            attributes:
              - key: client.ip
                from_context: client.address
                action: insert
              - key: tenant.id
                from_context: auth.tenant_id
                action: insert
        ```
        
      • quirks.md 4.9 KB
        # `resource`: known quirks
        
        ## Operates only on resource attributes
        
        The single most common confusion: `resource` touches **only resource attributes** — the attributes on the `Resource` that describes the producing entity. It does **not** see span, log-record, or metric-datapoint attributes. A config like this silently does nothing useful, because `http.status_code` is a span attribute, not a resource attribute:
        
        ```yaml
        # WRONG: http.status_code lives on the span, not the resource
        resource:
          attributes:
            - key: http.status_code
              action: delete
        ```
        
        For span/log/datapoint attributes use the [`attributes`](../attributes/README.md) processor — it has the exact same action grammar. `resource` is the resource-scoped twin of `attributes`.
        
        ## No include / exclude matching
        
        `resource` has **no `include`/`exclude` block**. Every resource flowing through the pipeline is processed — there's no way to scope its actions to a subset of resources. If you need conditional application (e.g. only rename a key for one service), use `transform` with an OTTL condition. The `attributes` processor can match on resource attributes, but it cannot edit them; its actions still target span/log/datapoint attributes. Attempting to add an `include:` key to a `resource` config is a configuration error.
        
        ## Changes are wholesale across the batch
        
        A `Resource` is shared by every span / log record / metric data point grouped under it in a batch. An action on a resource attribute therefore affects **all** of those records at once — there's no per-record granularity. In the `debug` exporter the attribute appears once, in the **Resource attributes** block, not repeated per span. This is what makes `resource` cheap (one edit per resource, not per record) but also means you can't vary the value by record.
        
        ## Action order matters
        
        Actions run top-to-bottom and later actions see the effect of earlier ones. The classic mistake is deleting a key before reading it:
        
        ```yaml
        # WRONG: source is gone before extract runs
        - key: cloud.availability_zone
          action: delete
        - key: cloud.availability_zone
          pattern: ^(?P<cloud_region>.+)[a-z]$
          action: extract   # no-op, key already deleted
        
        # RIGHT: read first, clean up last
        - key: cloud.availability_zone
          pattern: ^(?P<cloud_region>.+)[a-z]$
          action: extract
        - key: cloud.availability_zone
          action: delete
        ```
        
        Rule of thumb: order as `extract`/read → `insert`/`update`/`upsert`/write → `delete`/`hash`/cleanup.
        
        ## `delete` and `hash` semantics
        
        - `delete` and `hash` accept `key` and/or `pattern` (Go regex against attribute **keys**). With both set, the named key plus all pattern matches are affected.
        - `hash` replaces the value with its **SHA-256** lowercase-hex digest. It's deterministic (same input → same output) and irreversible; add salt at instrumentation time if you need stronger anonymization. Hashing has a higher per-attribute CPU cost than the simple write actions.
        - A `pattern` that matches nothing is a silent no-op — not an error.
        
        ## `insert` vs `update` vs `upsert`
        
        - `insert` only writes if the key is **absent** — using it to "change" an existing value silently does nothing.
        - `update` only writes if the key **already exists** — using it to "set" a missing value silently does nothing.
        - `upsert` always writes (the usual choice for guaranteeing a value).
        
        If an attribute "won't change," check you're not using `insert` on an existing key or `update` on a missing one.
        
        ## `extract` only works on string values
        
        `extract` applies its regex to the **string** value of `key`; non-string values are skipped. All capture groups must be **named** (`(?P<name>…)`) — anonymous `()` groups are a config error. Group names become new resource attribute keys, overwriting any existing attribute of the same name.
        
        ## `convert` failures are silent
        
        `convert` coerces a value to `int`/`double`/`string`. A failed conversion (e.g. `"abc"` → `int`) keeps the **original** value rather than erroring; the failure is only visible at debug log level. Make sure source values are actually convertible.
        
        ## One value source per action
        
        Exactly one of `value`, `from_attribute`, or `from_context` may be set per action. Setting more than one fails validation at startup (`error creating AttrProc due to multiple value sources being set`).
        
        ## `from_context` depends on transport
        
        `from_context: metadata.*` requires the receiver to set `include_metadata: true`; `client.address` is only populated for network receivers (OTLP, Jaeger), not file/push ingestion; `auth.*` requires a server authenticator extension. Missing context is a silent no-op — the attribute simply isn't created (unless `default_value` is set).
        
        ## Stability
        
        Beta for traces, metrics, and logs; **Development** for profiles. The Beta signals are production-viable; treat profiles support as experimental and subject to breaking changes. Confirm the current stability in the upstream README before relying on it for a given signal.
        
      • README.md 4.4 KB
        # `resource` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `resource` |
        | Signals | traces (Beta), metrics (Beta), logs (Beta), profiles (Development) |
        | Distributions | core, contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourceprocessor> |
        
        ## Description
        
        Modifies the **resource** attributes of telemetry through an ordered list of `attributes` actions. The resource describes the entity producing the telemetry (a service instance, host, container, …), and its attributes are shared across every span, log record, and metric data point from that resource. Each action names an attribute `key` and one of `insert`, `update`, `upsert`, `delete`, `hash`, `extract`, or `convert`; actions run in the order listed, so later actions see the effect of earlier ones. Values can be literal (`value`), copied from another resource attribute (`from_attribute`), or pulled from request context (`from_context` — client IP, transport metadata, or authenticator data).
        
        It uses the **same action grammar as the [`attributes`](../attributes/README.md) processor**, but scoped to the resource: set `service.name`, add `deployment.environment.name`, rename legacy keys to semantic conventions, or drop noisy resource keys. Unlike `attributes`, it has **no `include`/`exclude` matching** — every resource that flows through the pipeline is processed.
        
        ## Main use-cases
        
        Use it when:
        - You want to set or standardize resource identity — `service.name`, `service.namespace`, `service.instance.id` — across services (`upsert` static values, `insert` defaults).
        - You need to add environment / infrastructure context to all telemetry from a collector (`deployment.environment.name`, `cloud.region`, static tags).
        - You want to rename or migrate legacy resource keys to semantic conventions (`insert` from `from_attribute`, then `delete` the old key).
        - You need to drop high-cardinality or sensitive resource keys, or `hash` identifying resource attributes (`host.id`, container IDs).
        
        Avoid it when:
        - You need to edit **span / log / datapoint** attributes — use [`attributes`](../attributes/README.md) (same action set, telemetry scope).
        - You need `include`/`exclude` scoping — `resource` has none; use [`attributes`](../attributes/README.md) (which supports resource-attribute matching) or `transform`.
        - You want to auto-discover resource attributes from the environment — use [`resource_detection`](../resource_detection/README.md).
        - You want Kubernetes pod/namespace/node metadata — use [`k8s_attributes`](../k8s_attributes/README.md).
        - You need conditional logic, math, or cross-field transforms — use `transform` (OTTL).
        
        ## Related components
        
        - [`attributes`](../attributes/README.md) — the **same `insert`/`update`/`upsert`/`delete`/`hash`/`extract`/`convert` actions**, but applied to span/log/datapoint attributes instead of the resource.
        - [`resource_detection`](../resource_detection/README.md) — auto-detects resource attributes (cloud, host, container, k8s) from the environment; commonly run **before** `resource`, which then normalizes/renames the detected keys.
        - [`k8s_attributes`](../k8s_attributes/README.md) — enriches resources with Kubernetes pod/namespace/node metadata from the k8s API; use it for dynamic pod-level enrichment rather than static `resource` values.
        - `transform` — OTTL-based mutation that can also edit resource attributes (`context: resource`); a superset of `resource`, at the cost of more verbosity.
        
        ## Details
        
        - [Configuration](configuration.md) — the `attributes` action list (every action type and its `key`/`value`/`from_attribute`/`from_context`/`pattern`/`converted_type` fields), scoped to **resource** attributes, and why there is no include/exclude matching.
        - [Verification](verification.md) — telemetrygen recipe that sets a resource attribute and confirms it in the **Resource attributes** block of `debug` output.
        - [Advanced use-cases](advanced.md) — normalizing `service.name`, setting `deployment.environment.name`, dropping high-cardinality keys, hashing identifiers, ordered actions, named instances, and combining with `resource_detection`/`k8s_attributes`.
        - [Known quirks](quirks.md) — operates only on resource attributes (the common `attributes` confusion), no include/exclude, action order, batch-wide effect, `delete`/`hash` semantics, and stability.
        
      • verification.md 2.9 KB
        # `resource`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `resource` ships in the `core`, `contrib`, and `k8s` distributions.
        
        Config (`resource-verify.yaml`) — an `upsert` action that sets (or overwrites) the `deployment.environment.name` **resource** attribute:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          resource:
            attributes:
              - key: deployment.environment.name
                value: prod
                action: upsert
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [resource]
              exporters: [debug]
        ```
        
        Send a known number of traces whose **resource** carries `deployment.environment.name` with a *different* value, so the `upsert` overwrite is observable. telemetrygen sets resource attributes with `--otlp-attributes` (telemetry-level attributes use the separate `--telemetry-attributes` flag). Use `--workers 1` so the `--traces` count (which is **per worker**) is exact:
        
        ```bash
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 \
          --workers 1 --traces 5 --otlp-attributes 'deployment.environment.name="staging"'
        ```
        
        Flags used, all confirmed against the `otel-telemetrygen` skill (`references/flags.md`):
        
        - `--otlp-insecure` — disable transport security (common flag).
        - `--otlp-endpoint` — destination, defaults to `localhost:4317` for gRPC (common flag).
        - `--workers` — concurrent workers; `1` keeps the count exact (common flag).
        - `--traces` — traces **per worker** (trace-specific flag).
        - `--otlp-attributes` — **resource**-level attribute as `key="value"` (string values must be quoted; repeatable). This is the resource counterpart of `--telemetry-attributes`, which sets span/log/datapoint attributes that the `resource` processor does **not** touch.
        
        **What proves it worked:** in the `debug` exporter output, the **Resource attributes** block (not the span attributes) shows `deployment.environment.name: Str(prod)` — the value sent (`staging`) has been overwritten by the `upsert` action. The attribute appears once per resource, shared across all spans in the batch.
        
        Dropping `--otlp-attributes` and sending plain traces exercises the **absent-key path** of `upsert`: `deployment.environment.name: Str(prod)` still appears in the Resource attributes because the key wasn't present and got inserted. To test the `insert` **action** itself, swap `action: upsert` → `action: insert` in the config — `insert` writes only when the key is absent and leaves a pre-existing value untouched; `update` writes only when the key already exists.
        
        You can run the same recipe with `telemetrygen logs ... --logs 5` (or `metrics ... --metrics 5`) and a matching pipeline; `--logs`/`--metrics` are likewise per-worker, so keep `--workers 1`. `--otlp-attributes` sets the resource for every signal type.
        
    • resource_detection
      • advanced.md 3.6 KB
        # `resource_detection` — advanced use-cases
        
        ## Detector ordering — first detector to set an attribute wins
        
        Detectors run in list order, and the **first** detector to set a given attribute keeps it; later detectors do not overwrite a value already produced earlier in the same run. This matters when several detectors can emit the same key (e.g. `cloud.platform`, `host.id`). The upstream-recommended AWS order, most-specific first, is:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [lambda, elastic_beanstalk, eks, ecs, ec2]
        ```
        
        With `detectors: [eks, ec2]`, `cloud.platform` ends up `aws_eks`; reverse them and you would wrongly get `aws_ec2`. Put the most specific platform detector before the more general one. (`override` is a separate axis — it governs detected-vs-incoming, not detector-vs-detector; see [quirks](quirks.md).)
        
        ## Combining `env` with a platform detector
        
        A common pattern: pull deploy-time context from the environment **and** auto-detect the platform.
        
        ```yaml
        processors:
          resource_detection:
            detectors: [env, ec2]
            timeout: 2s
            override: false
        ```
        
        `env` reads `OTEL_RESOURCE_ATTRIBUTES` (e.g. `service.name`, `service.version`, `deployment.environment.name` injected by your deploy tooling); `ec2` adds the infra identity. `override: false` keeps anything the SDKs already set.
        
        ## Routing an attribute to a specific detector
        
        When two detectors can both produce an attribute, disable it on the one you don't want and enable it on the one you do, rather than relying on order:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [system, ec2]
            system:
              resource_attributes:
                host.name: { enabled: true }
                host.id:   { enabled: false }
            ec2:
              resource_attributes:
                host.name: { enabled: false }
                host.id:   { enabled: true }
        ```
        
        Here `host.name` comes from `system` and `host.id` from `ec2`, regardless of list order.
        
        ## Chaining with the `resource` processor
        
        `resource_detection` discovers; [`resource`](../resource/README.md) normalizes. Run detection first, then clean up what it found — rename legacy keys, drop high-cardinality ones, or hash identifiers:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [env, system, ec2]
          resource:
            attributes:
              - key: host.id          # drop a high-cardinality detected key
                action: delete
              - key: deployment.environment.name
                value: production      # force a literal regardless of what env said
                action: upsert
        
        service:
          pipelines:
            traces:
              processors: [resource_detection, resource]
        ```
        
        ## Capturing cloud tags / labels
        
        Several cloud detectors can fold instance tags/labels into resource attributes via a regex allow-list:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [ec2]
            ec2:
              tags:
                - ^team$
                - ^cost-center$
                - ^env.*$
        ```
        
        `gcp` and `nova` use `labels:` with the same regex semantics; `azure` uses `tags:` and prefixes matches as `azure.tags.<name>`. EC2 tag retrieval needs the `ec2:DescribeTags` IAM permission (or `tags_from_imds: true` with instance metadata tags enabled).
        
        ## Periodic refresh
        
        By default detection runs once at startup. Set `refresh_interval` to re-detect for environments where resource attributes change over the Collector's lifetime (cloud instance tags, k8s labels):
        
        ```yaml
        processors:
          resource_detection:
            detectors: [ec2]
            refresh_interval: 5m
        ```
        
        Use this sparingly — see the cardinality and performance caveats in [quirks](quirks.md). The upstream guidance is that a single startup detection suffices for most deployments, and intervals below 1 minute are strongly discouraged.
        
      • configuration.md 8.4 KB
        # `resource_detection` — configuration
        
        ## Top-level keys
        
        | Key | Type | Default | Description |
        |-----|------|---------|-------------|
        | `detectors` | `[]string` | `[env]` | Ordered list of detectors to run. Valid values: `env`, `system`, `docker`, `heroku`, `gcp`, `ec2`, `ecs`, `elastic_beanstalk`, `eks`, `lambda`, `azure`, `aks`, `azureappservice`, `azurecontainerapps`, `consul`, `kubeadm`, `oraclecloud`, `k8s_api`, `k8snode` (deprecated → `k8s_api`), `openshift`, `dynatrace`, `hetzner`, `akamai`, `scaleway`, `upcloud`, `vultr`, `digitalocean`, `nova`, `alibaba_ecs`, `tencent_cvm`, `ibmcloud_vpc`, `ibmcloud_classic`. |
        | `override` | `bool` | `true` | Whether detected attributes overwrite resource attributes already present on incoming telemetry. `true` overwrites; `false` keeps existing values and only adds missing ones. |
        | `refresh_interval` | `duration` | `0` | If `> 0`, re-runs all detectors on this interval. `0` (default) means detect once at startup and cache. |
        | `fail_on_missing_metadata` | `bool` | `false` | For supported network metadata detectors, make an unreachable metadata service a hard error that participates in processor retry instead of producing an empty resource. Added in v0.158.0. |
        | `timeout` | `duration` | `5s` | HTTP client timeout for detectors that call a metadata service. Inherited from the embedded `confighttp.ClientConfig`. |
        | `retry` | object | enabled (see below) | Global exponential-backoff policy applied to every detector attempt, including periodic refreshes. Added in v0.159.0. |
        
        The component embeds the standard `confighttp.ClientConfig`, so other HTTP client knobs (proxy, TLS, headers) are available for the metadata-service detectors; `timeout` is the one you will usually touch.
        
        > Defaults verified against `factory.go` (`createDefaultConfig`) and `config.go` on contrib v0.160.0: `Detectors: [env]`, `Override: true`, `RefreshInterval: 0`, `FailOnMissingMetadata: false`, client `Timeout: 5s`, and retry enabled with the values below.
        
        ## Global retry
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `retry.enabled` | bool | `true` | `false` makes one detector attempt with no retry. |
        | `retry.initial_interval` | duration | `1s` | Delay before the first retry. |
        | `retry.randomization_factor` | float | `0.5` | Jitter applied to retry intervals. |
        | `retry.multiplier` | float | `2` | Backoff growth factor. |
        | `retry.max_interval` | duration | `30s` | Maximum delay between attempts. |
        | `retry.max_elapsed_time` | duration | `0` | Overall retry budget. `0` uses `timeout` as the overall session bound; a positive value becomes the session bound and `timeout` limits each attempt. |
        
        With retry enabled, setting both `timeout: 0` and `retry.max_elapsed_time: 0` is rejected because
        the detector could block startup indefinitely.
        
        ## Per-detector configuration
        
        Each detector has its own config block, keyed by the detector name, squashed into the top level:
        
        ```yaml
        processors:
          resource_detection:
            detectors: [system, ec2]
            system:
              hostname_sources: ["os"]
            ec2:
              tags:
                - ^team$
        ```
        
        ### Selecting which attributes a detector emits — `resource_attributes`
        
        Most detectors expose a `resource_attributes` map that enables/disables individual attributes. This is how you both **opt in** to attributes that are off by default and **route** an attribute to a specific detector when several can produce it:
        
        ```yaml
        resource_detection:
          detectors: [system, ec2]
          system:
            resource_attributes:
              host.name:
                enabled: true
              host.id:
                enabled: false   # let ec2 own host.id instead
          ec2:
            resource_attributes:
              host.name:
                enabled: false
              host.id:
                enabled: true
        ```
        
        The set of attributes and their default-enabled state are per detector — consult the detector's `documentation.md` in the upstream source (e.g. `internal/system/documentation.md`). For the `system` detector, only `host.name` and `os.type` are enabled by default; `host.id`, `host.arch`, `os.description`, the `host.cpu.*` family, `host.ip`/`host.mac`/`host.interface`, and others are opt-in.
        
        ## Detector catalog
        
        Every detector reports `cloud.provider`/`cloud.platform` plus a platform-specific set. The most-used ones:
        
        | Detector | Source | Key attributes |
        |----------|--------|----------------|
        | `env` | `OTEL_RESOURCE_ATTRIBUTES` env var (falls back to deprecated `OTEL_RESOURCE`), `k=v,k=v` format | whatever you put in the variable |
        | `system` | host machine | `host.name`, `os.type` (default); `host.id`, `host.arch`, `host.cpu.*`, `os.description`, … (opt-in). `hostname_sources` (`["dns","os"]` default; also `cname`, `lookup`) controls how `host.name` is resolved |
        | `docker` | Docker daemon (mount the socket) | `host.name`, `os.type`. Use instead of `system` when the Collector runs as a container; **does not work on macOS** |
        | `ec2` | EC2 IMDS | `cloud.*`, `host.id`, `host.name`, `host.type`. Optional `tags` (regex list; needs `ec2:DescribeTags` IAM, or `tags_from_imds: true`). Deprecated per-detector `fail_on_missing_metadata` (use the top-level key), plus `max_attempts`, `max_backoff` |
        | `ecs` | ECS Task Metadata Endpoint (V4/V3) | `cloud.*`, `aws.ecs.*` |
        | `eks` | EC2 IMDS + k8s/EC2 API fallback | `cloud.*`; `k8s.cluster.name` opt-in (needs `EC2:DescribeInstances`). `node_from_env_var` |
        | `lambda` | Lambda runtime env vars | `cloud.*`, `faas.*` |
        | `gcp` | GCP metadata server | `cloud.*`, `host.*`, `k8s.cluster.name`, `faas.*` per platform (GCE/GKE/Cloud Run/Functions/App Engine). Optional `labels` (regex list; needs `roles/compute.viewer`). Since v0.161.0, `host.type` on GKE is fetched via the Compute API (needs `compute.instances.get`; disable `host.type` to skip the call) |
        | `azure` | Azure IMDS | `cloud.*`, `host.*`. Optional `tags` (regex → `azure.tags.<name>`) |
        | `aks` | Azure IMDS | `cloud.*`; `k8s.cluster.name` opt-in |
        | `azureappservice` | Azure App Service environment variables | `azure.app_service.instance.id`, `azure.resource_group.name`, `cloud.*`, `deployment.environment.name`, `service.name` |
        | `azurefunctions` | Azure Functions environment variables (`FUNCTIONS_WORKER_RUNTIME` / `FUNCTIONS_EXTENSION_VERSION`); v0.162.0+ | `azure.resource_group.name`, `cloud.*` (incl. `cloud.resource_id`), `faas.instance`, `service.name`; `deployment.environment.name` opt-in. Flex Consumption plans lack `azure.resource_group.name` / `cloud.resource_id` |
        | `azurecontainerapps` | Azure Container Apps environment variables | `azure.container_app.instance.id`, `cloud.platform`, `cloud.provider`, `service.name` |
        | `k8s_api` | k8s API server | node/cluster attrs; requires `node_from_env_var` (default `K8S_NODE_NAME`) and `nodes` RBAC. `auth_type` (`serviceAccount` default / `none` / `kubeConfig`). `k8snode` is the deprecated alias |
        | `kubeadm` | k8s API (`kubeadm-config` ConfigMap) | `k8s.cluster.name`, `k8s.cluster.uid`. `auth_type` |
        | `openshift` | OpenShift/k8s API | `cloud.*`, `k8s.cluster.name`. `address`, `token`, `tls` |
        | `heroku` | Heroku dyno metadata env vars | `service.name`, `service.version`, `service.instance.id`, `heroku.*` |
        | `dynatrace` | `dt_host_metadata.properties` file | `dt.entity.host`, `host.name`, `dt.smartscape.host` |
        | `consul` | Consul agent | node + exploded `_node_meta` |
        
        Additional metadata-service detectors have detector-specific settings, sometimes including a `labels`/`tags` regex list: `hetzner`, `akamai`, `scaleway`, `upcloud`, `vultr`, `digitalocean`, `nova` (OpenStack), `alibaba_ecs`, `tencent_cvm`, `ibmcloud_vpc` (`protocol: http|https`), `ibmcloud_classic`, `oraclecloud`, `elastic_beanstalk`, `consul`. The per-detector `fail_on_missing_metadata` fields on `upcloud`, `vultr`, `nova`, `alibaba_ecs`, and `tencent_cvm` are deprecated; use the top-level key instead.
        
        For the exact attribute list any detector emits, read its `internal/<detector>/documentation.md` in the upstream source — do not assume.
        
        ## Detector migration gates
        
        - `processor.resourcedetection.elasticbeanstalk.EmitV1DeploymentConventions` (Alpha, off by
          default) adds `deployment.environment.name` and `deployment.id` alongside the legacy attributes.
        - `processor.resourcedetection.elasticbeanstalk.DontEmitV0DeploymentConventions` (Alpha, off by
          default) removes deprecated `deployment.environment` and `service.instance.id`; it is rejected
          unless the emit-v1 gate is also enabled.
        - `processor.resourcedetection.consul.prefixMetaAttributes` (Alpha, off by default) emits Consul
          node metadata as `consul.meta.<key>` instead of unprefixed `<key>`.
        
      • quirks.md 5.2 KB
        # `resource_detection` — known quirks
        
        ## Fatal detector errors can stop startup
        
        Fatal errors from a configured detector **propagate and can prevent the Collector from starting** after the configured retry budget is exhausted. Every detector uses the global `retry` block; retries are enabled by default, with the default session bounded by `timeout: 5s`. For supported network metadata detectors, an unreachable service is fatal when `fail_on_missing_metadata` is enabled; otherwise it produces an empty resource. Only configure detectors for the environment you are in.
        
        Since v0.158.0, the top-level `fail_on_missing_metadata` flag (default `false`) controls supported network metadata detectors: enable it when an unreachable metadata service should be a hard error that participates in processor retry rather than returning an empty resource. The older per-detector fields on `ec2`, `upcloud`, `vultr`, `nova`, `alibaba_ecs`, and `tencent_cvm` are deprecated; migrate to the top-level key. The `oraclecloud` detector uses a fast probe: a failed probe returns an empty resource; if the probe succeeds but the follow-up fetch fails, it errors only when `fail_on_missing_metadata: true` and otherwise returns an empty resource.
        
        ## `override: true` is the default — and it overwrites SDK-set attributes
        
        By default detected attributes **overwrite** resource attributes already on incoming telemetry. If your SDKs already set `service.name`/`host.name` and the Collector then detects different values for its own host, you can silently replace good per-service identity with the Collector's host identity. For Collectors that receive pre-enriched telemetry (gateway/aggregation tier), prefer `override: false`. The `dynatrace` detector documentation explicitly recommends `override: false` for exactly this reason. (Verified on contrib 0.159.0: with a colliding `service.name`, `override: true` took the detected value and `override: false` kept the incoming one.)
        
        ## It describes the Collector's host — not the workload that emitted the telemetry
        
        Every detector reports on the environment **the Collector itself runs in**. The k8s-flavored detectors here (`k8s_api`, `kubeadm`, `openshift`, `eks`/`aks` cluster name) describe the node/cluster the Collector runs on, not the pod that produced each record. For per-record pod/namespace/workload metadata, use [`k8s_attributes`](../k8s_attributes/README.md) instead — the two are complementary, not interchangeable.
        
        ## Detector ordering vs `override` are two different things
        
        - **Order** within `detectors: [...]` decides which *detector* wins when two detectors emit the same attribute (first wins).
        - **`override`** decides whether *detected* values beat values *already on incoming telemetry*.
        
        They are independent; mixing them up leads to surprising precedence. See [advanced](advanced.md) for the recommended AWS ordering.
        
        ## `refresh_interval` multiplies metric cardinality
        
        Changing a resource attribute creates a **new metric time series**. With `refresh_interval` set, every refresh that picks up a changed attribute forks new series — which can sharply raise backend storage cost and query cost. Each refresh also re-runs all detectors, so short intervals add CPU/memory load. Leave it at the default (`0`, detect once) unless attributes genuinely change at runtime; intervals below 1 minute are strongly discouraged.
        
        ## Internal telemetry added in v0.157.0
        
        The processor now emits three enabled-by-default, **Development**-stability metrics:
        
        | Metric | Type / unit | Attributes | Meaning |
        |--------|-------------|------------|---------|
        | `otelcol.resourcedetection.attributes.detected` | asynchronous non-monotonic sum / `{attribute}` | — | Number of attributes in the currently detected resource. |
        | `otelcol.resourcedetection.detector.duration` | histogram / `s` | `detector`, `outcome` | Per-detector runtime, including retry backoff. |
        | `otelcol.resourcedetection.detector.results` | monotonic sum / `{detection}` | `detector`, `outcome`, `error.type` on failure | Detection outcomes by detector; useful for identifying failing metadata lookups. |
        
        The names and attribute sets may still change because all three metrics are Development stability.
        
        ## Docker / container caveats
        
        - Use the **`docker`** detector instead of `system` when the Collector runs as a Docker container, and mount the Docker socket (`/var/run/docker.sock`). Note the official images run as non-root since 0.40.0, so socket access needs group permissions.
        - The `docker` detector **does not work on macOS**.
        - `host.name` from the `system` detector defaults to the FQDN (`hostname_sources: ["dns", "os"]`); inside a container this is often the container ID. Set `hostname_sources: ["os"]` to use the OS-provided hostname only.
        
        ## The `resourcedetection` type is a deprecated alias
        
        The component type was renamed `resourcedetection` → `resource_detection` (with an underscore) in **v0.153.0**. The old `resourcedetection` name still works as a deprecated alias and logs a deprecation warning at startup; new configs should use `resource_detection`. The Go module path and internal package are still `resourcedetectionprocessor`.
        
        ## Stability is per-signal
        
        Beta for traces, metrics, and logs; **Development** for profiles. Treat profiles support as experimental.
        
      • README.md 4.9 KB
        # `resource_detection` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `resource_detection` (renamed from `resourcedetection` in v0.153.0; the old name remains as a deprecated alias) |
        | Signals | traces (Beta), metrics (Beta), logs (Beta), profiles (Development) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourcedetectionprocessor> |
        
        ## Description
        
        Detects **resource** attributes from the environment the Collector runs in — host machine, cloud provider metadata services, container runtimes, Kubernetes, and environment variables — and merges them onto every span, metric data point, and log record passing through the pipeline, in a form that follows the [OpenTelemetry resource semantic conventions](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/resource). You enable detection by listing one or more named **detectors** in `detectors`; each detector contributes the attributes it knows how to find (e.g. `system` → `host.name`/`os.type`, `ec2` → `cloud.provider`/`cloud.region`/`host.id`, `gcp` → `cloud.platform`/`k8s.cluster.name`).
        
        A global `retry` block applies to detector failures. Azure Container Apps (`azurecontainerapps`)
        Azure App Service (`azureappservice`, v0.160.0+), and Azure Functions (`azurefunctions`, v0.162.0+) are available as detectors. Retries are
        enabled by default, with the overall default session bounded by the 5s HTTP timeout.
        
        Detection normally runs **once at startup** and the results are cached. Detectors run in the order listed and, by default (`override: true`), the detected values overwrite any existing resource attributes on incoming telemetry; set `override: false` to keep values already present (the safer choice when telemetry arrives pre-enriched from SDKs or upstream Collectors). Unlike [`resource`](../resource/README.md), it does not take an explicit attribute action list — it *discovers* attributes rather than setting literal values, and is commonly chained **before** `resource` so you can rename or trim what it found.
        
        ## Main use-cases
        
        Use it when:
        - You want host identity on telemetry from a Collector running on a VM or bare metal — `host.name`, `host.id`, `os.type`, `host.arch` (the `system` detector).
        - You run the Collector on a cloud platform and want `cloud.*` / `k8s.cluster.name` / `faas.*` attributes auto-populated (`ec2`, `gcp`, `azure`, `aks`, `eks`, … detectors).
        - You want to pull resource attributes from `OTEL_RESOURCE_ATTRIBUTES` into telemetry that lacks them (the `env` detector — the default).
        - You need infra context attached at the Collector rather than configured into every SDK.
        
        Avoid it when:
        - You want to set **literal/static** resource values (`service.name`, `deployment.environment.name`) — use [`resource`](../resource/README.md); detection discovers from the environment, it does not invent values.
        - You need per-pod Kubernetes metadata keyed off each record's source pod — use [`k8s_attributes`](../k8s_attributes/README.md); the `k8s_api`/`kubeadm`/`openshift` detectors here describe the **node/cluster the Collector runs on**, not the workload that emitted the telemetry.
        - You need to edit span/log/datapoint attributes — use [`attributes`](../attributes/README.md) or `transform`.
        
        ## Related components
        
        - [`resource`](../resource/README.md) — sets/renames resource attributes from an explicit action list. Run `resource_detection` first to discover, then `resource` to normalize or drop what was found.
        - [`k8s_attributes`](../k8s_attributes/README.md) — per-record pod/namespace/workload enrichment from the k8s API. `resource_detection`'s k8s detectors describe the host/node the Collector itself runs on; the two are complementary.
        - [`attributes`](../attributes/README.md) — edits span/log/datapoint attributes (telemetry scope) rather than the resource.
        
        ## Details
        
        - [Configuration](configuration.md) — top-level keys (`detectors`, `override`, `refresh_interval`, `retry`, HTTP client settings), the per-detector `resource_attributes` enable/disable mechanism, and the full detector catalog with the attributes each populates.
        - [Verification](verification.md) — telemetrygen recipe using the `env` and `system` detectors, confirming detected attributes in the **Resource attributes** block of `debug` output.
        - [Advanced use-cases](advanced.md) — detector ordering, combining detectors, `override: false` for pre-enriched telemetry, chaining with `resource`, cloud tag/label capture, and `refresh_interval`.
        - [Known quirks](quirks.md) — a failed detector stops the Collector from starting, the `override` footgun, "describes the Collector's host, not the workload", `refresh_interval` cardinality cost, v0.157.0 internal telemetry, the `resourcedetection` deprecated alias, and Docker/cloud environment caveats.
        
      • verification.md 3 KB
        # `resource_detection` — verification
        
        Confirms that detected attributes appear in the **Resource attributes** block of telemetry. Uses the two detectors that work without a cloud/metadata service: `env` (reads `OTEL_RESOURCE_ATTRIBUTES` from the Collector's environment) and `system` (reads the host machine).
        
        Verified on `otel/opentelemetry-collector-contrib:0.159.0` with telemetrygen v0.159.0
        (2026-08-27). This `env` + `system` recipe's schema is unchanged through v0.160.0; it does not
        exercise `azureappservice`.
        
        See the `otel-collector` SKILL.md **Verification harness** note: this config is a minimal repro and omits `memory_limiter` and other production scaffolding on purpose.
        
        ## Config (`rdcol.yaml`)
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        
        processors:
          resource_detection:
            detectors: [env, system]
            override: true
            system:
              hostname_sources: ["os"]
        
        exporters:
          debug:
            verbosity: detailed
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [resource_detection]
              exporters: [debug]
        ```
        
        ## Run
        
        The `env` detector reads the **Collector's** environment, so set `OTEL_RESOURCE_ATTRIBUTES` on the container (not on telemetrygen):
        
        ```bash
        docker run --rm --name rdcol -p 4317:4317 \
          -e OTEL_RESOURCE_ATTRIBUTES='deployment.environment.name=prod,my.detector.test=hello' \
          -v "$PWD/rdcol.yaml:/etc/otelcol-contrib/config.yaml" \
          otel/opentelemetry-collector-contrib:0.159.0
        
        # in another shell — telemetrygen emits 2 spans per trace
        go run github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen@v0.159.0 \
          traces --otlp-insecure --otlp-endpoint localhost:4317 --traces 5 --workers 1
        ```
        
        ## Expected output
        
        The `debug` exporter prints a **Resource attributes** block carrying both the `env`-detected and `system`-detected attributes:
        
        ```
        Resource attributes:
             -> service.name: Str(telemetrygen)
             -> deployment.environment.name: Str(prod)   # from env detector
             -> my.detector.test: Str(hello)             # from env detector
             -> host.name: Str(<container hostname>)      # from system detector
             -> os.type: Str(linux)                       # from system detector
        ```
        
        10 spans total reach the exporter (5 traces × 2 spans). Tear down with `docker rm -f rdcol`.
        
        ## Verified result
        
        The `env` detector populated `deployment.environment.name=prod` and `my.detector.test=hello` from `OTEL_RESOURCE_ATTRIBUTES`; the `system` detector populated `host.name` (the container ID, via `hostname_sources: ["os"]`) and `os.type=linux`. All 10 spans carried the merged resource, and telemetrygen's own `service.name=telemetrygen` was kept (neither detector produces it).
        
        `override` was confirmed separately by setting `OTEL_RESOURCE_ATTRIBUTES=service.name=fromenv` (which collides with telemetrygen's `service.name=telemetrygen`): with `override: true` the resource showed `service.name=fromenv` (detector wins); with `override: false` it showed `service.name=telemetrygen` (incoming value preserved).
        
    • routing
      • advanced.md 5.5 KB
        # `routing`: advanced use-cases
        
        Examples below use context-qualified paths (`resource.attributes[...]`, `span.…`, `log.…`), the recommended style since v0.156.0 — the `context` field is inferred and omitted. Bare paths with an explicit `context` still work for backward compatibility.
        
        ## Named instances for independent dimensions
        
        Under the default `action: move`, each piece of telemetry matches at most one route, so a single `routing` instance can't route on two independent dimensions at once. Use one named instance per dimension and list both as exporters of the input pipeline — each gets an independent copy of the data:
        
        ```yaml
        connectors:
          routing/tenant:
            table:
              - condition: resource.attributes["tenant"] == "acme"
                pipelines: [logs/acme]
              - condition: resource.attributes["tenant"] == "ecorp"
                pipelines: [logs/ecorp]
          routing/region:
            table:
              - condition: resource.attributes["region"] == "us-east"
                pipelines: [logs/east]
              - condition: resource.attributes["region"] == "us-west"
                pipelines: [logs/west]
        
        service:
          pipelines:
            logs/in:
              receivers: [otlp]
              exporters: [routing/tenant, routing/region]   # both evaluate independently
        ```
        
        ## Fan-out to multiple pipelines in one route
        
        To send the same matched telemetry to several pipelines, list them all under one route — telemetry is copied to each:
        
        ```yaml
        table:
          - condition: span.end_time_unix_nano - span.start_time_unix_nano > 5000000000   # > 5s
            pipelines: [traces/slow, traces/all]       # slow traces go to BOTH
        ```
        
        ## Multi-tenant routing on request metadata
        
        Route on HTTP headers or gRPC metadata using the `otelcol.client.metadata` (HTTP/client) and `otelcol.grpc.metadata` (gRPC) paths — valid in every signal context. Metadata values are lists, so index with `[0]`:
        
        ```yaml
        connectors:
          routing:
            default_pipelines: [logs/default]
            table:
              - condition: otelcol.client.metadata["X-Tenant"][0] == "acme"
                pipelines: [logs/acme]
              - condition: otelcol.client.metadata["X-Tenant"][0] == "ecorp"
                pipelines: [logs/ecorp]
        ```
        
        For gRPC traffic use lowercase keys: `otelcol.grpc.metadata["x-tenant"][0]`. The older `request["X-Tenant"]` / `context: request` form still works but is **deprecated as of v0.156.0** (see [quirks](quirks.md)).
        
        ## Severity and environment routing
        
        ```yaml
        # logs: cheap storage for low severity, expensive for errors
        table:
          - condition: log.severity_number < SEVERITY_NUMBER_ERROR
            pipelines: [logs/cheap]
          - condition: log.severity_number >= SEVERITY_NUMBER_ERROR
            pipelines: [logs/important]
        ```
        
        ```yaml
        # traces: production to one backend, everything else to the default
        connectors:
          routing:
            default_pipelines: [traces/dev]
            table:
              - condition: resource.attributes["deployment.environment"] == "production"
                pipelines: [traces/prod]
        ```
        
        Order routes from specific to general — the first match wins, so a broad route placed first will shadow a narrower one below it.
        
        ## `action: copy` vs the default `move`
        
        `move` (the default) removes matched data from any further evaluation, so it never also reaches later routes or `default_pipelines`. `copy` leaves matched data available downstream — use it for an "archive everything, then route" pattern where the same data must hit both the archive route and a more specific one:
        
        ```yaml
        table:
          - condition: resource.attributes["archive"] == "true"
            action: copy                       # copied to archive, still evaluated below
            pipelines: [logs/archive]
          - condition: log.severity_number >= SEVERITY_NUMBER_ERROR
            pipelines: [logs/errors]           # default move: errors leave here
        ```
        
        Because `move` is already the default, you rarely set it explicitly; reach for `copy` only when a match must not be terminal.
        
        ## Migrating from `routingprocessor`
        
        The connector replaces the `routingprocessor`, which has since been removed from contrib entirely — the OLD config below no longer loads on current releases. The processor sat inside one pipeline and routed to **exporters** via a `from_attribute` + `value` table; the connector bridges pipelines and routes to **pipelines** via OTTL.
        
        ```yaml
        # OLD (routingprocessor)
        processors:
          routing:
            from_attribute: X-Tenant
            table:
              - value: acme
                exporters: [otlp/acme]
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [routing]
              exporters: [otlp/acme]
        ```
        
        ```yaml
        # NEW (routing connector)
        connectors:
          routing:
            default_pipelines: [logs/default]
            table:
              - condition: otelcol.client.metadata["X-Tenant"][0] == "acme"
                pipelines: [logs/acme]
        service:
          pipelines:
            logs/in:   {receivers: [otlp], exporters: [routing]}
            logs/acme: {receivers: [routing], exporters: [otlp/acme]}
            logs/default: {receivers: [routing], exporters: [otlp/default]}
        ```
        
        ## Migrating from `match_once`
        
        `match_once` was removed in v0.120.0; each item now matches at most one route (under the default `move`). To reproduce the old `match_once: false` (multi-match) behavior:
        
        - **Parallel routers** — split independent dimensions into separate named instances (see above). Each gets its own copy, so an item can match in both. Simplest when no `default_pipelines` is needed.
        - **Enumerate combinations** — for a small fixed set, write one route per combination and list all target pipelines in it (e.g. `condition: resource.attributes["env"] == "prod" and resource.attributes["region"] == "east"` → `pipelines: [logs/prod, logs/east]`). Practical only for a handful of dimensions.
        
        Prefer parallel routers; enumeration grows combinatorially.
        
      • configuration.md 8.4 KB
        # `routing`: configuration
        
        ## Typical config
        
        ```yaml
        connectors:
          routing:
            error_mode: ignore
            default_pipelines: [logs/default]
            table:
              # context inferred from the qualified path — no `context` field needed
              - condition: resource.attributes["tenant"] == "acme"
                pipelines: [logs/acme]
              - condition: log.severity_number >= SEVERITY_NUMBER_ERROR
                pipelines: [logs/errors]
        
        service:
          pipelines:
            logs/in:
              receivers: [otlp]
              exporters: [routing]            # connector as exporter of the input pipeline
            logs/acme:
              receivers: [routing]            # connector as receiver of an output pipeline
              exporters: [otlphttp/acme]
            logs/errors:
              receivers: [routing]
              exporters: [otlphttp/errors]
            logs/default:
              receivers: [routing]
              exporters: [otlphttp/default]
        ```
        
        ## Top-level configuration reference
        
        | Key | Type | Default | Required | Meaning |
        |-----|------|---------|----------|---------|
        | `table` | array of RoutingTableItem | — | **yes** (≥1 route) | The routing table. Routes are evaluated in order; under the default `action: move` each piece of telemetry matches at most one route. |
        | `default_pipelines` | array of pipeline IDs | none | no | Pipelines for telemetry that matches no route. **If unset, unmatched telemetry is dropped.** |
        | `error_mode` | string | `ignore` | no | How OTTL evaluation errors are handled: `propagate`, `ignore`, or `silent` (see below). The `connector.routing.defaultErrorModeIgnore` gate is Beta and enabled by default in v0.157.0; disabling it restores `propagate`. |
        
        ## RoutingTableItem reference
        
        | Key | Type | Default | Required | Meaning |
        |-----|------|---------|----------|---------|
        | `context` | string | inferred from path | no | OTTL context the expression runs in: `resource`, `span`, `metric`, `datapoint`, `log`, `otelcol`, or the deprecated `request`. Usually omit it — the context is inferred from context-qualified paths (see below). If set, it takes precedence over inference and must be valid for the pipeline's signal (see table below). |
        | `statement` | string | — | one of `statement`/`condition` | OTTL statement: `route() where <expr>` to route unchanged, or `<editor> where <expr>` (e.g. `delete_key(resource.attributes, "k") where …`) to mutate matched data in the same pass. **Mutually exclusive with `condition`.** Not allowed for the deprecated `request` context. |
        | `condition` | string | — | one of `statement`/`condition` | OTTL boolean expression (no `route() where`). **Mutually exclusive with `statement`.** Required for the deprecated `request` context. |
        | `action` | string | `move` | no | `move` (default) removes matched data from further route evaluation and `default_pipelines`; `copy` leaves it available to later routes and the default. |
        | `pipelines` | array of pipeline IDs | — | **yes** (≥1) | Output pipelines this route forwards matching telemetry to. Listing several fans the data out to all of them. |
        
        Exactly one of `statement` / `condition` must be set per route — setting both, or neither, fails validation.
        
        ## Context inference (recommended)
        
        Since v0.156.0 the connector infers the `context` from **context-qualified paths** in the condition/statement, so the `context` field is usually unnecessary. Prefix each path with its context and drop the field:
        
        ```yaml
        table:
          - condition: resource.attributes["env"] == "prod"     # -> resource context
            pipelines: [logs/prod]
          - condition: span.attributes["http.method"] == "GET"  # -> span context
            pipelines: [traces/http]
          - condition: log.severity_text == "ERROR"             # -> log context
            pipelines: [logs/errors]
        ```
        
        The inferred (or explicit) context must be compatible with the pipeline signal — e.g. a `span` context only works in a traces pipeline. A condition with no OTTL paths (e.g. the literal `"true"`) can't be inferred, so it still needs an explicit `context`. Bare paths with an explicit `context` (`context: resource` + `attributes["env"]`) remain valid for backward compatibility.
        
        ## Supported contexts by signal
        
        A route's context (inferred or explicit) must be supported by the signal of the pipeline it routes:
        
        | Signal | Supported contexts |
        |--------|--------------------|
        | Traces | `resource`, `span`, `otelcol` (+ deprecated `request`) |
        | Metrics | `resource`, `metric`, `datapoint`, `otelcol` (+ deprecated `request`) |
        | Logs | `resource`, `log`, `otelcol` (+ deprecated `request`) |
        
        `otelcol` (request metadata) is valid in every signal. Coarser contexts are cheaper: `resource` evaluates once per resource bundle, while `span`/`metric`/`log` evaluate per item and `datapoint` per data point. Use the coarsest context that expresses your condition.
        
        ## `statement` vs `condition`
        
        Both express the same boolean test in OTTL; they differ only in syntax and which OTTL functions you can call:
        
        ```yaml
        # condition: a bare boolean expression
        - condition: resource.attributes["env"] == "prod"
          pipelines: [logs/prod]
        
        # statement: <editor> where <expr> — the editor (e.g. delete_key) is the statement's
        #            function; it runs against matched data in the same pass, then the data is
        #            routed. Plain routing without mutation is `route() where <expr>`. You cannot
        #            chain an editor onto route() with `and` — that is invalid OTTL syntax.
        - statement: delete_key(resource.attributes, "internal.debug") where resource.attributes["env"] == "prod"
          pipelines: [logs/prod]
        ```
        
        Use `condition` for a plain match (recommended for clarity); reach for `statement` only when you want to mutate the data in the same pass. Beyond the standard converters, the connector exposes only the `delete_key` and `delete_matching_keys` editors. For the full expression language — paths, converters, editors — see the `otel-ottl` skill.
        
        ## Request metadata routing (`otelcol.*`)
        
        To route on HTTP headers or gRPC metadata, use the `otelcol.client.metadata` (HTTP/client) and `otelcol.grpc.metadata` (gRPC) paths. Metadata values are lists, so index the first element with `[0]`. These paths are valid in every signal context and, unlike the deprecated `request` context, support the full OTTL grammar (`and`/`or`, statements, etc.):
        
        ```yaml
        - condition: otelcol.client.metadata["X-Tenant"][0] == "acme"
          pipelines: [logs/acme]
        - condition: otelcol.grpc.metadata["x-tenant"][0] == "acme"
          pipelines: [logs/acme]
        ```
        
        gRPC metadata keys are lowercased, so use lowercase keys for gRPC traffic. The receiver must propagate request metadata (the `otlp` receiver does).
        
        ### Deprecated `request` context
        
        The `request` context is **deprecated as of v0.156.0** (a warning is logged when it is used) — prefer `otelcol.client.metadata` / `otelcol.grpc.metadata`. It requires `condition` (a `statement` is rejected) and supports only a single simple comparison — `==` or `!=`, no `and`/`or`:
        
        ```yaml
        - context: request
          condition: request["X-Tenant"] == "acme"
          pipelines: [logs/acme]
        ```
        
        ## `error_mode`
        
        Controls what happens when an OTTL expression fails to evaluate (missing attribute, type mismatch, etc.):
        
        | Value | Behavior |
        |-------|----------|
        | `propagate` | The connector returns an error and the **payload is dropped** from the collector. |
        | `ignore` | The error is logged and the payload is sent to `default_pipelines`. |
        | `silent` | Same as `ignore`, but the error is not logged. |
        
        Prefer `ignore` in production so a transient OTTL error doesn't drop data. It is the default in v0.157.0 because `connector.routing.defaultErrorModeIgnore` is Beta and enabled by default. Set `error_mode` explicitly in reusable configs that also target v0.156.0 or earlier, or deployments that disable the gate.
        
        ## Pipeline wiring
        
        `routing` is a connector: it bridges pipelines rather than living inside one. Wire it as the **exporter** of the input pipeline and the **receiver** of each output pipeline (including the default). Pipeline definition order does not matter — only that the connector name appears in both roles:
        
        ```yaml
        service:
          pipelines:
            logs/in:                # input
              receivers: [otlp]
              exporters: [routing]
            logs/acme:              # output
              receivers: [routing]
              exporters: [otlphttp/acme]
            logs/default:           # default fallback output
              receivers: [routing]
              exporters: [otlphttp/default]
        ```
        
        Every pipeline ID referenced in `table[].pipelines` and in `default_pipelines` must exist as a pipeline that lists `routing` as a receiver, otherwise the collector fails to start.
        
      • quirks.md 4.9 KB
        # `routing`: known quirks
        
        ## Unmatched telemetry is dropped without `default_pipelines`
        
        If a piece of telemetry matches no route and `default_pipelines` is not set, it is **silently dropped** — no error, no log. This is the most common cause of "where did my data go?" with this connector. Always configure `default_pipelines` (even just a catch-all archive or a `debug` pipeline) unless dropping unmatched data is genuinely what you want.
        
        ## `error_mode: propagate` drops the payload on OTTL errors
        
        With `error_mode: propagate`, any OTTL evaluation error — a missing attribute, a type mismatch — makes the connector return an error and the **whole payload is dropped** from the collector. In v0.157.0 the default is `ignore`: the `connector.routing.defaultErrorModeIgnore` gate is Beta and enabled by default. `ignore` logs the error and sends the payload to `default_pipelines` instead — so it only actually rescues data when `default_pipelines` is set; without it the errored payload still has nowhere to go. Disabling the gate restores the old `propagate` default. Guard fragile conditions with nil checks (`resource.attributes["k"] != nil and resource.attributes["k"] == "v"`) so a missing key doesn't error in the first place.
        
        ## `action` defaults to `move`, so matched data skips `default_pipelines`
        
        The default `action` is `move`: a matched item is removed from further route evaluation and does **not** also reach `default_pipelines` or later routes. Use `action: copy` when you want matched data to stay available for subsequent routes and the default (e.g. an archive-everything route placed first).
        
        ## `statement` and `condition` are mutually exclusive
        
        Each route needs **exactly one** of `statement` or `condition`. Setting both, or neither, fails config validation. `statement` is the `route() where <expr>` form — or `<editor> where <expr>` (e.g. `delete_key(...) where …`) when you also want to mutate matched data in the same pass (you can't chain an editor onto `route()` with `and`); `condition` is a bare boolean expression. Pick one per route.
        
        ## `request` context is deprecated and has a limited grammar
        
        The `request` context is **deprecated as of v0.156.0** (a warning is logged when it is used) — prefer the `otelcol.client.metadata["key"][0]` (HTTP) / `otelcol.grpc.metadata["key"][0]` (gRPC) paths, which are valid in every signal context and support the full OTTL grammar. The legacy `request` context supports only a single `==` or `!=` comparison — no `and`/`or`, and a `statement` is rejected (it must be `condition`). gRPC metadata keys are lowercased, so use lowercase keys (`request["x-tenant"]` / `otelcol.grpc.metadata["x-tenant"][0]`) for gRPC traffic. The receiver must actually propagate request metadata (the `otlp` receiver does; file-based receivers don't).
        
        ## Item-level contexts can split a Resource bundle across pipelines
        
        `span`, `metric`, `datapoint`, and `log` contexts evaluate per item, so a single incoming ResourceSpans/ResourceMetrics/ResourceLogs bundle can be torn apart — some items to one pipeline, others to another (or to the default). This is usually intended, but it means downstream pipelines may receive partial resource bundles. Use `resource` context when you want whole bundles kept together.
        
        ## Validation errors → fixes
        
        | Error | Cause | Fix |
        |-------|-------|-----|
        | `the routing table is empty` | `table` missing or empty | Add at least one route. |
        | `no condition or statement provided` | route has neither | Add one of `statement`/`condition`. |
        | `both condition and statement provided` | route has both | Remove one. |
        | `no pipelines defined` | route missing `pipelines` | Add at least one pipeline ID. |
        | `invalid Action string: <value>` (inside a `cannot unmarshal the configuration` error) | `action` set to something other than `move`/`copy` | Use `move` or `copy` (or omit for the `move` default). |
        | `"request" context requires a 'condition'` | `request` route used a `statement` | Use `condition` for `request` context (or migrate to `otelcol.*` paths). |
        | `invalid context: <name>` | unsupported/typo context | Use `resource`, `span`, `metric`, `datapoint`, `log`, `otelcol`, or the deprecated `request` (and one valid for the signal). |
        
        ## `match_once` was removed in v0.120.0
        
        `match_once` (deprecated v0.116.0, removed v0.120.0) is gone — each item now matches **at most one route**. Configs using `match_once: false` no longer load. To fan out, list multiple pipelines in one route; to route on independent dimensions, use parallel named instances (see [advanced](advanced.md#migrating-from-match_once)).
        
        ## Stability caveats
        
        All three signal pairs (traces, metrics, logs) are **Alpha**. Config surface and behavior can shift between releases — confirm against the upstream README for your exact collector version. The connector replaces the `routingprocessor`, which has been removed from contrib (v0.156.0 rejects it as an unknown type); existing configs must migrate to the connector form.
        
      • README.md 3.9 KB
        # `routing` connector
        
        | | |
        |-|-|
        | Kind | connector |
        | Type | `routing` |
        | Signals | traces→traces (Alpha), metrics→metrics (Alpha), logs→logs (Alpha) — same-signal routing, no transformation |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/routingconnector> |
        
        ## Description
        
        Routes telemetry to different pipelines based on OTTL conditions, keeping the **same signal type** (traces→traces, metrics→metrics, logs→logs) — it forwards data unchanged, it does not transform it. As a connector it is wired as the **exporter** of an input pipeline and the **receiver** of one or more output pipelines; the input pipeline hands its data to `routing`, which then dispatches it to the matching output pipelines.
        
        The decision is driven by an ordered `table` of routes, each an OTTL `condition` (or a `route() where …` `statement`) evaluated in a `context`. Since v0.156.0 the context is **inferred** from context-qualified paths (`resource.attributes[...]`, `span.…`, `log.…`, `metric.…`, `datapoint.…`, `otelcol.…`), so the `context` field is usually omitted; the deprecated `request` context is the exception. Routes are evaluated **in order** and, under the default `action: move`, each piece of telemetry matches **at most one route** — to fan out to several pipelines, list them all under that route's `pipelines`, or use `action: copy` to keep matched data available to later routes. Telemetry that matches no route goes to `default_pipelines`, or is **dropped** if none is set. This replaces the `routingprocessor`, which has since been removed from contrib. The OTTL expression language itself lives in the `otel-ottl` skill; this page documents the connector's config surface.
        
        ## Main use-cases
        
        Use when:
        - You need to send telemetry to different backends/pipelines by attribute (tenant, region, environment, severity, service).
        - You want a fan-out: the same data to multiple pipelines based on one condition.
        - You need request-level routing from HTTP headers / gRPC metadata (multi-tenant isolation before parsing telemetry).
        - You want to split high-priority from low-priority data (e.g. errors to expensive storage, the rest to cheap storage).
        
        Avoid when:
        - You only need to **drop** telemetry — use `filter`.
        - You need to **modify** telemetry — use `transform`.
        - You need to change signal type (e.g. spans→metrics) — that is what transforming connectors like `spanmetrics` are for.
        
        ## Related components
        
        - `filter` — drops telemetry via OTTL; use it when the goal is to remove data, not redirect it.
        - `transform` — mutates telemetry via OTTL; use it to rewrite data rather than route it.
        - `otel-ottl` skill — the `condition`/`statement` expression language, contexts, and functions used in the routing `table`.
        - `routingprocessor` — the removed processor this connector replaces (see [Known quirks](quirks.md) for migration).
        
        ## Details
        
        - [Configuration](configuration.md) — full config tables for top-level keys and `table` items, context inference, supported contexts per signal, `otelcol.*` request-metadata routing, statement-vs-condition rules, `error_mode` semantics, and the connector pipeline wiring.
        - [Verification](verification.md) — telemetrygen recipe that splits telemetry across two pipelines, each writing to its own `file` exporter, proving routing and the default fallback.
        - [Advanced use-cases](advanced.md) — named instances, fan-out, multi-tenant `otelcol.*` routing, severity/environment routing, `action: copy` vs the default `move`, and migrating from `routingprocessor` / the removed `match_once`.
        - [Known quirks](quirks.md) — dropped unmatched data, `error_mode` data loss, `move`-vs-`copy` defaults, mutual exclusivity, the deprecated `request` context, split Resource bundles, a validation-error→fix table, and stability caveats.
        
      • verification.md 3.6 KB
        # `routing`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `routing` ships in the `contrib` and `k8s` distributions, so a stock contrib collector can run this.
        
        The idea: send logs carrying a resource attribute `tenant`, route the ones where `tenant == "acme"` to one pipeline and let everything else fall through to `default_pipelines`. Each output pipeline writes to its **own** `file` exporter, so the split is visible as two distinct files. Run telemetrygen twice — once with the matching attribute, once without — and check which file each batch lands in.
        
        Config (`routing-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        connectors:
          routing:
            error_mode: ignore
            default_pipelines: [logs/default]
            table:
              - context: resource
                condition: attributes["tenant"] == "acme"
                pipelines: [logs/match]
        exporters:
          file/match:
            path: /tmp/routed-match.json
          file/default:
            path: /tmp/routed-default.json
        service:
          pipelines:
            logs/in:
              receivers: [otlp]
              exporters: [routing]
            logs/match:
              receivers: [routing]
              exporters: [file/match]
            logs/default:
              receivers: [routing]
              exporters: [file/default]
        ```
        
        Send a matching batch and a non-matching batch — see the `otel-telemetrygen` skill:
        
        ```bash
        # matches the route -> /tmp/routed-match.json
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 3 --workers 1 --otlp-attributes 'tenant="acme"'
        
        # no matching attribute -> falls through to /tmp/routed-default.json
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 2 --workers 1 --otlp-attributes 'tenant="other"'
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure` (bool), `--otlp-endpoint` (string), `--logs` (int, **per worker**; ignored if `--duration` is set, so no `--duration` here, and `--workers 1` makes the count exact), and `--otlp-attributes` (`key="value"`, **resource-level** — the right scope, since the route uses `context: resource` and reads `attributes["tenant"]` off the resource). No flag here invents a value.
        
        **What proves it worked:** `/tmp/routed-match.json` holds the 3 records sent with `tenant="acme"` and `/tmp/routed-default.json` holds the 2 records sent with `tenant="other"`. telemetrygen sends nothing on a `tenant` attribute by default, so the split is unambiguous — records only reach `file/match` when the route condition matched, and only reach `file/default` via the unmatched fallback. The `file` exporter writes one newline-delimited JSON object per export, so count the log records inside each file (the resource-level `tenant` attribute appears once per batch, not once per record):
        
        ```bash
        # each batch is one JSON line carrying its logRecords; count records, not the resource attr
        for f in /tmp/routed-match.json /tmp/routed-default.json; do
          echo "$f: $(grep -o '"timeUnixNano"' "$f" | wc -l | tr -d ' ') records"
        done
        # => routed-match.json: 3 records, routed-default.json: 2 records
        ```
        
        > Verified end-to-end on `otel/opentelemetry-collector-contrib:0.152.0`: the `tenant="acme"` batch landed only in `routed-match.json` (3 records) and the `tenant="other"` batch only in `routed-default.json` (2 records). Exact JSON field rendering can shift between collector versions; what matters is the matching records land in `routed-match.json` and the rest in `routed-default.json` — and that removing `default_pipelines` would make the non-matching batch vanish entirely (dropped), the behavior [quirks](quirks.md) warns about.
        
    • tail_sampling
      • advanced.md 4.2 KB
        # `tail_sampling`: advanced use-cases
        
        ## `and` — require multiple conditions
        
        Sample only traces that are **both** an error **and** slow:
        
        ```yaml
        - name: slow-errors
          type: and
          and:
            and_sub_policy:
              - name: has-error
                type: status_code
                status_code:
                  status_codes: [ERROR]
              - name: is-slow
                type: latency
                latency:
                  threshold_ms: 1000
        ```
        
        `and` accepts all policy types except `composite`, `and`, and `drop`. A `not` sub-policy can be nested inside `and_sub_policy` (and inside `drop_sub_policy`), letting you AND a negated condition without a separate top-level policy.
        
        ## `composite` — tiered sampling with rate allocation
        
        Allocate a total spans-per-second budget across policies in priority order:
        
        ```yaml
        - name: tiered
          type: composite
          composite:
            max_total_spans_per_second: 1000
            policy_order: [errors, slow, sample-rest]
            composite_sub_policy:
              - name: errors
                type: status_code
                status_code:
                  status_codes: [ERROR]
              - name: slow
                type: latency
                latency:
                  threshold_ms: 1000
              - name: sample-rest
                type: always_sample
            rate_allocation:
              - policy: errors
                percent: 60
              - policy: slow
                percent: 30
              - policy: sample-rest
                percent: 10
        ```
        
        Each sub-policy gets `(percent/100) * max_total_spans_per_second`; the first matching policy under budget samples the trace. Put an `always_sample` last to use leftover capacity.
        
        A sub-policy omitted from `rate_allocation` receives the default equal share
        (`max_total_spans_per_second / number of sub-policies`) instead of an accidental zero rate. That
        default is not the unallocated remainder; list every policy explicitly when exact percentages matter.
        
        ## In-process sharding
        
        `num_shards` runs independent event loops inside one processor and assigns each trace by trace-ID
        hash. This reduces contention at high ingest rates without splitting a trace:
        
        ```yaml
        processors:
          tail_sampling:
            num_shards: 8
            num_traces: 100000
            policies: [ ... ]
        ```
        
        `num_traces`, `expected_new_traces_per_sec`, both decision-cache sizes, and per-second limits in
        `rate_limiting`, `bytes_limiting`, and composite policies are divided across shards so their
        configured totals remain aggregate limits. Limiter `burst_capacity` is not divided. Values above
        256 are rejected, and `num_shards > 1` cannot be combined with `tail_storage`.
        
        ## Combining policies and dropping noise
        
        Order policies by intent: force-drop noise first (`drop`), then keep errors, then service-specific rules, then a probabilistic baseline. Any `sample` decision keeps the trace unless a `drop` policy fired.
        
        ```yaml
        policies:
          - name: drop-health
            type: drop
            drop:
              drop_sub_policy:
                - name: health-routes
                  type: string_attribute
                  string_attribute:
                    key: http.route
                    values: ["^/health$", "^/ready$", "^/metrics$"]
                    enabled_regex_matching: true
          - name: errors
            type: status_code
            status_code:
              status_codes: [ERROR]
          - name: baseline
            type: probabilistic
            probabilistic:
              sampling_percentage: 1
        ```
        
        ## Scaling horizontally with the `load_balancing` exporter
        
        A single tail-sampling instance only works while every span of a trace lands on it. To run **more than one** tail-sampling collector you need a front layer of collectors using the `load_balancing` exporter with `routing_key: traceID`, so all spans of a given trace are routed to the **same** downstream tail-sampling instance:
        
        ```yaml
        # Layer 1 — routing collectors (any number of replicas)
        exporters:
          load_balancing:
            routing_key: traceID
            protocol:
              otlp:
                tls:
                  insecure: true
            resolver:
              dns:
                hostname: tail-sampling-layer
                port: "4317"
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [batch]
              exporters: [load_balancing]
        
        # Layer 2 — tail-sampling collectors (behind the DNS name above)
        processors:
          tail_sampling:
            decision_wait: 30s
            num_traces: 100000
            policies: [ ... ]
        ```
        
        For high-volume systems also size `decision_cache` larger than `num_traces` so late-arriving spans inherit the prior decision instead of forming a new (partial) trace.
        
      • configuration.md 3.6 KB
        # `tail_sampling`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          tail_sampling:
            decision_wait: 10s
            num_traces: 50000
            policies:
              # Keep all error traces
              - name: errors
                type: status_code
                status_code:
                  status_codes: [ERROR]
              # Sample 10% of everything else
              - name: baseline
                type: probabilistic
                probabilistic:
                  sampling_percentage: 10
        
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [tail_sampling]
              exporters: [otlphttp]
        ```
        
        ## Configuration reference (top-level)
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `sampling_strategy` | enum | `trace-complete` | `trace-complete`: evaluate the accumulated trace on the timer path (most flexible, higher memory). `span-ingest`: evaluate each incoming batch at ingest — terminal `sampled`/`dropped` outcomes finalize immediately, non-terminal traces finalize as not-sampled on cleanup (rejects stateful policies). Invalid values fail validation. |
        | `decision_wait` | duration | `30s` | Time from first span arrival before the decision is made. Buffers spans for this long. Under `span-ingest` it instead controls pending-cleanup finalization timing. |
        | `decision_wait_after_root_received` | duration | `0s` | Decide this long after the root span arrives; `0` disables (only `decision_wait` is used). |
        | `num_traces` | int | `50000` | Max traces kept in memory. When full, the oldest are evicted (before decision) unless `block_on_overflow`. |
        | `num_shards` | int | `1` | Parallel in-process event loops, with traces assigned by trace-ID hash. Maximum `256`; values >1 cannot be combined with `tail_storage`. Aggregate capacities and per-second policy limits are divided across shards. Added in v0.159.0. |
        | `expected_new_traces_per_sec` | int | `0` | Hint for pre-allocating the trace buffer; `0` disables pre-allocation. |
        | `sample_on_first_match` | bool | `false` | Stop evaluating and sample as soon as one policy matches. Do not combine with tracestate handling: later policies may report a less strict threshold. |
        | `block_on_overflow` | bool | `false` | Block ingest instead of dropping the oldest traces when `num_traces` is reached. |
        | `drop_pending_traces_on_shutdown` | bool | `false` | On shutdown, drop pending traces instead of deciding with partial data. |
        | `maximum_trace_size_bytes` | int | `0` | Traces larger than this are dropped immediately; `0` disables. |
        | `decision_cache.sampled_cache_size` | int | `0` | LRU cache of sampled trace IDs so late spans inherit the decision; `0` disables. |
        | `decision_cache.non_sampled_cache_size` | int | `0` | LRU cache of dropped trace IDs; `0` disables. |
        | `policies` | list | (required) | Sampling policies. At least one is required. |
        
        `tail_storage` (a component ID) offloads span buffering to a tail-storage extension instead of memory, but is behind the alpha `processor.tailsamplingprocessor.tailstorageextension` feature gate — setting it without the gate fails validation. It cannot be combined with `num_shards > 1`.
        
        The alpha `processor.tailsamplingprocessor.usetracestate` gate (off by default) makes the
        `probabilistic`, `rate_limiting`, and `bytes_limiting` policies report effective probability
        sampling thresholds and rewrites outgoing `th` values. It falls back to the legacy trace-ID hash
        when no probability sampling information is present. Do not combine it with
        `sample_on_first_match`. See [Tracestate probability sampling](policies.md#tracestate-probability-sampling).
        
        For the full catalog of policy types and their sub-fields, see [Policy types](policies.md).
        
      • policies.md 5.6 KB
        # `tail_sampling`: policy types
        
        Each policy has `name` and `type`, plus a config block named after the type.
        
        | `type` | Purpose |
        |--------|---------|
        | `always_sample` | Sample every trace (catch-all / debugging). No sub-config. |
        | `latency` | Sample by total trace duration (earliest start to latest end); the lower bound is exclusive. |
        | `numeric_attribute` | Sample when a numeric span/resource attribute is within a range. |
        | `probabilistic` | Sample a fixed percentage via trace-ID hash, or via W3C probability sampling information when the opt-in tracestate gate is enabled. |
        | `status_code` | Sample if any span has a matching status code. |
        | `string_attribute` | Sample by string span/resource attribute value (exact or regex). |
        | `rate_limiting` | Sample up to a maximum spans-per-second via a token bucket; `spans_per_second` (required) + `burst_capacity` (optional, default 2× the rate). A trace with more spans than the burst never passes. |
        | `bytes_limiting` | Sample up to a maximum bytes-per-second via a token bucket; `bytes_per_second` (required) + `burst_capacity` (optional, default 2× the rate). |
        | `span_count` | Sample by number of spans in the trace. |
        | `trace_state` | Sample by W3C TraceState key/value. |
        | `trace_flags` | Sample if the W3C `sampled` trace flag is set on any span in the trace. No sub-config. |
        | `boolean_attribute` | Sample by boolean attribute value. |
        | `ottl_condition` | Sample when OTTL conditions on spans/span-events match (`span:` and `spanevent:` lists, plus `error_mode`). Prefer path-qualified context (e.g. `span.attributes[...]`, `resource.attributes[...]`, `spanevent.name`) over bare `attributes[...]` to avoid future breaking changes. |
        | `and` | Combine sub-policies with AND — all must match to sample. |
        | `composite` | Combine sub-policies with priority order and per-policy rate allocation. |
        | `not` | Invert the decision of a single wrapped sub-policy via `not_sub_policy`. |
        | `drop` | Force-drop traces where all sub-policies match; takes precedence over sampling. |
        
        Key sub-fields for the common policies:
        
        ```yaml
        # latency: minimum (and optional maximum) trace duration
        - name: slow
          type: latency
          latency:
            threshold_ms: 1000
            upper_threshold_ms: 5000   # optional; 0 = no upper bound
        
        # numeric_attribute: span or resource attribute within a range
        - name: http-errors
          type: numeric_attribute
          numeric_attribute:
            key: http.response.status_code
            min_value: 400
            max_value: 599
        
        # string_attribute: exact or regex match
        - name: by-route
          type: string_attribute
          string_attribute:
            key: http.route
            values: ["/api/users", "^/api/.*"]
            enabled_regex_matching: false   # treat values as regex when true
            invert_match: false             # sample traces that do NOT match
        
        # status_code: OK / ERROR / UNSET
        - name: errors
          type: status_code
          status_code:
            status_codes: [ERROR]
        
        # probabilistic: percentage via trace-ID hash
        - name: baseline
          type: probabilistic
          probabilistic:
            sampling_percentage: 10.0
        
        # rate_limiting: cap spans per second (token bucket)
        - name: cap
          type: rate_limiting
          rate_limiting:
            spans_per_second: 1000
            burst_capacity: 2000   # optional; default 2× spans_per_second
        ```
        
        The latency interval is **`threshold_ms < duration <= upper_threshold_ms`**. With no upper bound (`upper_threshold_ms: 0`), only durations strictly greater than `threshold_ms` match. v0.157.0 fixed the no-upper-bound case to use this same exclusive lower bound; a trace exactly equal to `threshold_ms` is not sampled.
        
        ## Decision precedence
        
        All policies are evaluated (unless `sample_on_first_match: true`), then a single decision is chosen: a `drop` decision wins over everything; otherwise any `sample` decision keeps the trace; if no policy matches, the trace is **not** sampled.
        
        > **`invert_match` note:** the legacy "inverted decisions" behavior is gone — the `disableinvertdecisions` feature gate was stabilized and then removed, so as of v0.156.0 `invert_match: true` always yields a plain sample/not-sample on the negated condition and no longer vetoes other policies. `invert_match` still exists on the `numeric_attribute`, `string_attribute`, and `boolean_attribute` policies. To explicitly suppress traces, use a `drop` policy; to sample on the opposite of a wrapped policy's decision, use a `not` policy instead.
        
        ## Tracestate probability sampling
        
        The alpha `processor.tailsamplingprocessor.usetracestate` gate is off by default. When enabled, the `probabilistic` policy reads OpenTelemetry probability sampling fields (`rv` and `th` in the `ot` section) from W3C `tracestate`: it uses explicit `rv` randomness when present, otherwise trace-ID-derived randomness, and falls back to the legacy FNV trace-ID hash (with `hash_salt`) when no probability sampling information exists.
        
        For sampled traces, the processor rewrites each parseable span's outgoing `th` to the smallest effective threshold across policies that voted to sample. Since v0.160.0, top-level and `and`-nested `rate_limiting` and `bytes_limiting` policies report the threshold actually applied; their token-bucket limits and burst behavior are unchanged. These policies inside `composite` still report always-sample. Existing stricter thresholds are preserved. Spans with unparseable tracestate are skipped during rewriting and counted by the Development metric `otelcol_processor_tail_sampling_count_spans_with_unparseable_tracestate`.
        
        Do not combine tracestate handling with `sample_on_first_match`: stopping early can skip a later
        policy with a less strict threshold and make adjusted counts incorrect.
        
        The `and`, `composite`, `not`, and `drop` policy types compose other policies; see [Advanced use-cases](advanced.md) for worked examples.
        
      • quirks.md 2.9 KB
        # `tail_sampling`: known quirks
        
        ## All spans of a trace must reach the SAME instance
        
        The decision is made per-trace inside one process. If spans of one trace are spread across multiple tail-sampling collectors, each sees only a fragment and makes its own (wrong/partial) decision. Whenever you run more than one tail-sampling collector you **must** put a `load_balancing` exporter layer in front that routes by `traceID`. A single instance needs no load balancer.
        
        `num_shards` does not change this rule. Sharding is internal to one processor and routes a complete
        trace to one event loop; it is not coordination among Collector instances.
        
        ## Memory scales with `num_traces` and trace size
        
        `num_traces` is the in-flight trace buffer: every trace awaiting a decision is held in memory. Rough estimate `num_traces * avg_spans_per_trace * ~1KB/span` (e.g. 50,000 traces × 20 spans ≈ 1 GB). Longer `decision_wait` means more traces resident at once. When the buffer fills, the oldest traces are evicted **before** their decision (surfacing as the `sampling_trace_dropped_too_early` metric) unless `block_on_overflow` is set. Size it as `traces_per_sec * decision_wait_seconds * safety_factor`.
        
        ## `decision_wait` adds latency and a fixed window
        
        Sampled traces are only exported after `decision_wait` expires, so this delay is added before any trace leaves the processor. Set it long enough for distributed traces to assemble; too short and late spans miss the window. Spans that arrive **after** the decision are not retroactively folded into it — they either inherit the cached decision (if `decision_cache` is configured) or risk forming a separate/dropped partial trace.
        
        ## Stateful, single-process decision
        
        Tail sampling is inherently stateful: the keep/drop choice is computed once, in one process, from whatever spans were buffered at decision time. There is no cross-instance coordination and no re-evaluation of a trace once decided. Place context-enriching processors (e.g. `k8s_attributes`) **before** `tail_sampling` in the pipeline, since the processor re-batches spans and downstream context can be lost.
        
        ## Tracestate rewriting is opt-in and parse-sensitive
        
        The processor only consumes and rewrites OpenTelemetry probability sampling fields when the alpha `processor.tailsamplingprocessor.usetracestate` gate is enabled. If a span's W3C `tracestate` cannot be parsed, its outgoing `th` is not rewritten; monitor `otelcol_processor_tail_sampling_count_spans_with_unparseable_tracestate` (Development stability) for this case. The probabilistic decision still falls back to the legacy trace-ID hash when the trace carries no probability sampling information. Do not combine the gate with `sample_on_first_match`, which can stop before the least-strict matched threshold is known. Before v0.162.0, late-arriving spans of an already-sampled trace could be rewritten with a different threshold than the original decision; v0.162.0 reuses the original decision's threshold.
        
      • README.md 3.6 KB
        # `tail_sampling` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `tail_sampling` |
        | Signals | traces |
        | Stability | Beta |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor> |
        
        ## Description
        
        Tail sampling buffers **all spans of a trace** (grouped automatically by `trace_id` — no `groupbytrace` needed), waits a configurable `decision_wait` for the trace to complete, then evaluates a set of policies against the **whole trace** to make a single keep/drop decision. Because the decision is made after the trace is (mostly) assembled, it can act on trace-wide signals: presence of an error on any span, total latency, span count, specific attributes, etc.
        
        `num_shards` can split that in-process work across parallel event loops while
        keeping each trace on one shard. This improves concurrency inside one Collector; it does not replace
        trace-affinity routing when scaling across Collector instances.
        
        This is the opposite of **head sampling** (e.g. `probabilistic_sampler`), which decides at the start of a trace based only on the trace ID — before any span content is known. Head sampling is cheap and stateless; tail sampling is content-aware but stateful, requires buffering, and adds latency.
        
        ## Main use-cases
        
        Use it when:
        - You want to keep **all error traces** regardless of volume.
        - You want to keep **slow** traces (latency-based) while down-sampling fast successful ones.
        - You need different sampling rates per service, endpoint, tenant, or attribute.
        - You need sophisticated logic — combining conditions (`and`), tiered rate allocation (`composite`), or explicit noise removal (`drop`).
        
        Avoid it when:
        - Probabilistic head sampling already meets your needs (cheaper, no buffering, no co-location requirement).
        - You need the sampling decision at the edge/SDK before data reaches a collector.
        - You **cannot guarantee all spans of a trace reach the same collector instance** (see [Known quirks](quirks.md)).
        
        ## Related components
        
        - `probabilistic_sampler` — head sampling; stateless, trace-ID based, decided up front. Cheaper but content-blind.
        - [`load_balancing`](../load_balancing/README.md) exporter — routes spans by `traceID` so all spans of a trace reach the same tail-sampling instance; required when scaling tail sampling to more than one collector.
        - `groupbytrace` — groups spans by trace ID and waits before forwarding. Not needed in front of `tail_sampling`, which already groups by trace ID internally.
        
        ## Details
        
        - [Configuration](configuration.md) — top-level config keys (`decision_wait`, `num_traces`, `num_shards`, `decision_cache`, …), defaults, and the minimal pipeline. Open when wiring up the processor or checking a key/default.
        - [Policy types](policies.md) — the full catalog of all ~17 policy types and their sub-fields, latency-boundary semantics, and optional W3C tracestate probability handling. Open when choosing or configuring a sampling policy.
        - [Verification](verification.md) — telemetrygen recipe to confirm sampling works. Open when you want to prove the config end-to-end.
        - [Advanced use-cases](advanced.md) — `and`/`composite`/`drop` combinations, in-process sharding, and scaling out with `load_balancing`. Open when building multi-condition policies or increasing concurrency.
        - [Known quirks](quirks.md) — same-instance/loadbalancing requirement, memory model, `decision_wait` latency, late spans, statefulness, and tracestate parsing. Open when sizing memory or debugging missing/partial traces.
        
      • verification.md 1.9 KB
        # `tail_sampling`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `tail_sampling` ships in the `contrib` and `k8s` distributions.
        
        Verified on `otel/opentelemetry-collector-contrib:0.159.0` with
        `ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.159.0`
        (2026-08-27). This status-code-policy recipe's schema is unchanged through v0.160.0; it does not
        exercise tracestate-aware limiting.
        
        Config (`tailsampling-verify.yaml`):
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          tail_sampling:
            decision_wait: 5s
            num_traces: 1000
            policies:
              - name: errors-only
                type: status_code
                status_code:
                  status_codes: [ERROR]
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [tail_sampling]
              exporters: [debug]
        ```
        
        Generate traces, some with error status (see the `otel-telemetrygen` skill):
        
        ```bash
        # OK traces — expect these to be dropped by the errors-only policy
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 \
          --traces 20 --status-code Ok
        # Error traces — expect these to survive
        telemetrygen traces --otlp-insecure --otlp-endpoint localhost:4317 --traces 20 --status-code Error
        ```
        
        The `--status-code` flag is confirmed in the `otel-telemetrygen` skill (accepted values: `Unset`/`0`, `Error`/`1`, `Ok`/`2`). It sets the span status that the `status_code` policy evaluates. telemetrygen's integer mapping (`1`=Error, `2`=Ok) intentionally differs from the OpenTelemetry status-code enum (`1`=Ok, `2`=Error), so pass the strings shown above.
        
        **What proves it worked:** after `decision_wait`, the `debug` exporter shows the 20 error traces and none of the 20 OK traces. telemetrygen emits two spans per generated trace, so the verified debug output contained 40 Error spans and no Ok spans.
        
    • transform
      • advanced.md 4.6 KB
        # `transform`: advanced use-cases
        
        ## Multiple statement groups
        
        Split work into ordered groups when statements need different contexts, conditions, or error modes. Groups run top-to-bottom; within a group, statements run top-to-bottom too.
        
        ```yaml
        processors:
          transform:
            error_mode: ignore
            metric_statements:
              # Group 1 — metric context
              - statements:
                  - convert_sum_to_gauge() where metric.name == "process.count"
                  - set(metric.description, "process count gauge")
              # Group 2 — datapoint context (separate group: different context)
              - statements:
                  - limit(datapoint.attributes, 50, ["host.name", "service.name"])
                  - delete_matching_keys(datapoint.attributes, "(?i).*temp.*")
        ```
        
        Set `shared_cache: true` on groups that intentionally exchange values through the OTTL cache. This
        experimental v0.160.0 option keeps one cache for groups of the same signal; by default each group
        gets a fresh cache.
        
        ## Scoping statements with conditions
        
        Group-level `conditions` pre-gate a whole group; they are OR'd, and AND'd with each statement's `where`.
        
        ```yaml
        processors:
          transform:
            error_mode: ignore
            log_statements:
              # Only JSON-looking logs
              - conditions:
                  - IsMatch(log.body, "^\\{")
                statements:
                  - merge_maps(log.cache, ParseJSON(log.body), "upsert")
                  - set(log.attributes["parsed"], true)
              # Only one service
              - conditions:
                  - resource.attributes["service.name"] == "payment-service"
                statements:
                  - delete_matching_keys(log.attributes, "(?i).*(card|ssn|password).*")
        ```
        
        ## Context selection trade-offs
        
        Pick the **broadest** context that still reaches what you need: editing at `resource` runs once per resource, while `datapoint`/`spanevent` runs per item and is more expensive. Inference already prefers the most specific context that covers your paths — set `context:` explicitly only to override it, and prefer to express a transform at the highest level that the data allows. Path inventories per context live in the `otel-ottl` skill; the context table is in [configuration.md](configuration.md).
        
        ## Combining with `filter`
        
        `transform` and `filter` share the OTTL language but do opposite jobs: `filter` drops, `transform` rewrites. A common pipeline normalizes then drops:
        
        ```yaml
        service:
          pipelines:
            logs:
              processors: [memory_limiter, transform, filter]
        ```
        
        Run `transform` first if `filter`'s conditions depend on attributes `transform` sets (or vice-versa) — ordering in the pipeline is load-bearing.
        
        ## error_mode for resilience
        
        ```yaml
        # Production: a failing statement logs but doesn't drop the batch
        processors:
          transform:
            error_mode: ignore
        ```
        
        ```yaml
        # Mixed: most groups tolerant, one group must fail loudly
        processors:
          transform:
            error_mode: ignore
            metric_statements:
              - error_mode: propagate          # this group only
                statements:
                  - set(metric.unit, "By") where metric.name == "system.memory.usage"
              - statements:                     # inherits ignore
                  - set(metric.description, metric.cache["description"])
        ```
        
        ## Named instances
        
        ```yaml
        processors:
          transform/redact:
            error_mode: ignore
            log_statements:
              - replace_pattern(log.body, "\\b\\d{16}\\b", "****")
          transform/enrich:
            error_mode: ignore
            log_statements:
              - set(log.attributes["env"], "prod")
        
        service:
          pipelines:
            logs/redact:
              processors: [memory_limiter, transform/redact]
            logs/enrich:
              processors: [memory_limiter, transform/enrich]
        ```
        
        ## Common transforms
        
        | Goal | Shape (OTTL — see `otel-ottl` skill for function details) |
        |------|------------------------------------------------------------|
        | Rename an attribute | `set(resource.attributes["new"], resource.attributes["old"])` then `delete_key(resource.attributes, "old")` — set before delete. |
        | Redact a value | `replace_pattern(...)` / `delete_key(...)` / `delete_matching_keys(...)`. |
        | Set a default | `set(log.severity_text, "INFO") where log.severity_text == nil or log.severity_text == ""`. |
        | Limit / truncate attributes | `keep_keys(...)`, `limit(...)`, `truncate_all(...)`. |
        | Convert metric types | `convert_sum_to_gauge()`, `convert_gauge_to_sum(...)` (metric context, with a `where` clause). |
        | Reshape histograms | `extract_count_metric(...)`, `extract_sum_metric(...)`, `extract_percentile_metric(...)`, `extract_avg_metric(...)` (v0.162.0+). |
        | Aggregate datapoints | `aggregate_on_attributes(...)`, `aggregate_on_attribute_value(...)`. |
        
        These are processor-specific OTTL functions; their full signatures and the general OTTL converter set live in the `otel-ottl` skill.
        
      • configuration.md 6.5 KB
        # `transform`: configuration
        
        ## Typical config
        
        ```yaml
        processors:
          transform:
            error_mode: ignore
            log_statements:
              - set(log.attributes["env"], "prod")
        
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [memory_limiter, transform]
              exporters: [otlphttp]
        ```
        
        ## Top-level keys
        
        | Key | Type | Default | Notes |
        |-----|------|---------|-------|
        | `error_mode` | string | `ignore` | How OTTL statement-evaluation errors are handled: `ignore`, `silent`, or `propagate`. See [Error mode](#error-mode). |
        | `trace_statements` | list | `[]` | Statements run against trace data. |
        | `metric_statements` | list | `[]` | Statements run against metric data. |
        | `log_statements` | list | `[]` | Statements run against log data. |
        | `profile_statements` | list | `[]` | Statements run against profile data (Development stability). |
        
        > The default `error_mode` is `ignore`. The `processor.transform.defaultErrorModeIgnore` gate was removed in v0.162.0, so naming it in `--feature-gates` fails startup; see [quirks](quirks.md#error_mode-propagate-drops-the-whole-batch-on-error).
        >
        > A `flatten_data` boolean (default `false`, behind the `transform.flatten.logs` alpha feature gate) gives each log record a distinct copy of its resource and scope before transformation, then regroups afterwards — useful when log-level data drives resource/scope edits. It copies and hashes every record, so enable only when needed.
        
        ## Error mode
        
        | Mode | Behavior | When |
        |------|----------|------|
        | `ignore` | Logs the error and moves to the next statement. | **Default** (since v0.153.0) and **recommended in production** — one bad statement won't drop unrelated data. |
        | `silent` | Continues without logging. | When error logging is too noisy. |
        | `propagate` | Returns the error up the pipeline, dropping the whole payload. | Development/testing — surfaces config mistakes immediately. Was the default before v0.153.0. |
        
        The top-level `error_mode` can be overridden per statement group (see below).
        
        ## Statement groups
        
        Each `*_statements` field accepts entries in two styles. **Do not mix the two styles within one signal's list.**
        
        ### Flat (inferred-context) form
        
        The recommended form for most cases: a plain list of OTTL statements. The processor **infers the context** from the path prefixes in each statement (e.g. `log.body` → `log` context; `resource.attributes` alone → `resource` context). You write statements without declaring a context.
        
        ```yaml
        transform:
          error_mode: ignore
          trace_statements:
            - keep_keys(span.attributes, ["service.name", "http.request.method", "http.route"])
            - set(resource.attributes["deployment.environment.name"], "production")
          log_statements:
            - set(log.severity_text, "FAIL") where log.body == "request failed"
        ```
        
        ### Explicit-group form
        
        For complex cases, group related statements and attach options. Each group is an object with an optional `context`, an optional group-level `error_mode`, optional `conditions`, and a required `statements` list.
        
        ```yaml
        transform:
          error_mode: propagate           # default for all groups
          metric_statements:
            - context: metric             # optional; inferred if omitted
              error_mode: ignore          # optional; overrides top-level for this group
              conditions:                 # optional; OR'd together
                - metric.type == METRIC_DATA_TYPE_SUM
              statements:                 # required
                - set(metric.description, "Sum metric")
                - convert_sum_to_gauge() where metric.name == "system.processes.count"
        ```
        
        | Group key | Type | Required | Notes |
        |-----------|------|----------|-------|
        | `context` | string | No | Explicit OTTL context (see [Context values](#context-values)). Usually inferred — only set it to override inference. |
        | `error_mode` | string | No | Overrides the top-level `error_mode` for this group only. |
        | `conditions` | list | No | Group-level OTTL booleans, OR'd together. If any is true (or the list is empty), the group's statements run. |
        | `shared_cache` | bool | No (`false`) | Experimental. Share one OTTL cache map among statement groups of the same signal instead of clearing it between groups. The cache never persists across batches (v0.162.0 fixed a bug where it could). Added in v0.160.0. |
        | `statements` | list | Yes | The OTTL statements to execute, in order. |
        
        ## Context values
        
        Context determines which paths the statements can reach. Contexts are hierarchical — a more specific context can also read the broader ones above it. The processor picks the **most specific context** that covers all paths in a group; set `context:` explicitly only when inference can't reconcile the paths.
        
        | Signal | Contexts (broad → specific) | Reaches |
        |--------|------------------------------|---------|
        | traces | `resource`, `scope`, `span`, `spanevent` | `spanevent` can read `resource`, `scope`, `span`, `spanevent` |
        | metrics | `resource`, `scope`, `metric`, `datapoint`, `exemplar` | `exemplar` can read `resource`, `scope`, `metric`, `datapoint`, `exemplar` |
        | logs | `resource`, `scope`, `log` | `log` can read `resource`, `scope`, `log` |
        | profiles | `resource`, `scope`, `profile` | `profile` can read `resource`, `scope`, `profile` (Development) |
        
        The `exemplar` metric context (reaching a datapoint's exemplars) was added in v0.156.0; on older versions `metric_statements` stops at `datapoint`.
        
        Some path/function combinations can't share a context (e.g. a metric-context-only function alongside a `datapoint` path). When inference fails, split the conflicting statements into separate groups. See [Known quirks](quirks.md).
        
        ## `conditions` vs `where`
        
        - `conditions` (group-level) gate the **whole group**; the entries are OR'd. If none match, no statement in the group runs.
        - `where` (per statement) gates a **single statement**; it is AND'd with the group's conditions.
        
        ```yaml
        metric_statements:
          - conditions:
              - metric.type == METRIC_DATA_TYPE_SUM        # group runs only for Sum metrics
            statements:
              - set(metric.description, "count") where metric.name == "http.requests"  # AND name matches
        ```
        
        ## Statement ordering and short-circuiting
        
        Statements run top-to-bottom; later statements observe the effects of earlier ones (so set-then-delete order matters). With `error_mode: propagate`, the first failing statement **short-circuits the rest of the payload** — remaining statements never run and the batch is dropped. With `ignore`/`silent`, a failing statement is skipped and the next one still runs.
        
        The OTTL expression surface — functions, paths, editors, converters, grammar, enums — is covered in the `otel-ottl` skill and is not reproduced here.
        
      • quirks.md 5.8 KB
        # `transform`: known quirks
        
        ## Statement order matters
        
        Statements run top-to-bottom and later ones see earlier effects. A classic bug is deleting an attribute before reading it:
        
        ```yaml
        # BAD — delete runs first, set reads nil
        - delete_key(resource.attributes, "k8s.namespace.name")
        - set(resource.attributes["namespace"], resource.attributes["k8s.namespace.name"])
        
        # GOOD — set first, then delete
        - set(resource.attributes["namespace"], resource.attributes["k8s.namespace.name"])
        - delete_key(resource.attributes, "k8s.namespace.name")
        ```
        
        ## Mutating shared resource/scope affects everything under it
        
        A `resource`-context statement runs once per resource and changes are seen by **all** spans/metrics/logs sharing that resource. Setting a resource attribute from per-item data (e.g. a single log's attribute) leaks that value across the whole group. When per-item resource edits are genuinely needed for logs, enable `flatten_data` (behind the `transform.flatten.logs` feature gate) so each record gets its own resource copy — at the cost of copying and hashing every record.
        
        ## `error_mode: propagate` drops the whole batch on error
        
        Under `propagate`, the first statement that errors (a type mismatch, a nil dereference like `log.cache["x"]["y"]` when `x` is nil) **drops the entire payload**, not just the offending item, and the remaining statements never run. The default is `ignore`, but any config or group that still sets `propagate` behaves this way. Keep `error_mode: ignore` (or `silent`) in production, and nil-check before accessing nested fields:
        
        ```yaml
        - set(log.attributes["uid"], log.cache["user"]["id"]) where log.cache["user"] != nil
        ```
        
        `silent` hides evaluation errors entirely — if a transform "isn't working," confirm it isn't set to `silent`. The default flipped from `propagate` to `ignore` in v0.153.0 and became permanent when `processor.transform.defaultErrorModeIgnore` reached Stable in v0.157.0. Disabling the gate failed startup on v0.157.0–v0.161.0; the gate was removed in v0.162.0, so any remaining `--feature-gates` reference to it now fails startup as an unknown gate. Pre-v0.153.0 collectors still default to `propagate`; on v0.153.0–v0.156.0 the Beta gate could be disabled to restore it.
        
        ## Context performance
        
        Statements evaluate per item at their inferred context. A `datapoint`- or `spanevent`-context group runs for every datapoint/event, which is far more work than a `resource`- or `metric`-context group. Express each transform at the broadest context that reaches the data. Mixing incompatible paths/functions in one group makes context inference fail — split them:
        
        ```yaml
        # FAILS — convert_sum_to_gauge is metric-context-only; limit needs datapoint context
        metric_statements:
          - convert_sum_to_gauge() where metric.name == "process.count"
          - limit(datapoint.attributes, 100, ["host.name"])
        ```
        
        ```yaml
        # WORKS — one group per context
        metric_statements:
          - statements:
              - convert_sum_to_gauge() where metric.name == "process.count"
          - statements:
              - limit(datapoint.attributes, 100, ["host.name"])
        ```
        
        ## OTTL version drift
        
        `transform` is a thin shell around OTTL, which evolves quickly: function names, signatures, path syntax, and enums change between collector releases (e.g. the path-prefix style `log.body` vs older bare `body`; `extract_percentile_metric` added in v0.151.0, `extract_avg_metric` in v0.162.0). `set_semconv_span_name` accepts semantic-convention versions `1.37.0` through `1.43.0` in v0.162 and v0.163 (verified 2026-10-15; owner: the `otel-ottl` skill's `references/functions.md`). Validate statements against the running version — see the `otel-ottl` skill — rather than assuming a snippet from another release still parses.
        
        ## Don't change identity carelessly
        
        - Setting `span.trace_id`/`span.span_id` orphans spans and breaks trace/log correlation.
        - Renaming a metric or stripping all its datapoint attributes (`set(metric.name, ...)`, `delete_matching_keys(datapoint.attributes, ".*")`) collapses distinct series into one — an identity conflict.
        - `copy_metric` without a `where` clause that excludes the copy creates an infinite loop. Always gate it.
        
        ## Stability caveats
        
        Traces, metrics, and logs are **Beta**; profiles are **Development**. Beta means breaking changes are rare but config keys can still move; profile support may break. Check the upstream README for the running version before treating any key as stable.
        
        ## Troubleshooting
        
        **Statement does nothing.**
        - A `where` clause or group `conditions` didn't match.
        - Wrong context inferred — set `context:` explicitly, or split the group.
        - The field is nil; guard with a nil check.
        - Enable `service.telemetry.logs.level: debug` to see the `TransformContext` before/after each statement and whether conditions matched.
        
        **Errors logged but telemetry still flows.** Expected under `error_mode: ignore`; use `silent` to quiet the logs once you've accepted the errors.
        
        **Whole payloads disappear.** `error_mode: propagate` plus an erroring statement. Switch to `ignore` or fix the statement.
        
        **"unexpected token" / "unknown context" on startup.** Incompatible paths/functions in one group (split them), a syntax error, an unknown/renamed function, or mixing the flat and explicit-group styles in one signal's list.
        
        ## Anti-patterns
        
        **Using `transform` to filter.** Setting a flag so a later `filter` drops the item is a smell — drop directly with `filter`.
        
        **Hardcoding values that should come from detection.** `set(resource.attributes["deployment.environment.name"], "production")` belongs in resource detection or the SDK, not a static transform, unless you genuinely have no better source.
        
        **`propagate` in production.** Any single error drops the batch; use `ignore`/`silent` and nil-guard nested access.
        
        **Over-transformation.** Long chains of rewrites per item add latency and CPU; do at the source what you can, and transform only what must change downstream.
        
      • README.md 3.6 KB
        # `transform` processor
        
        | | |
        |-|-|
        | Kind | processor |
        | Type | `transform` |
        | Signals | traces (Beta), metrics (Beta), logs (Beta), profiles (Development) |
        | Distributions | contrib, k8s |
        | Go module | `github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor` |
        | Upstream README | <https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor> |
        
        ## Description
        
        Mutates telemetry — spans, span events, metrics, datapoints, log records (and, in development, profiles) — **in place** by running OTTL statements against it. You supply a per-signal list of statements (`trace_statements`, `metric_statements`, `log_statements`, `profile_statements`); each statement executes sequentially against incoming data, optionally guarded by a `where` clause. Unlike `filter`, which drops items, `transform` rewrites them: set, delete, rename, redact, convert types, parse structured fields, and more.
        
        The top-level `error_mode` controls what happens when a statement fails to evaluate — `ignore` (default) logs and continues to the next statement, `silent` continues without logging, `propagate` drops the whole payload. The `processor.transform.defaultErrorModeIgnore` gate was removed in v0.162.0, so naming it in `--feature-gates` fails startup; see [quirks](quirks.md#error_mode-propagate-drops-the-whole-batch-on-error). Statement groups run in the order written, so later statements see the effects of earlier ones. The OTTL language itself (functions, paths, editors/converters, grammar) lives in the `otel-ottl` skill; this page documents only the processor's config surface.
        
        ## Main use-cases
        
        Use it when:
        - You need to **modify** telemetry — rename or set attributes, set defaults for missing fields, normalize formats across sources.
        - You want to redact sensitive values (passwords, tokens, PII) in place rather than dropping the record.
        - You need metric reshaping — convert sum↔gauge, extract count/sum/percentile from histograms, scale or aggregate datapoints.
        - You want to parse structured data out of an unstructured log body into attributes.
        
        Avoid it when:
        - You only need to **drop** whole items — use `filter` (it complements `transform`).
        - You need a whole-trace keep/drop decision — use `tail_sampling`.
        - You need cross-batch metric aggregation over time — use `interval` or a metrics connector.
        
        ## Related components
        
        - `filter` — drops telemetry via the same OTTL language; pair it with `transform` when some items should be removed and others rewritten.
        - `attributes` — simpler key/value attribute edits without OTTL, for straightforward add/update/delete.
        - `resource` — resource-attribute edits specifically.
        - `metricstransform` — name/label-oriented metric renaming and aggregation outside OTTL.
        - `routing` — sends telemetry to different pipelines by condition instead of mutating it.
        
        ## Details
        
        - [Configuration](configuration.md) — `error_mode`, the statement-group structure, `context` values per signal, the inferred-context form, group-level `conditions`, and statement ordering/short-circuiting.
        - [Verification](verification.md) — telemetrygen recipe that sets an attribute on logs so the change is observable in `debug` output.
        - [Advanced use-cases](advanced.md) — multiple statement groups, scoping with conditions, context selection trade-offs, combining with `filter`, error_mode for resilience, named instances, common transforms.
        - [Known quirks](quirks.md) — statement ordering, shared-resource mutation, propagate dropping the batch, context performance, OTTL version drift, stability caveats, anti-patterns.
        
      • verification.md 1.9 KB
        # `transform`: verification
        
        See [Verification harness](../../SKILL.md#verification-harness) for how to run this end-to-end.
        
        `transform` ships in the `contrib` and `k8s` distributions, so a stock contrib collector can run this.
        
        Verified on `otel/opentelemetry-collector-contrib:0.159.0` with
        `ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:v0.159.0`
        (2026-08-27). This recipe's schema is unchanged through v0.160.0; it does not use `shared_cache`.
        
        Config (`transform-verify.yaml`) — add an `env` attribute to every log record that telemetrygen does **not** itself produce:
        
        ```yaml
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
        processors:
          transform:
            error_mode: ignore
            log_statements:
              - set(log.attributes["env"], "prod")
        exporters:
          debug:
            verbosity: detailed
        service:
          pipelines:
            logs:
              receivers: [otlp]
              processors: [transform]
              exporters: [debug]
        ```
        
        Send a batch of logs — see the `otel-telemetrygen` skill:
        
        ```bash
        telemetrygen logs --otlp-insecure --otlp-endpoint localhost:4317 \
          --logs 5 --body "transform me"
        ```
        
        Flags confirmed against the `otel-telemetrygen` skill (`references/flags.md`): `--otlp-insecure` (bool), `--otlp-endpoint` (string), `--logs` (int, per-worker count; ignored if `--duration` is set, so no `--duration` here), and `--body` (string). No flag here invents a value.
        
        **What proves it worked:** the `debug` exporter prints all 5 log records, and each carries an `env: Str(prod)` attribute in its `Attributes` block. telemetrygen never emits an `env` log attribute, so its presence in the output is unambiguous proof the statement ran. The log body ("transform me") is unchanged — only the attribute was added.
        
        > The exact `debug` rendering of attributes (`-> env: Str(prod)`) can shift between collector versions; what matters is that the `env`/`prod` pair appears on records that didn't have it on input.
        
  • SKILL.md 17.5 KB
    ---
    name: otel-collector
    description: OpenTelemetry Collector component configuration. Use when authoring, reviewing, or debugging Collector YAML for a specific receiver, processor, exporter, connector, or extension — config keys, defaults, validation rules, signal support, stability levels, and component-level gotchas. Triggers on Collector component questions including receivers, processors, exporters, connectors, extensions, component renames, signal support, and pipeline wiring.
    ---
    
    # OpenTelemetry Collector
    
    This skill covers the configuration surface of individual OpenTelemetry Collector components. It targets [opentelemetry-collector](https://github.com/open-telemetry/opentelemetry-collector) and [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib).
    
    It does **not** cover OTTL expressions (see `otel-ottl`), declarative SDK configuration (`otel-declarative-config`), deploying the eBPF profiler or other profiles producers (`otel-profiles`), or end-to-end pipeline design choices. Reach for those skills when the question is about transformation language, SDK setup, or pipeline composition.
    
    ## Workflow
    
    1. **Identify the component.** Find the `type` in the user's config or question (`log_dedup`, `interval`, `otlp`, …). Note that many components have been renamed to snake_case since v0.146.0 with deprecated aliases preserved — see [Recent renames](#recent-renames).
    2. **Load the component page before answering.** If the component is in the [Component index](#component-index), always read `components/<type>/README.md` first — the top-level index is only a routing aid, not enough evidence for an answer. Follow its **Details** index to the smallest relevant file: read `configuration.md` for versioned keys, defaults, or validation; `quirks.md` for stability, availability, failure behavior, or migration traps; and the other detail files only as the question requires. Do not infer exact configuration or status from the summary row or memory.
    3. **If the component is not indexed**, say so explicitly and fall back to the upstream README under `processor/<name>/`, `receiver/<name>/`, `exporter/<name>/`, `connector/<name>/`, or `extension/<name>/` in [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib). Don't invent config keys from memory — Collector components evolve quickly.
    4. **Apply Collector-wide conventions.** Named instances (`type/name`), stability levels, and pipeline placement rules in [Collector-wide conventions](#collector-wide-conventions) apply to every component.
    5. **Verify.** Run the component page's **Verification** recipe — `telemetrygen` (see the `otel-telemetrygen` skill) plus a `debug` or `file` exporter — to confirm the component behaves as the docs claim. Alpha- and Development-stability components are common here, and behavior changes between releases. See [Verification harness](#verification-harness) for how to run a recipe end-to-end.
    
    ## Component index
    
    Each component is a directory under `components/<type>/`. The `File` column points at the lean `README.md` (metadata, description, main use-cases, and a **Details** index); the full config reference, verification recipe, advanced use-cases, and quirks live in on-demand detail files linked from that README.
    
    Coverage is intentionally selective. If a component is not indexed here, fall back to the upstream component README for the user's Collector version.
    
    Verified 2026-10-15: Stability cells here and in component README headers match `metadata.yaml` at core and contrib `v0.163.0`; rename and "since" versions match `metadata.yaml` at the named tag and the one before. Offline, cite that release and date, not current stability.
    
    | Type | File | Kind | Signals | Stability | Summary |
    |------|------|------|---------|-----------|---------|
    | `log_dedup` | `components/log_dedup/README.md` | processor | logs | Alpha | Deduplicates identical log records over a time window; emits one aggregated log with a count. Renamed from `logdedup` in v0.151.0; alias preserved. |
    | `interval` | `components/interval/README.md` | processor | metrics | Alpha | Buffers metrics and emits one point per series per interval: latest value for cumulative monotonic series, gauges, and summaries; the summed value for delta series. Non-monotonic sums pass through unchanged. |
    | `tail_sampling` | `components/tail_sampling/README.md` | processor | traces | Beta | Buffers whole traces and makes a single keep/drop decision after a wait window via policies. Supports in-process concurrency through `num_shards`; scaling across Collector instances still requires a `load_balancing` layer. v0.160 adds tracestate-aware thresholds for rate/bytes limiting. |
    | `drain` | `components/drain/README.md` | processor | logs | Alpha | Clusters log bodies and annotates each record with a template. v0.158 replaced `extract_parameters` / `params_attribute` with `masking_rules` + `parameter_key_prefix` for named values and `emit_wildcards` + `wildcards_attribute` for remaining `<*>` positions. |
    | `redaction` | `components/redaction/README.md` | processor | traces, logs, metrics | Beta (traces), Alpha (logs/metrics) | Allow/block-list masking or removal of sensitive attribute keys and values, with hashing and URL/DB sanitizers. |
    | `filter` | `components/filter/README.md` | processor | traces, metrics, logs, profiles | Alpha (traces/metrics/logs), Development (profiles) | Drops spans, metric data points, log records, and profiles that match OTTL conditions (or legacy metric-name / severity filters). The most direct telemetry-volume lever. |
    | `transform` | `components/transform/README.md` | processor | traces, metrics, logs, profiles | Beta (traces/metrics/logs), Development (profiles) | Applies OTTL statements to mutate spans, metric data points, log records, and profiles in place (rename/redact/convert/aggregate attributes). The OTTL language itself lives in the `otel-ottl` skill. |
    | `probabilistic_sampler` | `components/probabilistic_sampler/README.md` | processor | traces, logs | Beta (traces), Alpha (logs) | Head sampling — deterministically keeps a configured percentage of traces/logs by hashing the trace ID (or an attribute). The cheaper, stateless counterpart to `tail_sampling`. |
    | `attributes` | `components/attributes/README.md` | processor | traces, metrics, logs | Beta | Modifies span/log/datapoint **attributes** via an ordered action list (insert/update/upsert/delete/hash/extract/convert), scoped by include/exclude matching. |
    | `resource` | `components/resource/README.md` | processor | traces, metrics, logs, profiles | Beta (traces/metrics/logs), Development (profiles) | Modifies the **resource** attributes of telemetry via the same action grammar as `attributes` (e.g. set `service.name`, drop noisy resource keys). No include/exclude matching. |
    | `k8s_attributes` | `components/k8s_attributes/README.md` | processor | traces, metrics, logs, profiles | Stable (traces/metrics/logs, since v0.161), Development (profiles) | Enriches telemetry with Kubernetes pod/namespace/node/workload metadata, associating each record to a pod by IP or resource attribute. v0.161 enables the v1 semconv gates by default (singular `k8s.pod.label.*`, `container.image.tags`). |
    | `routing` | `components/routing/README.md` | connector | traces, metrics, logs | Alpha | Routes same-signal telemetry to different pipelines by OTTL `condition`/`statement` per context; ordered table, first match wins, `default_pipelines` fallback. Replaces the deprecated `routingprocessor`. |
    | `memory_limiter` | `components/memory_limiter/README.md` | processor | traces, metrics, logs, profiles | Beta (traces/metrics/logs), Alpha (profiles) | Safety valve against OOM: refuses data with backpressure when Go heap exceeds a soft/hard limit. Its receiver-middleware **extension** reached Beta in v0.158 but is absent from stock distributions; the bundled processor remains the standard form. |
    | `load_balancing` | `components/load_balancing/README.md` | exporter | traces, logs, metrics | Beta (traces/logs), Alpha (metrics) | Distributes telemetry across downstream Collectors via a consistent-hash ring keyed on trace ID / `service.name` / (v0.162+) OTel randomness, etc., pinning related records to one backend. The standard way to scale `tail_sampling`/`span_metrics`. Renamed from `loadbalancing` in v0.153.0; alias preserved. |
    | `otlp` (receiver) | `components/otlp/README.md` | receiver | traces, metrics, logs, profiles | Stable (traces/metrics/logs), Alpha (profiles) | The canonical OTLP ingress over gRPC (`4317`) and/or HTTP (`4318`, protobuf + JSON). `protocols:` block, at least one required. Default endpoint is `localhost`, not `0.0.0.0` — must be set explicitly to receive containerized traffic. |
    | `otlp_grpc` (exporter) | `components/otlp_exporter/README.md` | exporter | traces, metrics, logs, profiles | Stable (traces/metrics/logs), Alpha (profiles) | Sends OTLP over gRPC to a downstream endpoint; gzip, sending queue, and retry are enabled by default. Exporter-side `sending_queue.batch` is opt-in unless the `pkg.exporterhelper.queueBatchEnabled` gate is enabled. Renamed from `otlp` in core v1.50.0; alias preserved. |
    | `file_log` (receiver) | `components/file_log/README.md` | receiver | logs | Beta | Tails log files (glob `include`), turns each line/entry into a log record, and parses it via a stanza `operators` pipeline. Fingerprint + offset tracking across rotation; `storage` for durable offsets. v0.160 adds opt-in `skip_unmodified_files`. |
    | `resource_detection` | `components/resource_detection/README.md` | processor | traces, metrics, logs, profiles | Beta (traces/metrics/logs), Development (profiles) | Auto-detects resource attributes via an ordered `detectors` list with global retry settings. Includes Azure Container Apps, Azure App Service (v0.160+), and Azure Functions (v0.162+) detectors. Renamed from `resourcedetection` in v0.153.0; alias preserved. |
    | `file_storage` | `components/file_storage/README.md` | extension | — (not pipeline-scoped) | Beta | Persists component state to local disk (bbolt) so it survives a Collector restart: receiver read offsets (`storage:`), exporter persistent send queues (`sending_queue.storage:`), and stateful-processor decision caches. One bbolt file per consumer; `directory` must exist unless `create_directory: true`. Not placed in a pipeline — listed under `service.extensions:` and referenced by other components. |
    | `prometheus` (receiver) | `components/prometheus/README.md` | receiver | metrics | Beta | Scrapes Prometheus-format HTTP endpoints and converts them to OTLP metrics, embedding `prometheus/prometheus`'s scrape manager — the `config:` block is the same YAML as `prometheus.yml` (`global`, `scrape_configs`, `scrape_config_files`, service discovery, relabeling). Extra keys: `trim_metric_suffixes`, `target_allocator` (sharded targets from the OTel Operator), `api_server` (debug API). Scrape-only: rejects `remote_write`/`remote_read`/`rule_files`/alerting. Stateful, does not auto-shard across replicas. |
    | `prometheus` (exporter) | `components/prometheus_exporter/README.md` | exporter | metrics | Beta | Exposes pipeline metrics for Prometheus to scrape. In-memory accumulator expires stale series after 5m. v0.160 adds `resource_constant_labels` and deprecates `resource_to_telemetry_conversion`; `add_metric_suffixes` remains deprecated in favor of `translation_strategy`. |
    | `prometheus_remote_write` (exporter) | `components/prometheus_remote_write/README.md` | exporter | metrics | Beta | Pushes OTLP metrics as Prometheus Remote Write. Nests HTTP client settings under `http`; uses `remote_write_queue` (not `sending_queue`). v0.160 adds `resource_constant_labels` and deprecates `resource_to_telemetry_conversion`; RW2 remains gated and in development. |
    
    ## Collector-wide conventions
    
    ### Named instances
    
    Every component type supports the `type/name` pattern so the same type can be configured more than once. The pipeline references the named form:
    
    ```yaml
    processors:
      log_dedup/health-checks:
        interval: 30s
        conditions:
          - 'log.attributes["log.type"] == "health_check"'
      log_dedup/access-logs:
        interval: 10s
    
    service:
      pipelines:
        logs/health:
          processors: [log_dedup/health-checks]
        logs/access:
          processors: [log_dedup/access-logs]
    ```
    
    ### Stability levels
    
    Components publish a stability level per signal. Treat these as load-bearing when recommending production use:
    
    | Level | Use in |
    |-------|--------|
    | Development | Tests and prototypes only — breaking changes expected. |
    | Alpha | Limited, non-critical workloads — config keys can still change. |
    | Beta | Production viable — breaking changes rare. |
    | Stable | Production — backward compatibility guaranteed. |
    
    Stability now varies per component — and per signal for multi-signal components (e.g. `redaction` is Beta for traces but Alpha for logs/metrics). Don't assume a single level for the indexed set: check each component's README header metadata table for the authoritative stability and surface it when the user asks about production readiness.
    
    ### Recent renames
    
    Many components have been renamed to snake_case since v0.146.0, and renames are still landing (e.g. the `awss3` receiver → `aws_s3` in v0.162.0; receivers and exporters of one family rename separately, so check the component's `metadata.yaml` `type`). The legacy names remain as deprecated aliases — old configs keep working but new configs should use the new names. Check the upstream component README for the exact rename version before flagging a config as broken.
    
    Examples: `logdedup` → `log_dedup`, `hostmetrics` → `host_metrics`, `spanmetrics` → `span_metrics`, `servicegraph` → `service_graph`, `k8sattributes` → `k8s_attributes`, plus several `_log` and `_check` receivers.
    
    ### Pipeline placement
    
    Two rules of thumb that apply across components:
    
    - `memory_limiter` belongs first in any processor list, before anything that allocates buffers (`log_dedup`, `transform`, `tail_sampling`, …).
    - The `otlp_grpc` exporter's sending queue is enabled by default, but its `sending_queue.batch` sub-block is opt-in. Enable exporter-side batching explicitly (or with the alpha `pkg.exporterhelper.queueBatchEnabled` migration gate). The separate `batch` processor remains Beta and supported; use it when pipeline-level batching is needed and place it after data-dropping processors.
    
    ### Verification harness
    
    Each component page's **Verification** section gives a config, a `telemetrygen` command, and the expected output. To run any of them:
    
    1. Save the YAML to a file and start a collector that bundles the component — for components in the `contrib`/`k8s` distributions, `otelcol-contrib --config <file>.yaml`; for components not in any distribution, build a custom collector with the [OpenTelemetry Collector Builder (OCB)](https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/builder) first.
    2. Send telemetry with `telemetrygen` (see the `otel-telemetrygen` skill).
    3. Watch the `debug` exporter's stdout (or the `file` exporter's output) for the expected result.
    
    The Verification configs are **minimal repros**: they omit `memory_limiter` and other production scaffolding on purpose, to isolate the component under test. Don't copy them verbatim into production.
    
    ## Adding a new component to this skill
    
    When extending coverage:
    
    1. **Create the directory `components/<type>/`.** Files carry no frontmatter — only `SKILL.md` has frontmatter.
    2. **Write a lean `README.md`** — always loaded, kept small:
       - **Header metadata table** — kind, `type` name, signals, per-signal stability, distributions, Go module, upstream README link, and a rename note if the component was renamed.
       - **Description** — what the component does and the mechanism, in 1–2 tight paragraphs (push detailed mechanism/reference into `configuration.md`).
       - **Main use-cases** — "Use when" / "Avoid when".
       - **Related components** — cross-links.
       - **Details index** — a bullet list where each item links a detail file followed by an em dash and a short description of its contents (e.g. `- [Configuration](configuration.md) — config keys, defaults, validation`), so the reader loads only what a question needs.
    3. **Split the rest into on-demand detail files** under the same directory:
       - **`configuration.md`** — full config-reference table (key, type, default, validation) plus any mechanism/reference tables.
       - **`verification.md`** — a `telemetrygen` + `debug`/`file` exporter recipe that proves the documented behavior; cross-reference the `otel-telemetrygen` skill. **Verify every `telemetrygen` flag against that skill — never assert a flag that doesn't exist.** If `telemetrygen` can't produce the input the component needs, say so and point to an alternative (OTTL/`transform`, a custom emitter). Keep the config a minimal repro (see [Verification harness](#verification-harness)).
       - **`advanced.md`** — named instances, multi-pipeline setups, combinations, and edge configs.
       - **`quirks.md`** — gotchas, stability caveats, memory model, a validation-error→fix table, anti-patterns, and troubleshooting.
       - Split a heavy section into its own file when it's large (e.g. `policies.md` for a big policy catalog); merge trivial sections into a sibling rather than create a stub. Repoint any in-page anchor links that now cross files.
    4. **Use `components/log_dedup/` and `components/interval/` as reference implementations** of this structure.
    5. **Add a row to the [Component index](#component-index)** above (the `File` column points at `components/<type>/README.md`).
    6. **Update the description trigger phrases** in this file's frontmatter if the new component introduces a clearly distinct user-facing keyword.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related