Checker for if the implementation of OpenTelemetry instrumentation is correct by scanning the code in your repository, checking environment variables, validating your Grafana token and more.
For source installs, you need Go 1.26 or higher. Prebuilt binaries are also available from the GitHub Releases page.
Download the archive for your operating system and CPU architecture from the
latest release,
extract it, and put the otel-checker binary on your PATH. Release archives
are provided for Linux, macOS, and Windows on amd64 and arm64. The statically
linked Linux archives work on both glibc- and musl-based distributions, such as
Alpine Linux. checksums.txt contains SHA-256 checksums for verification.
-
Install the
otel-checkerbinarygo install github.com/grafana/otel-checker/cmd/otel-checker@latest -
You can confirm it was installed with:
❯ ls $GOPATH/bin otel-checker
otel-checker check # all components
otel-checker check sdk # SDK only
otel-checker check collector # Collector config only
otel-checker check beyla # Beyla only
otel-checker check alloy # Grafana Alloy only
otel-checker check grafana-cloud # Grafana Cloud connectivity only
otel-checker check config # OpenTelemetry declarative config file only
otel-checker serve # web UI for a previously-saved JSON result
otel-checker explain # show explanations for every finding from a saved results file
otel-checker explain <id> # show the explanation for a single ID
otel-checker explain list # list every available explain ID
otel-checker version # print the binary version
otel-checker completion <shell> # generate shell completion script
The check command takes an optional comma-separated list of components
(check sdk,collector,beyla). With no argument, every component is checked.
Run otel-checker check --help or otel-checker check sdk --help for the full
flag set on each subcommand.
# Single-component checks
otel-checker check sdk --language=js
otel-checker check sdk --language=java --manual-instrumentation
otel-checker check collector --collector-config-path=./otel/config.yaml
otel-checker check config --config-path=./otel-config.yaml
otel-checker check grafana-cloud --language=python
# Multi-component (positional, comma-separated, no spaces)
otel-checker check sdk,collector,beyla --language=js
# Every component at once
otel-checker check --language=jsStatic checks do not prove runtime export or ingestion. Whether a finding needs runtime investigation or checks passed but data is absent, follow Runtime debugging with gcx. The guide includes the setup path if you do not yet have gcx or a telemetry backend.
Each actionable finding (errors and warnings) is tagged with a stable
explain ID. In the default table output the ID appears in the
EXPLAIN_ID column:
STATUS COMPONENT MESSAGE EXPLAIN_ID
FAIL SDK package.json missing on path /src/inst js.package-json.unreadable
Look up the guidance for any finding with:
otel-checker explain js.package-json.unreadableOr enumerate every available explain ID:
otel-checker explain listTo see explanations for every finding from a previous run, save the JSON output
once and call explain with no ID:
otel-checker check --language=js --format=json > results.json
otel-checker explainWith no ID, explain reads ./results.json by default (also ./results.yaml /
./results.yml) — the same file serve watches — and prints each explain doc
in turn, deduplicating repeated IDs and skipping findings that have none.
Pass --data=<path> to point at a different file.
The same IDs also appear as a explain_id field on every entry when using
--format=json or --format=yaml, so downstream tooling can match findings
against the explain catalog programmatically.
By default results are printed as colored text. Use --format=json or
--format=yaml for machine-readable output suitable for CI pipelines:
otel-checker check sdk --language=go --format=jsonPass --web-server to any check invocation to also serve the results at
http://127.0.0.1:8080. Override the bind address with --listen=host:port;
the default binds to loopback only. Press Ctrl-C to shut the server down
cleanly.
otel-checker serve watches a JSON or YAML results file on disk and renders it
in the web UI. The page polls every few seconds, so the moment the file is
created or rewritten the browser picks up the new content automatically.
By default, serve looks for ./results.json, then ./results.yaml, then
./results.yml in the current directory. If none exist yet, the server still
starts and shows a placeholder pointing at the expected path — useful for
keeping the UI open while a long-running pipeline writes results.
# Start the server (looks for ./results.json by default)
otel-checker serve
# In another terminal, write the file; the UI updates on the next poll
otel-checker check sdk --language=go --format=json > results.json
# Point at a specific file or a different format
otel-checker serve --data=./out/results.yamlTo validate an application deployed to a Kubernetes pod, see the Kubernetes runbook.
These checks are automatically performed for all languages and components.
- Signal exporters (
OTEL_TRACES_EXPORTER,OTEL_METRICS_EXPORTER,OTEL_LOGS_EXPORTER): must beotlp,console, or unset;noneis rejected. - Service name:
OTEL_SERVICE_NAME(orservice.nameinOTEL_RESOURCE_ATTRIBUTES). - Resource attributes checks:
- Validates the presence of recommended OpenTelemetry resource attributes
- Checks for the following attributes:
service.name(viaOTEL_SERVICE_NAMEor inOTEL_RESOURCE_ATTRIBUTES)service.namespace(e.g.,shop)deployment.environment.name(e.g.,production)service.instance.id(e.g.,checkout-123)service.version(e.g.,1.2)
- For missing attributes, provides specific recommendations with example values
- Follows the
OpenTelemetry specification
for precedence (e.g.,
OTEL_SERVICE_NAMEtakes precedence overservice.nameinOTEL_RESOURCE_ATTRIBUTES) - Example warning:
Set OTEL_RESOURCE_ATTRIBUTES="service.namespace=shop": An optional namespace for service.name
When a declarative config file is present (see the
Config section below), resource
attribute checks source from the file's resource.attributes and
resource.attributes_list blocks instead of OTEL_RESOURCE_ATTRIBUTES,
with env-var substitutions (${VAR}, ${VAR:-default}) resolved
against the process environment. Warnings then point at the config
file (config.resource-attributes.missing,
config.service-name.unset) instead of the env-var equivalents.
Run otel-checker check grafana-cloud (add --language=python if the
customer uses Python — that variant expects the URL-encoded
Authorization=Basic%20<token> form; every other language uses the
literal-space form, which is also the default when --language is
omitted):
OTEL_EXPORTER_OTLP_ENDPOINTmatcheshttps://otlp-gateway-<zone>.grafana.net/otlp.OTEL_EXPORTER_OTLP_PROTOCOLishttp/protobuf.OTEL_EXPORTER_OTLP_HEADERScontains anAuthorization=Basic <token>entry.- Credential validation: tests the endpoint with the provided token.
When a declarative config file is present, endpoint validation reads
from the file's otlp_http blocks (with env-var substitution) instead
of the env vars, and the auth-header / credential-HTTP checks are
skipped. See the Config section.
Run otel-checker check sdk --language=js:
- Node version — must be an Active LTS release (even-major, currently 22, 24, or 26). Odd-major Current releases (23, 25) are flagged as well.
@opentelemetry/apidependency inpackage.json.- Auto-instrumentation mode (default):
@opentelemetry/auto-instrumentations-nodedependency.NODE_OPTIONSincludes--require @opentelemetry/auto-instrumentations-node/register.OTEL_NODE_RESOURCE_DETECTORSset toallor coversenv,host,os,serviceinstance.
- Manual-instrumentation mode (
--manual-instrumentation):- No concurrent auto-instrumentation via
NODE_OPTIONS. - Uses
@opentelemetry/exporter-trace-otlp-http(not-otlp-proto). - Warns when
ConsoleSpanExporter/ConsoleMetricExporteris used outside debugging.
- No concurrent auto-instrumentation via
- Supported libraries:
package.jsondependencies are matched against the OpenTelemetry JS contrib registry.
Run otel-checker check sdk --language=python:
- Supported libraries:
requirements.txtdependencies are matched against the OpenTelemetry Python contrib registry.
Run otel-checker check sdk --language=dotnet:
- .NET version (>= 8.0).
- Auto-instrumentation environment variables:
CORECLR_ENABLE_PROFILING=1CORECLR_PROFILER={918728DD-259F-4A6A-AC2B-B85E1B658318}CORECLR_PROFILER_PATHis setOTEL_DOTNET_AUTO_HOMEis set
- Supported instrumentations:
.csprojNuGet dependencies are matched against the OpenTelemetry .NET auto-instrumentation library list.
Note
Only .NET 8.0 and higher are supported
Run otel-checker check sdk --language=java:
- Java version (>= 8).
- Supported libraries (discovered from a locally running Maven or Gradle,
including a wrapper if found in the current directory or a parent):
- Without
--manual-instrumentation: libraries supported by the Java Agent. - With
--manual-instrumentation: libraries with manual instrumentation support.
- Without
Run otel-checker check sdk --language=go:
- Supported libraries for manual instrumentation, based on
go.modin the current directory.
Run otel-checker check sdk --language=ruby:
- Ruby version (CRuby >= 3.3, JRuby >= 9.4, or TruffleRuby >= 22.1).
- Bundler installed.
GemfileandGemfile.lockexist.- Required gems:
opentelemetry-api,opentelemetry-sdk,opentelemetry-exporter-otlp. - Auto-instrumentation (default):
opentelemetry-instrumentation-allor at least one specific instrumentation gem (e.g.-rack,-rails).
Run otel-checker check sdk --language=php:
- PHP version (>= 8.0).
- Composer installed.
composer.jsonandcomposer.lockexist.- Required packages:
open-telemetry/api,open-telemetry/sem-conv,open-telemetry/sdk,open-telemetry/exporter-otlp. - Auto-instrumentation (default): at least one instrumentation package (e.g.
symfony,pdo,laravel,wordpress,guzzle).
Run otel-checker check collector:
config.yamlexists and is readable, and parses as valid YAML.- At least one
otlpreceiver has thehttpprotocol configured. - At least one
otlphttp/otlp_httpexporter has an endpoint matching the Grafana Cloud format (https://*.grafana.net/otlp); warns when set tolocalhost. - For each of the
traces,metrics, andlogspipelines: the exporter list contains anotlphttp/otlp_httpexporter, and the receiver list contains anotlpreceiver.
Named components (e.g. otlphttp/grafana_cloud, otlp/app) are supported.
Run otel-checker check config:
otel-config.yaml(orotel-config.yml) exists and parses as valid YAML matching the OpenTelemetry declarative configuration schema. Override the path with--config-path=<file>.file_formatis declared at the top level.- Each of the three signals (
traces,metrics,logs) declares at least oneotlp_httpendpoint under its provider (endpoint format validated by the Grafana Cloud check):- Traces:
tracer_provider.processors[].batch|simple.exporter.otlp_http.endpoint - Metrics:
meter_provider.readers[].periodic|pull.exporter.otlp_http.endpoint - Logs:
logger_provider.processors[].batch|simple.exporter.otlp_http.endpoint
- Traces:
- Recommended resource attributes are declared under
resource.attributesorresource.attributes_list. Both blocks contribute; per spec,attributesentries have higher priority thanattributes_listwhen both declare the same key. Reported IDs:config.service-name.unset,config.resource-attributes.missing. - Every key in the file is checked against the generated schema
model. Fields the schema doesn't know about (typos, deprecated
fields, or a checker schema that's out of date) surface as
config.unknown-fieldwarnings. Schema-level extension slots like custom sampler / processor variants are silently accepted per spec. The warning fires only for keys inside strict (additionalProperties: false) containers.
Environment-variable substitution
is applied to every string value the checker consumes (endpoints,
resource attribute values, attributes_list). Supported forms:
${VAR}and${env:VAR}— resolves to the process env; unresolved reference firesconfig.env-var.unresolved.${VAR:-default}— usesdefaultverbatim whenVARis unset or empty.$$— escape sequence for a literal$;$${VAR}produces the literal string${VAR}(unexpanded).
Substitution is single-pass per spec — if a resolved value itself
contains ${...}, the placeholder is left literal, NOT re-expanded.
The declarative config and OpenTelemetry environment variables are
mutually exclusive per the OTel spec. When --config-path is passed
(or a default otel-config.yaml is present):
- The Grafana Cloud check reads OTLP endpoints from the config file and skips the env-var, auth-header, and credential-HTTP checks.
- The Common Environment Variables
resource-attribute checks read from
resource:in the config file instead ofOTEL_RESOURCE_ATTRIBUTES/OTEL_SERVICE_NAME. - When running
check configalone (without also selectinggrafana-cloud), the endpoint check is driven from the config component socheck configproduces a complete report; when both components are selected, endpoint findings are reported once under Grafana Cloud.
Without a config file, all env-var-based behavior is unchanged.
Note
TBD
Note
TBD