From skills
Ollygarden's recommended patterns and anti-patterns for OpenTelemetry declarative configuration. Use when reviewing or authoring otel.yaml files, when deciding whether declarative config is right for a project, or when the user asks for "the right way" to configure OpenTelemetry. Triggers on "otel best practices", "otel anti-patterns", "is this otel config correct", "should I use declarative config".
How this skill is triggered — by the user, by Claude, or both
Slash command
/skills:ollygarden-otel-declarative-configThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Prefer declarative YAML configuration over scattered `OTEL_*` environment variables and over
Prefer declarative YAML configuration over scattered OTEL_* environment variables and over
programmatic SDK construction:
${VAR} substitution for secrets and environment-specific valuesRecommend it when the user is setting up the OTel SDK for Go, Java, or JS. These SDKs have stable or near-stable implementations.
For .NET and Python, fall back to environment variables or programmatic setup — declarative
config is still in development. To check the current per-language status, fetch the SDK
compatibility matrix listed in the otel-declarative-config reference skill's Sources of Truth.
These patterns describe the shape of a correct config. For exact field names and
exporter syntax, fetch examples/otel-sdk-config.yaml (see the otel-declarative-config
reference skill's Sources of Truth) — those details vary by schema version.
Every config MUST set these. They identify the service across all signals; without them
telemetry reports as unknown_service and cannot be grouped or owned.
| Attribute | Source | How |
|---|---|---|
service.name | author-time, stable | literal |
service.namespace | author-time, stable | literal |
service.owner.url | author-time, stable | literal — the service's repository URL |
service.version | build-time, varies | ${SERVICE_VERSION:-unknown} |
deployment.environment.name | deploy-time, varies | ${DEPLOYMENT_ENVIRONMENT_NAME:-unknown} |
resource:
attributes:
# Static — known at author time, identical across every deploy
- name: service.name
value: "checkout-service"
- name: service.namespace
value: "payments"
- name: service.owner.url
value: "https://github.com/acme/checkout-service"
# Injected — varies per build / environment
- name: service.version
value: "${SERVICE_VERSION:-unknown}"
- name: deployment.environment.name
value: "${DEPLOYMENT_ENVIRONMENT_NAME:-unknown}"
Rules:
service.name, service.namespace, service.owner.url) are
hardcoded — they are stable per service. Do NOT route them through env vars; a missing
var silently degrades the value (e.g. to unknown_service).service.version, deployment.environment.name) use
${VAR:-default}. Declarative config cannot compute values — the env var must be
supplied at build/deploy time. Always include a :-default so the attribute is never empty.SERVICE_VERSION and DEPLOYMENT_ENVIRONMENT_NAME
must be wired into the runtime (CI build arg, container env, k8s manifest). That wiring
is the deployment layer's responsibility, not this config file's — but the config MUST
list which vars it expects.service.instance.id. Omit it; let the SDK generate one per replica.
Hardcoding it makes every replica report the same id and corrupts per-instance metrics.telemetry.sdk.*, host, container, k8s, or process attributes. Resource
detectors populate these automatically. Hardcoding produces wrong data.tracer_provider:
sampler:
parent_based:
root:
trace_id_ratio_based:
ratio: ${SAMPLE_RATE:-1.0}
# Inside an exporter block (exact field names: see canonical example)
headers:
- name: api-key
value: "${API_KEY}"
endpoint: "${OTEL_ENDPOINT}"
When the instrumentation plan marks endpoints as excluded (health, readiness, and
liveness probes — see ollygarden-otel-instrumentation-planning), the declarative
mechanism is a composite sampler that drops matching spans. Do NOT reach for
OTEL_INSTRUMENTATION_* exclude-path env vars — they are ignored when a config file is
active (see Mixing env vars and config file below).
Nest a rule-based routing sampler as the root of parent_based, keeping your normal
ratio sampler as its fallback:
tracer_provider:
sampler:
parent_based:
root:
rule_based_routing:
fallback_sampler: # used when no rule matches
trace_id_ratio_based:
ratio: ${SAMPLE_RATE:-1.0}
span_kind: SERVER # only inbound server spans
rules:
- action: DROP # DROP or RECORD_AND_SAMPLE
attribute: url.path # match the request path
pattern: /health.* # align with the plan's probe path list (e.g /actuator /health /ready etc.)
Rules:
parent_based.root so only root spans are evaluated — upstream sampling
decisions still propagate to downstream services.span_kind: SERVER keeps client/producer/consumer spans untouched.pattern with the exclusion path list in the instrumentation plan
(/health, /healthz, /actuator/*, etc.).otel-declarative-config reference skill's Sources of Truth). For language-specific
availability and agent details, see the relevant ollygarden-otel-*-setup skill.parent_based wrapper# BAD: ignores upstream sampling decisions, breaks distributed traces
tracer_provider:
sampler:
trace_id_ratio_based:
ratio: 0.1
# GOOD: respects parent sampling, applies ratio only to root spans
tracer_provider:
sampler:
parent_based:
root:
trace_id_ratio_based:
ratio: 0.1
simple processor in production# BAD: exports synchronously, blocks the application
tracer_provider:
processors:
- simple:
exporter: { ... }
# GOOD: exports asynchronously in batches
tracer_provider:
processors:
- batch:
exporter: { ... }
# BAD: secrets in version control
headers:
- name: api-key
value: "sk-1234567890abcdef"
# BAD: when a config file is set, OTEL_* knobs are IGNORED — sampling,
# instrumentation exclude-patterns, propagators, everything.
export OTEL_CONFIG_FILE="/app/otel.yaml"
export OTEL_TRACES_SAMPLER="always_off" # NO effect
export OTEL_INSTRUMENTATION_HTTP_EXCLUDE_PATTERNS="" # NO effect (any OTEL_INSTRUMENTATION_*)
With the exception of ${VAR} substitution inside the YAML, the SDK ignores
environment variables when a config file is active. Anything you would normally set via
OTEL_* must move into the config file — health-check exclusion becomes the
rule_based_routing sampler above, not an env var.
otel-declarative-config skill — schema sources of truth, env-var substitution, configuration precedence.ollygarden-otel-go-setup, ollygarden-otel-java-setup, ollygarden-otel-js-setup.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub ollygarden/skills --plugin ollygarden-otel-java-setup