open-telemetry / open-telemetry/opentelemetry-php

Counter Issue not working with example code in opentelemetry docs

Open
#1,589 12 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stale
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 ObservableGauge are 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

  1. PHP SDK uses AggregationTemporality = Delta, while Prometheus prefers CUMULATIVE
  2. StartTimestamp may reset or be incomplete for Prometheus to stitch time series
  3. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.