open-telemetry / open-telemetry/opentelemetry-php
Counter Issue not working with example code in opentelemetry docs
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 912
- Forks
- 232
- Avg merge
- 7d 16h
- Merged PRs (30d)
- 4
Description
❗ Metrics not pushed to Prometheus from PHP SDK (Counter not working)
We are working on a legacy CRM system, and we’re adding full observability using OpenTelemetry, Grafana Alloy, and Prometheus.
- ✅ Traces and logs are working via Alloy and OTLP
- ✅ Metrics using
ObservableGaugeare working - ❌ Metrics using
Counter::add()are collected by Alloy, but do not show up in Prometheus
✅ Environment
| Component | Version / Details |
|---|---|
| PHP | 8.2.28 |
| OpenTelemetry PHP SDK | 1.2.4 |
| Alloy | Latest (May 2025) |
| Prometheus | v2.51+ |
| Export Protocol | OTLP over HTTP (x-protobuf) |
| Prometheus remote_write | Enabled (/api/v1/write) |
📜 Sample Code
require_once 'vendor/autoload.php';
use OpenTelemetry\Contrib\Otlp\MetricExporter;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Metrics\StalenessHandler\ImmediateStalenessHandlerFactory;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
use OpenTelemetry\SDK\Metrics\View\CriteriaViewRegistry;
$transport = (new OtlpHttpTransportFactory())->create(
'http://localhost:4318/v1/metrics',
'application/x-protobuf'
);
$reader = new ExportingReader(new MetricExporter($transport), ClockFactory::getDefault());
$resource = ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAME => 'counter-test',
]));
$provider = new MeterProvider(
null,
ResourceInfoFactory::emptyResource(),
ClockFactory::getDefault(),
Attributes::factory(),
new InstrumentationScopeFactory(Attributes::factory()),
[$reader],
new CriteriaViewRegistry(),
new WithSampledTraceExemplarFilter(),
new ImmediateStalenessHandlerFactory(),
);
$meter = $provider->getMeter('io.opentelemetry.contrib.php');
$counter = $meter->createCounter('test.counter.total', 'ms', 'A simple counter.');
$counter->add(1, ['label' => 'test']);
$reader->collect();
$provider->forceFlush();
📦 Alloy Log Output
Alloy successfully logs this:
Metric #0
-> Name: test.counter.total
-> DataType: Sum
-> IsMonotonic: true
-> AggregationTemporality: Delta
-> Value: 1
❌ Problem
Prometheus only shows:
sample: ts=..., labels={__name__="target_info"}, value=1.0
No counter metric appears.
✅ Comparison with Python SDK
Using Python with:
meter.create_counter("test.counter.total").add(1)
✅ Metric appears in Prometheus immediately.
✅ ObservableGauge Works in PHP
If we simulate the counter like this:
$meter->createObservableGauge('test.counter.total')
->observe(fn($observer) => $observer->observe(1, ['label' => 'test']));
✅ It appears in Prometheus without issue.
❓ Expected Behavior
Counter::add()should produce a flushable metric- It should show up in Prometheus after
collect()+forceFlush()
❓ Actual Behavior
- Metric is collected by Alloy and logged
- Prometheus drops or does not store the sample
- Same metric works from Python, not from PHP
🔍 Hypothesis
- PHP SDK uses
AggregationTemporality = Delta, while Prometheus prefersCUMULATIVE StartTimestampmay reset or be incomplete for Prometheus to stitch time series- Prometheus silently drops delta counters that do not follow its expectations
✅ Alloy Configuration Used
otelcol.receiver.otlp "default" {
http {
endpoint = "0.0.0.0:4318"
}
output {
metrics = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch "default" {
output {
metrics = [otelcol.exporter.prometheus.default.input]
}
}
otelcol.exporter.prometheus "default" {
forward_to = [prometheus.remote_write.default.receiver]
}
prometheus.remote_write "default" {
endpoint {
url = "http://localhost:9090/api/v1/write"
}
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No repository files or tests are named. Start by tracing the PHP SDK metrics path from Counter::add() through collect(), forceFlush(), and the OTLP MetricExporter, then compare the emitted data with the Alloy and Prometheus configuration shown here. Done means the counter appears in Prometheus after the documented flush sequence, with a regression test if the relevant test location can be identified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, prometheus
- Domain
- observability
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100