otel-collector-config — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited otel-collector-config (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Generate correct configs from a plain-English description, and diagnose broken ones. The Collector is a small set of concepts with a lot of sharp edges; this skill encodes the edges so the output validates on the first try.
A Collector config has four component blocks plus a service block that wires them:
receivers: # where data comes IN (otlp, prometheus, hostmetrics, filelog, kafka…)
processors: # what happens in the MIDDLE, in order (memory_limiter, batch, transform…)
exporters: # where data goes OUT (otlp, otlphttp, debug, prometheus, prometheusremotewrite…)
connectors: # bridge one pipeline's OUTPUT into another's INPUT (spanmetrics, routing…)
extensions: # collector-level features, not in the data path (health_check, pprof, zpages…)
service:
extensions: [health_check]
pipelines:
traces: { receivers: [...], processors: [...], exporters: [...] }
metrics: { receivers: [...], processors: [...], exporters: [...] }
logs: { receivers: [...], processors: [...], exporters: [...] }
telemetry: { ... } # the Collector's OWN logs/metricsThe #1 rule everyone violates: defining a component under receivers: / processors: / exporters: does nothing on its own. A component only runs if it is referenced inside a service.pipelines.<signal> list. A config can be "valid" yet do nothing because a pipeline was never wired. Always check the service block last, and confirm every component you defined is actually used (and every name used is actually defined — that one fails validation).
Pipelines are per-signal. traces, metrics, logs are separate pipelines. A receiver/exporter must support the signal of the pipeline it sits in (e.g. the prometheus receiver is metrics-only; putting it in a traces pipeline fails). You may run multiple named pipelines of the same signal (metrics/internal, metrics/app).
Many common components ship only in otelcol-contrib, not core otelcol: prometheus receiver, hostmetrics, filelog, kafka, resourcedetection, transform/filter (OTTL) processors, tail_sampling, the spanmetrics connector, prometheusremotewrite/loki exporters. If the config uses any of these, it needs the contrib image (otel/opentelemetry-collector-contrib) or a custom build via the OpenTelemetry Collector Builder (ocb). Calling for a contrib component on the core image is a startup crash, not a YAML error — flag it.
Processors run in the listed order. The canonical safe order:
processors: [memory_limiter, <resource/detection>, <sampling/filter/transform>, batch]otherwise it can't shed load. Putting batch before it defeats the purpose.
batch, and needs whole traces —don't shard the same trace across replicas without a load-balancing exporter in front.
otlp = OTLP over gRPC (default 4317). otlphttp = OTLP over HTTP (4318). Match the backend's port/protocol.debug is the console exporter (use verbosity: detailed while debugging). The old logging exporter is removed/deprecated → use `debug`./metrics endpoint for Prometheus to scrape (pull). `prometheusremotewrite` = Collector pushes to a remote-write endpoint (Mimir/Thanos/Cortex/Prometheus --web.enable-remote-write-receiver). Don't confuse pull vs push.loki exporter was removed from contrib — send OTLP to Loki's native OTLP endpoint via otlphttp (endpoint: http://loki:3100/otlp).otlp/otlphttp — the jaeger exporter is gone (Jaeger ingests OTLP natively). The jaeger receiver still exists (beta, in core+contrib) for ingesting legacy Jaeger-protocol spans during a migration; don't call it removed.memory_limiter processor in every pipeline that can be flooded.health_check extension (and wire it into service.extensions).endpoint: ${env:OTLP_ENDPOINT} (note the env: prefix — bare ${VAR} is deprecated syntax).service.telemetry.logs.level and, if needed, expose the Collector's own metrics.Never hand back a config you haven't validated. The Collector has a built-in validator that loads and type-checks the full config without starting it:
otelcol validate --config=config.yaml # core
otelcol-contrib validate --config=config.yaml # contribThe bundled scripts/validate.sh wraps this (auto-detects the contrib binary, falls back to a Docker run if no local binary). If otelcol isn't available, say so explicitly rather than claiming the config is validated.
When handed a broken or "it starts but nothing arrives" config, check in order:
service.pipelines.*? (silent no-op)batch before memory_limiter? memory_limiter not first?prometheus where prometheusremotewrite was meant (or vice-versa)?logging exporter (→debug), loki exporter (→otlphttp), jaeger exporter (→otlp; the jaeger receiver is still fine), bare ${VAR} env syntax (→${env:VAR}).tls/insecure set correctly for the environment.localhost when traffic comes from other containers (needs 0.0.0.0), or bound to 0.0.0.0 in a context where that's a security concern.State the diagnosis as "what was wrong → why it failed → the fix", then output the corrected config and validate it.
references/advanced.md)A capable model already handles the basics above. These six are the ones it gets inconsistently right; treat them as load-bearing and pull the exact patterns from references/advanced.md:
transform/filter processors). The function syntaxitself — set(attributes["x"], "y") where <cond>, delete_key(...), replace_pattern(...) — is usually recalled correctly. What isn't: there are two forms with different path syntax, and mixing them fails to parse. Flat list → prefix every path (span.attributes[...]); inside a context: span block → write paths bare (attributes[...]). Pick one per statement list. See references/advanced.md §1. Always set error_mode; regexes are RE2 (no lookahead).
in the source pipeline and a receiver in the destination pipeline. Wiring it once silently produces no metrics (or a validation error).
tail_sampling (or anysampler) runs before the spanmetrics connector, RED metrics are computed from only the surviving fraction — request rate is wrong by the sampling ratio and nothing looks broken. Fork instead: the ingest pipeline exports to [spanmetrics, forward/sampled]; a second traces pipeline receives forward/sampled, applies tail_sampling, and exports to the trace backend. spanmetrics must see 100% of spans.
default), resource attributes land only on the target_info series — so sum by (service_name) returns nothing and you must join (... * on (job, instance) group_left(service_name) target_info). On, it copies every resource attribute onto every series — including k8s.pod.name, host.id, container.id, multiplying series count per pod. Rule: enable it only after dropping the high-cardinality resource attributes; otherwise leave it off and keep target_info: enabled: true for the join. State which you chose and why.
traceID,so all spans of a trace reach the same sampler instance. Tail-sampling directly behind a plain load balancer is broken sampling. tail_sampling goes before batch.
need retry_on_failure plus a queue, but the key differs: otlp/otlphttp use sending_queue (add storage: file_storage/... when data loss is unacceptable), while `prometheusremotewrite` uses `remote_write_queue` and rejects sending_queue at startup. If validation rejects a queue key, look up that exporter's own key — do not conclude the exporter has no queue and ship it with retry only. Defaults drop data on a transient backend blip. Durability differs too: remote_write_queue is memory-only (it takes no storage:), so a restart loses the backlog — but that does not mean the exporter has no on-disk option. prometheusremotewrite has its own wal: (directory, buffer_size, truncate_frequency). For survive-a-restart, use wal here, or move the path to otlphttp + sending_queue.storage against an OTLP-capable backend.
When the request touches any of these, encode the pattern explicitly rather than trusting recall — that's the difference this skill is for.
references/components.md — curated catalog of the most-used receivers / processors / exporters / connectors with the gotcha for each, and the core-vs-contrib split.references/advanced.md — the hard parts in depth: OTTL patterns, spanmetrics dual-wiring, resource_to_telemetry_conversion, tail_sampling load-balancing topology, exporter reliability.examples/ — a before/after pair (broken → corrected, validated) and spanmetrics-config.yaml, a validated advanced config (spanmetrics connector + OTTL + remote-write labels + retry/queue).... placeholders that won't parse.service.pipelines against the blocksabove it.** Connectors are the usual miss: they appear in two pipelines, so it's easy to reference forward/x or spanmetrics without ever declaring it under connectors:. A config that needs a "one thing to fix before you deploy" footnote is a failed answer.
memory_limiter is first, why this exporter).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.