implement scope configuration (#1353)

This commit is contained in:
Brett McBride 2024-08-18 20:52:43 +10:00 committed by GitHub
parent 382331e062
commit 41dd113030
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 1267 additions and 123 deletions

View File

@ -284,6 +284,7 @@ return [
'PhanAccessClassInternal',
'PhanAccessMethodInternal',
'PhanAccessPropertyInternal',
'PhanTypeMismatchPropertyReal',
'PhanTemplateTypeNotUsedInFunctionReturn',
],

View File

@ -38,3 +38,15 @@ parameters:
message: "#Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface::.*#"
paths:
- src/Config/SDK
-
message: "#Cannot call method .* on null#"
paths:
- tests/Integration/SDK/Trace
-
message: "#Property .*Logger.*config .* does not accept .*Config?#"
paths:
- src/SDK/Logs
-
message: "#.*return with type T is not subtype.*#"
paths:
- src/SDK/Common/InstrumentationScope

View File

@ -21,7 +21,7 @@ class LateBindingLogger implements LoggerInterface
($this->logger ??= ($this->factory)())->emit($logRecord);
}
public function enabled(): bool
public function isEnabled(): bool
{
return true;
}

View File

@ -17,5 +17,5 @@ interface LoggerInterface
* are about to generate a LogRecord, to avoid performing computationally expensive work.
* @experimental
*/
public function enabled(): bool;
public function isEnabled(): bool;
}

View File

@ -31,7 +31,7 @@ class NoopLogger implements LoggerInterface
{
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -13,7 +13,7 @@ use OpenTelemetry\Context\ContextInterface;
*
* @experimental
*/
interface GaugeInterface
interface GaugeInterface extends SynchronousInstrument
{
/**

View File

@ -8,7 +8,12 @@ interface Instrument
{
/**
* Determine if the instrument is enabled. Instrumentation authors SHOULD call this API each time they record a measurement.
*
* MUST return false if:
* - The MeterConfig of the Meter used to create the instrument has parameter disabled=true
* - All resolved views for the instrument are configured with the Drop Aggregation
* @experimental
* @see https://opentelemetry.io/docs/specs/otel/metrics/sdk/#instrument-enabled
*/
public function enabled(): bool;
public function isEnabled(): bool;
}

View File

@ -58,4 +58,9 @@ class LateBindingMeter implements MeterInterface
{
return ($this->meter ??= ($this->factory)())->createObservableUpDownCounter($name, $unit, $description, $advisory, $callbacks);
}
public function isEnabled(): bool
{
return true;
}
}

View File

@ -178,4 +178,6 @@ interface MeterInterface
array|callable $advisory = [],
callable ...$callbacks,
): ObservableUpDownCounterInterface;
public function isEnabled(): bool;
}

View File

@ -16,7 +16,7 @@ final class NoopCounter implements CounterInterface
// no-op
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -15,4 +15,9 @@ final class NoopGauge implements GaugeInterface
{
// no-op
}
public function isEnabled(): bool
{
return false;
}
}

View File

@ -16,7 +16,7 @@ final class NoopHistogram implements HistogramInterface
// no-op
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -56,4 +56,9 @@ final class NoopMeter implements MeterInterface
{
return new NoopObservableUpDownCounter();
}
public function isEnabled(): bool
{
return false;
}
}

View File

@ -17,7 +17,7 @@ final class NoopObservableCounter implements ObservableCounterInterface
return new NoopObservableCallback();
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -17,7 +17,7 @@ final class NoopObservableGauge implements ObservableGaugeInterface
return new NoopObservableCallback();
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -17,7 +17,7 @@ final class NoopObservableUpDownCounter implements ObservableUpDownCounterInterf
return new NoopObservableCallback();
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -16,7 +16,7 @@ final class NoopUpDownCounter implements UpDownCounterInterface
// no-op
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -21,7 +21,7 @@ class LateBindingTracer implements TracerInterface
return ($this->tracer ??= ($this->factory)())->spanBuilder($spanName);
}
public function enabled(): bool
public function isEnabled(): bool
{
return true;
}

View File

@ -24,7 +24,7 @@ final class NoopTracer implements TracerInterface
return new NoopSpanBuilder(Context::storage());
}
public function enabled(): bool
public function isEnabled(): bool
{
return false;
}

View File

@ -14,5 +14,5 @@ interface TracerInterface
* creating a new span.
* @experimental
*/
public function enabled(): bool;
public function isEnabled(): bool;
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Common\InstrumentationScope;
/**
* @internal
*/
interface Config
{
public function setDisabled(bool $disabled): void;
public function isEnabled(): bool;
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Common\InstrumentationScope;
trait ConfigTrait
{
private bool $disabled = false;
public function setDisabled(bool $disabled): void
{
$this->disabled = $disabled;
}
public function isEnabled(): bool
{
return $this->disabled === false;
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Common\InstrumentationScope;
interface Configurable
{
public function updateConfigurator(Configurator $configurator): void;
}

View File

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Common\InstrumentationScope;
use Closure;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Logs\LoggerConfig;
use OpenTelemetry\SDK\Metrics\MeterConfig;
use OpenTelemetry\SDK\Trace\TracerConfig;
use WeakMap;
/**
* @template T
*/
final class Configurator
{
/** @var Closure(InstrumentationScopeInterface): T */
private readonly Closure $factory;
/** @var WeakMap<InstrumentationScopeInterface, T> */
private WeakMap $configs;
/** @var list<ConfiguratorClosure> */
private array $configurators = [];
/**
* @param Closure(InstrumentationScopeInterface): T $factory
* @psalm-suppress PropertyTypeCoercion
*/
public function __construct(Closure $factory)
{
$this->configs = new WeakMap();
$this->factory = $factory;
}
/**
* @param Closure(T, InstrumentationScopeInterface): void $closure
*/
public function with(Closure $closure, ?string $name, ?string $version = null, ?string $schemaUrl = null): self
{
$this->configurators[] = $configurator = new ConfiguratorClosure($closure, self::namePattern($name), $version, $schemaUrl);
foreach ($this->configs as $instrumentationScope => $config) {
if ($configurator->matches($instrumentationScope)) {
($configurator->closure)($config, $instrumentationScope);
}
}
return $this;
}
/**
* @return T
*/
public function resolve(InstrumentationScopeInterface $instrumentationScope): Config
{
if ($config = $this->configs[$instrumentationScope] ?? null) {
return $config;
}
$config = ($this->factory)($instrumentationScope);
foreach ($this->configurators as $configurator) {
if ($configurator->matches($instrumentationScope)) {
($configurator->closure)($config, $instrumentationScope);
}
}
return $this->configs[$instrumentationScope] ??= $config;
}
/**
* Create a default Configurator for a LoggerConfig
* @return Configurator<LoggerConfig>
*/
public static function logger(): self
{
return (new Configurator(static fn () => new LoggerConfig()));
}
/**
* Create a default Configurator for a MeterConfig
* @return Configurator<MeterConfig>
*/
public static function meter(): self
{
return (new Configurator(static fn () => new MeterConfig()));
}
/**
* Create a default Configurator for a TracerConfig
* @return Configurator<TracerConfig>
*/
public static function tracer(): self
{
return (new Configurator(static fn () => new TracerConfig()));
}
private static function namePattern(?string $name): ?string
{
return $name !== null
? sprintf('/^%s$/', strtr(preg_quote($name, '/'), ['\\?' => '.', '\\*' => '.*']))
: null;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Common\InstrumentationScope;
use Closure;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
/**
* @internal
*/
final class ConfiguratorClosure
{
public function __construct(
public readonly Closure $closure,
private readonly ?string $name,
private readonly ?string $version,
private readonly ?string $schemaUrl,
) {
}
/**
* @psalm-suppress ArgumentTypeCoercion
*/
public function matches(InstrumentationScopeInterface $instrumentationScope): bool
{
return ($this->name === null || preg_match($this->name, $instrumentationScope->getName()))
&& ($this->version === null || $this->version === $instrumentationScope->getVersion())
&& ($this->schemaUrl === null || $this->schemaUrl === $instrumentationScope->getSchemaUrl());
}
}

View File

@ -8,6 +8,7 @@ use OpenTelemetry\API\Behavior\LogsMessagesTrait;
use OpenTelemetry\API\Logs\LoggerInterface;
use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
/**
* Note that this logger class is deliberately NOT psr-3 compatible, per spec: "Note: this document defines a log
@ -18,18 +19,26 @@ use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
class Logger implements LoggerInterface
{
use LogsMessagesTrait;
private LoggerConfig $config;
/**
* @internal
* @param Configurator<LoggerConfig>|null $configurator
*/
public function __construct(
private readonly LoggerSharedState $loggerSharedState,
private readonly InstrumentationScopeInterface $scope,
?Configurator $configurator = null,
) {
$this->config = $configurator ? $configurator->resolve($scope) : LoggerConfig::default();
}
public function emit(LogRecord $logRecord): void
{
//If a Logger is disabled, it MUST behave equivalently to No-op Logger.
if ($this->isEnabled() === false) {
return;
}
$readWriteLogRecord = new ReadWriteLogRecord($this->scope, $this->loggerSharedState, $logRecord);
// @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#onemit
$this->loggerSharedState->getProcessor()->onEmit(
@ -43,8 +52,16 @@ class Logger implements LoggerInterface
}
}
public function enabled(): bool
public function isEnabled(): bool
{
return true;
return $this->config->isEnabled();
}
/**
* @param Configurator<LoggerConfig> $configurator
*/
public function updateConfig(Configurator $configurator): void
{
$this->config = $configurator->resolve($this->scope);
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Logs;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\ConfigTrait;
class LoggerConfig implements Config
{
use ConfigTrait;
public static function default(): self
{
return new self();
}
}

View File

@ -8,23 +8,31 @@ use OpenTelemetry\API\Logs\LoggerInterface;
use OpenTelemetry\API\Logs\NoopLogger;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use WeakMap;
class LoggerProvider implements LoggerProviderInterface
{
private readonly LoggerSharedState $loggerSharedState;
private readonly WeakMap $loggers;
/**
* @param Configurator<LoggerConfig>|null $configurator
*/
public function __construct(
LogRecordProcessorInterface $processor,
private readonly InstrumentationScopeFactoryInterface $instrumentationScopeFactory,
?ResourceInfo $resource = null,
private ?Configurator $configurator = null,
) {
$this->loggerSharedState = new LoggerSharedState(
$resource ?? ResourceInfoFactory::defaultResource(),
(new LogRecordLimitsBuilder())->build(),
$processor
);
$this->loggers = new WeakMap();
}
/**
@ -36,8 +44,10 @@ class LoggerProvider implements LoggerProviderInterface
return NoopLogger::getInstance();
}
$scope = $this->instrumentationScopeFactory->create($name, $version, $schemaUrl, $attributes);
$logger = new Logger($this->loggerSharedState, $scope, $this->configurator);
$this->loggers->offsetSet($logger, null);
return new Logger($this->loggerSharedState, $scope);
return $logger;
}
public function shutdown(CancellationInterface $cancellation = null): bool
@ -54,4 +64,17 @@ class LoggerProvider implements LoggerProviderInterface
{
return new LoggerProviderBuilder();
}
/**
* Update the {@link Configurator} for a {@link LoggerProvider}, which will
* reconfigure all loggers created from the provider.
* @experimental
*/
public function updateConfigurator(Configurator $configurator): void
{
$this->configurator = $configurator;
foreach ($this->loggers as $logger => $unused) {
$logger->updateConfig($configurator);
}
}
}

View File

@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Logs;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Logs\Processor\MultiLogRecordProcessor;
use OpenTelemetry\SDK\Logs\Processor\NoopLogRecordProcessor;
use OpenTelemetry\SDK\Resource\ResourceInfo;
@ -15,6 +16,7 @@ class LoggerProviderBuilder
/** @var array<LogRecordProcessorInterface> */
private array $processors = [];
private ?ResourceInfo $resource = null;
private ?Configurator $configurator = null;
public function addLogRecordProcessor(LogRecordProcessorInterface $processor): self
{
@ -35,10 +37,18 @@ class LoggerProviderBuilder
return new LoggerProvider(
$this->buildProcessor(),
new InstrumentationScopeFactory(Attributes::factory()),
$this->resource
$this->resource,
configurator: $this->configurator ?? Configurator::logger(),
);
}
public function setConfigurator(Configurator $configurator): self
{
$this->configurator = $configurator;
return $this;
}
private function buildProcessor(): LogRecordProcessorInterface
{
return match (count($this->processors)) {

View File

@ -5,8 +5,9 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Logs;
use OpenTelemetry\API\Logs as API;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurable;
interface LoggerProviderInterface extends API\LoggerProviderInterface
interface LoggerProviderInterface extends API\LoggerProviderInterface, Configurable
{
public function shutdown(): bool;
public function forceFlush(): bool;

View File

@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Logs;
use OpenTelemetry\API\Logs\LoggerInterface;
use OpenTelemetry\API\Logs\NoopLogger;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
class NoopLoggerProvider implements LoggerProviderInterface
{
@ -30,4 +31,9 @@ class NoopLoggerProvider implements LoggerProviderInterface
{
return true;
}
public function updateConfigurator(Configurator $configurator): void
{
//no-op
}
}

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
use OpenTelemetry\API\Metrics\MeterInterface;
final class Instrument
{
public function __construct(
@ -12,6 +14,12 @@ final class Instrument
public readonly ?string $unit,
public readonly ?string $description,
public readonly array $advisory = [],
public readonly ?MeterInterface $meter = null,
) {
}
public function isEnabled(): bool
{
return $this->meter?->isEnabled() ?? true;
}
}

View File

@ -21,6 +21,9 @@ use OpenTelemetry\API\Metrics\ObservableGaugeInterface;
use OpenTelemetry\API\Metrics\ObservableUpDownCounterInterface;
use OpenTelemetry\API\Metrics\UpDownCounterInterface;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurable;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use function OpenTelemetry\SDK\Common\Util\closure;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilterInterface;
use OpenTelemetry\SDK\Metrics\MetricRegistration\MultiRegistryRegistration;
@ -34,11 +37,12 @@ use function serialize;
/**
* @internal
*/
final class Meter implements MeterInterface
final class Meter implements MeterInterface, Configurable
{
use LogsMessagesTrait;
private ?string $instrumentationScopeId = null;
private Config $config;
/**
* @param iterable<MetricSourceRegistryInterface&DefaultAggregationProviderInterface> $metricRegistries
@ -57,7 +61,9 @@ final class Meter implements MeterInterface
private readonly MetricRegistryInterface $registry,
private readonly MetricWriterInterface $writer,
private readonly ArrayAccess $destructors,
private ?Configurator $configurator = null,
) {
$this->config = $this->configurator?->resolve($this->instrumentationScope) ?? MeterConfig::default();
}
private static function dummyInstrument(): Instrument
@ -67,6 +73,15 @@ final class Meter implements MeterInterface
return $dummy ??= (new \ReflectionClass(Instrument::class))->newInstanceWithoutConstructor();
}
/**
* @internal
*/
public function updateConfigurator(Configurator $configurator): void
{
$this->configurator = $configurator;
$this->config = $configurator->resolve($this->instrumentationScope);
}
public function batchObserve(callable $callback, AsynchronousInstrument $instrument, AsynchronousInstrument ...$instruments): ObservableCallbackInterface
{
$referenceCounters = [];
@ -116,7 +131,7 @@ final class Meter implements MeterInterface
$advisory,
);
return new Counter($this->writer, $instrument, $referenceCounter);
return new Counter($this->writer, $instrument, $referenceCounter, $this);
}
public function createObservableCounter(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableCounterInterface
@ -138,7 +153,7 @@ final class Meter implements MeterInterface
$referenceCounter->acquire(true);
}
return new ObservableCounter($this->writer, $instrument, $referenceCounter, $this->destructors);
return new ObservableCounter($this->writer, $instrument, $referenceCounter, $this->destructors, $this);
}
public function createHistogram(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): HistogramInterface
@ -151,7 +166,7 @@ final class Meter implements MeterInterface
$advisory,
);
return new Histogram($this->writer, $instrument, $referenceCounter);
return new Histogram($this->writer, $instrument, $referenceCounter, $this);
}
public function createGauge(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): GaugeInterface
@ -164,7 +179,7 @@ final class Meter implements MeterInterface
$advisory,
);
return new Gauge($this->writer, $instrument, $referenceCounter);
return new Gauge($this->writer, $instrument, $referenceCounter, $this);
}
public function createObservableGauge(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableGaugeInterface
@ -186,7 +201,7 @@ final class Meter implements MeterInterface
$referenceCounter->acquire(true);
}
return new ObservableGauge($this->writer, $instrument, $referenceCounter, $this->destructors);
return new ObservableGauge($this->writer, $instrument, $referenceCounter, $this->destructors, $this);
}
public function createUpDownCounter(string $name, ?string $unit = null, ?string $description = null, array $advisory = []): UpDownCounterInterface
@ -199,7 +214,7 @@ final class Meter implements MeterInterface
$advisory,
);
return new UpDownCounter($this->writer, $instrument, $referenceCounter);
return new UpDownCounter($this->writer, $instrument, $referenceCounter, $this);
}
public function createObservableUpDownCounter(string $name, ?string $unit = null, ?string $description = null, $advisory = [], callable ...$callbacks): ObservableUpDownCounterInterface
@ -221,7 +236,12 @@ final class Meter implements MeterInterface
$referenceCounter->acquire(true);
}
return new ObservableUpDownCounter($this->writer, $instrument, $referenceCounter, $this->destructors);
return new ObservableUpDownCounter($this->writer, $instrument, $referenceCounter, $this->destructors, $this);
}
public function isEnabled(): bool
{
return $this->config->isEnabled();
}
/**
@ -245,7 +265,7 @@ final class Meter implements MeterInterface
*/
private function createSynchronousWriter(string|InstrumentType $instrumentType, string $name, ?string $unit, ?string $description, array $advisory = []): array
{
$instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory);
$instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory, $this);
$instrumentationScopeId = $this->instrumentationScopeId($this->instrumentationScope);
$instrumentId = $this->instrumentId($instrument);
@ -291,7 +311,7 @@ final class Meter implements MeterInterface
*/
private function createAsynchronousObserver(string|InstrumentType $instrumentType, string $name, ?string $unit, ?string $description, array $advisory): array
{
$instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory);
$instrument = new Instrument($instrumentType, $name, $unit, $description, $advisory, $this);
$instrumentationScopeId = $this->instrumentationScopeId($this->instrumentationScope);
$instrumentId = $this->instrumentId($instrument);

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\ConfigTrait;
class MeterConfig implements Config
{
use ConfigTrait;
public static function default(): self
{
return new self();
}
}

View File

@ -11,6 +11,7 @@ use OpenTelemetry\API\Metrics\Noop\NoopMeter;
use OpenTelemetry\Context\ContextStorageInterface;
use OpenTelemetry\SDK\Common\Attribute\AttributesFactoryInterface;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilterInterface;
use OpenTelemetry\SDK\Metrics\MetricFactory\StreamFactory;
use OpenTelemetry\SDK\Metrics\MetricRegistry\MetricRegistry;
@ -29,6 +30,7 @@ final class MeterProvider implements MeterProviderInterface
private readonly ArrayAccess $destructors;
private bool $closed = false;
private readonly WeakMap $meters;
/**
* @param iterable<MetricReaderInterface&MetricSourceRegistryInterface&DefaultAggregationProviderInterface> $metricReaders
@ -44,6 +46,7 @@ final class MeterProvider implements MeterProviderInterface
private readonly ?ExemplarFilterInterface $exemplarFilter,
private readonly StalenessHandlerFactoryInterface $stalenessHandlerFactory,
MetricFactoryInterface $metricFactory = null,
private ?Configurator $configurator = null,
) {
$this->metricFactory = $metricFactory ?? new StreamFactory();
$this->instruments = new MeterInstruments();
@ -52,6 +55,7 @@ final class MeterProvider implements MeterProviderInterface
$this->registry = $registry;
$this->writer = $registry;
$this->destructors = new WeakMap();
$this->meters = new WeakMap();
}
public function getMeter(
@ -64,7 +68,7 @@ final class MeterProvider implements MeterProviderInterface
return new NoopMeter();
}
return new Meter(
$meter = new Meter(
$this->metricFactory,
$this->resource,
$this->clock,
@ -77,7 +81,11 @@ final class MeterProvider implements MeterProviderInterface
$this->registry,
$this->writer,
$this->destructors,
$this->configurator,
);
$this->meters->offsetSet($meter, null);
return $meter;
}
public function shutdown(): bool
@ -118,4 +126,19 @@ final class MeterProvider implements MeterProviderInterface
{
return new MeterProviderBuilder();
}
/**
* Update the {@link Configurator} for a {@link MeterProvider}, which will reconfigure
* all meters created from the provider.
* @todo enabling a previous-disabled meter does not drop/recreate the underlying metric streams, so previously collected synchronous metrics will still be exported.
* @experimental
*/
public function updateConfigurator(Configurator $configurator): void
{
$this->configurator = $configurator;
foreach ($this->meters as $meter => $unused) {
$meter->updateConfigurator($configurator);
}
}
}

View File

@ -5,8 +5,10 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
use OpenTelemetry\API\Common\Time\Clock;
use OpenTelemetry\API\Common\Time\ClockInterface;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilter\WithSampledTraceExemplarFilter;
use OpenTelemetry\SDK\Metrics\Exemplar\ExemplarFilterInterface;
use OpenTelemetry\SDK\Metrics\StalenessHandler\NoopStalenessHandlerFactory;
@ -20,6 +22,8 @@ class MeterProviderBuilder
private array $metricReaders = [];
private ?ResourceInfo $resource = null;
private ?ExemplarFilterInterface $exemplarFilter = null;
private ?Configurator $configurator = null;
private ?ClockInterface $clock = null;
public function setResource(ResourceInfo $resource): self
{
@ -42,6 +46,20 @@ class MeterProviderBuilder
return $this;
}
public function setConfigurator(Configurator $configurator): self
{
$this->configurator = $configurator;
return $this;
}
public function setClock(ClockInterface $clock): self
{
$this->clock = $clock;
return $this;
}
/**
* @psalm-suppress PossiblyInvalidArgument
*/
@ -50,13 +68,14 @@ class MeterProviderBuilder
return new MeterProvider(
null,
$this->resource ?? ResourceInfoFactory::emptyResource(),
Clock::getDefault(),
$this->clock ?? Clock::getDefault(),
Attributes::factory(),
new InstrumentationScopeFactory(Attributes::factory()),
$this->metricReaders,
new CriteriaViewRegistry(),
$this->exemplarFilter ?? new WithSampledTraceExemplarFilter(),
new NoopStalenessHandlerFactory(),
configurator: $this->configurator ?? Configurator::meter(),
);
}
}

View File

@ -4,7 +4,9 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
interface MeterProviderInterface extends \OpenTelemetry\API\Metrics\MeterProviderInterface
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurable;
interface MeterProviderInterface extends \OpenTelemetry\API\Metrics\MeterProviderInterface, Configurable
{
public function shutdown(): bool;

View File

@ -39,4 +39,9 @@ final class StreamMetricSource implements MetricSourceInterface
{
$this->provider->stream->unregister($this->reader);
}
public function isEnabled(): bool
{
return $this->provider->instrument->isEnabled();
}
}

View File

@ -102,7 +102,9 @@ final class ExportingReader implements MetricReaderInterface, MetricSourceRegist
$metrics = [];
foreach ($this->sources as $source) {
$metrics[] = $source->collect();
if ($source->isEnabled()) {
$metrics[] = $source->collect();
}
}
if ($metrics === []) {

View File

@ -38,6 +38,8 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter
private array $asynchronousCallbacks = [];
/** @var array<int, list<int>> */
private array $asynchronousCallbackArguments = [];
/** @var array<int, Instrument> */
private array $instruments = [];
public function __construct(
private readonly ?ContextStorageInterface $contextStorage,
@ -55,6 +57,7 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter
$this->synchronousAggregators[$streamId] = $aggregator;
$this->instrumentToStreams[$instrumentId][$streamId] = $streamId;
$this->streamToInstrument[$streamId] = $instrumentId;
$this->instruments[$instrumentId] = $instrument;
return $streamId;
}
@ -68,6 +71,7 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter
$this->asynchronousAggregatorFactories[$streamId] = $aggregatorFactory;
$this->instrumentToStreams[$instrumentId][$streamId] = $streamId;
$this->streamToInstrument[$streamId] = $instrumentId;
$this->instruments[$instrumentId] = $instrument;
return $streamId;
}
@ -81,6 +85,7 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter
$this->asynchronousAggregatorFactories[$streamId],
$this->instrumentToStreams[$instrumentId][$streamId],
$this->streamToInstrument[$streamId],
$this->instruments[$instrumentId],
);
if (!$this->instrumentToStreams[$instrumentId]) {
unset($this->instrumentToStreams[$instrumentId]);
@ -139,10 +144,16 @@ final class MetricRegistry implements MetricRegistryInterface, MetricWriterInter
$observers = [];
$callbackIds = [];
foreach ($streamIds as $streamId) {
$instrumentId = $this->streamToInstrument[$streamId];
if (
array_key_exists($instrumentId, $this->instruments)
&& $this->instruments[$instrumentId]->meter?->isEnabled() === false
) {
continue;
}
if (!$aggregator = $this->synchronousAggregators[$streamId] ?? null) {
$aggregator = $this->asynchronousAggregatorFactories[$streamId]->create();
$instrumentId = $this->streamToInstrument[$streamId];
$observers[$instrumentId] ??= new MultiObserver($this->attributesFactory, $timestamp);
$observers[$instrumentId]->writers[] = $aggregator;
foreach ($this->instrumentToCallbacks[$instrumentId] ?? [] as $callbackId) {

View File

@ -21,4 +21,5 @@ interface MetricSourceInterface
* @return Metric collected metric
*/
public function collect(): Metric;
public function isEnabled(): bool;
}

View File

@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Metrics;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\Noop\NoopMeter;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
class NoopMeterProvider implements MeterProviderInterface
{
@ -23,4 +24,9 @@ class NoopMeterProvider implements MeterProviderInterface
{
return new NoopMeter();
}
public function updateConfigurator(Configurator $configurator): void
{
// no-op
}
}

View File

@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Metrics;
use ArrayAccess;
use function assert;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\ObservableCallbackInterface;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\SDK\Metrics\MetricRegistry\MetricWriterInterface;
@ -15,24 +16,15 @@ use OpenTelemetry\SDK\Metrics\MetricRegistry\MetricWriterInterface;
*/
trait ObservableInstrumentTrait
{
private MetricWriterInterface $writer;
private Instrument $instrument;
private ReferenceCounterInterface $referenceCounter;
private ArrayAccess $destructors;
public function __construct(
MetricWriterInterface $writer,
Instrument $instrument,
ReferenceCounterInterface $referenceCounter,
ArrayAccess $destructors,
private readonly MetricWriterInterface $writer,
private readonly Instrument $instrument,
private readonly ReferenceCounterInterface $referenceCounter,
private readonly ArrayAccess $destructors,
private readonly MeterInterface $meter,
) {
assert($this instanceof InstrumentHandle);
$this->writer = $writer;
$this->instrument = $instrument;
$this->referenceCounter = $referenceCounter;
$this->destructors = $destructors;
$this->referenceCounter->acquire();
}
@ -60,8 +52,12 @@ trait ObservableInstrumentTrait
);
}
public function enabled(): bool
public function isEnabled(): bool
{
if (!$this->meter->isEnabled()) {
return false;
}
return $this->writer->enabled($this->instrument);
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Metrics;
use function assert;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\SDK\Metrics\MetricRegistry\MetricWriterInterface;
/**
@ -15,14 +16,16 @@ trait SynchronousInstrumentTrait
private MetricWriterInterface $writer;
private Instrument $instrument;
private ReferenceCounterInterface $referenceCounter;
private MeterInterface $meter;
public function __construct(MetricWriterInterface $writer, Instrument $instrument, ReferenceCounterInterface $referenceCounter)
public function __construct(MetricWriterInterface $writer, Instrument $instrument, ReferenceCounterInterface $referenceCounter, MeterInterface $meter)
{
assert($this instanceof InstrumentHandle);
$this->writer = $writer;
$this->instrument = $instrument;
$this->referenceCounter = $referenceCounter;
$this->meter = $meter;
$this->referenceCounter->acquire();
}
@ -39,11 +42,17 @@ trait SynchronousInstrumentTrait
public function write($amount, iterable $attributes = [], $context = null): void
{
$this->writer->record($this->instrument, $amount, $attributes, $context);
if ($this->isEnabled()) {
$this->writer->record($this->instrument, $amount, $attributes, $context);
}
}
public function enabled(): bool
public function isEnabled(): bool
{
return true;
if (!$this->meter->isEnabled()) {
return false;
}
return $this->writer->enabled($this->instrument);
}
}

View File

@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK\Trace;
use OpenTelemetry\API;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
class NoopTracerProvider extends API\Trace\NoopTracerProvider implements TracerProviderInterface
{
@ -18,4 +19,8 @@ class NoopTracerProvider extends API\Trace\NoopTracerProvider implements TracerP
{
return true;
}
public function updateConfigurator(Configurator $configurator): void
{
}
}

View File

@ -8,15 +8,20 @@ use function ctype_space;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\Context\Context;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
class Tracer implements API\TracerInterface
{
public const FALLBACK_SPAN_NAME = 'empty';
private Config $config;
public function __construct(
private readonly TracerSharedState $tracerSharedState,
private readonly InstrumentationScopeInterface $instrumentationScope,
?Configurator $configurator = null,
) {
$this->config = $configurator ? $configurator->resolve($this->instrumentationScope) : TracerConfig::default();
}
/** @inheritDoc */
@ -25,8 +30,8 @@ class Tracer implements API\TracerInterface
if (ctype_space($spanName)) {
$spanName = self::FALLBACK_SPAN_NAME;
}
if ($this->tracerSharedState->hasShutdown()) {
// If a Tracer is disabled, it MUST behave equivalently to No-op Tracer
if (!$this->config->isEnabled() || $this->tracerSharedState->hasShutdown()) {
return new API\NoopSpanBuilder(Context::storage());
}
@ -42,8 +47,13 @@ class Tracer implements API\TracerInterface
return $this->instrumentationScope;
}
public function enabled(): bool
public function isEnabled(): bool
{
return true;
return $this->config->isEnabled();
}
public function updateConfig(Configurator $configurator): void
{
$this->config = $configurator->resolve($this->instrumentationScope);
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\ConfigTrait;
class TracerConfig implements Config
{
use ConfigTrait;
public static function default(): self
{
return new self();
}
}

View File

@ -11,15 +11,18 @@ use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use WeakMap;
final class TracerProvider implements TracerProviderInterface
{
private readonly TracerSharedState $tracerSharedState;
private readonly InstrumentationScopeFactoryInterface $instrumentationScopeFactory;
private readonly WeakMap $tracers;
/** @param list<SpanProcessorInterface>|SpanProcessorInterface|null $spanProcessors */
public function __construct(
@ -29,6 +32,7 @@ final class TracerProvider implements TracerProviderInterface
SpanLimits $spanLimits = null,
IdGeneratorInterface $idGenerator = null,
?InstrumentationScopeFactoryInterface $instrumentationScopeFactory = null,
private ?Configurator $configurator = null,
) {
$spanProcessors ??= [];
$spanProcessors = is_array($spanProcessors) ? $spanProcessors : [$spanProcessors];
@ -45,6 +49,7 @@ final class TracerProvider implements TracerProviderInterface
$spanProcessors
);
$this->instrumentationScopeFactory = $instrumentationScopeFactory ?? new InstrumentationScopeFactory(Attributes::factory());
$this->tracers = new WeakMap();
}
public function forceFlush(?CancellationInterface $cancellation = null): bool
@ -65,10 +70,15 @@ final class TracerProvider implements TracerProviderInterface
return NoopTracer::getInstance();
}
return new Tracer(
$scope = $this->instrumentationScopeFactory->create($name, $version, $schemaUrl, $attributes);
$tracer = new Tracer(
$this->tracerSharedState,
$this->instrumentationScopeFactory->create($name, $version, $schemaUrl, $attributes),
$scope,
$this->configurator,
);
$this->tracers->offsetSet($tracer, null);
return $tracer;
}
public function getSampler(): SamplerInterface
@ -92,4 +102,18 @@ final class TracerProvider implements TracerProviderInterface
{
return new TracerProviderBuilder();
}
/**
* Update the {@link Configurator} for a {@link TracerProvider}, which will
* reconfigure all tracers created from the provider.
* @experimental
*/
public function updateConfigurator(Configurator $configurator): void
{
$this->configurator = $configurator;
foreach ($this->tracers as $tracer => $unused) {
$tracer->updateConfig($configurator);
}
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace OpenTelemetry\SDK\Trace;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Resource\ResourceInfo;
class TracerProviderBuilder
@ -12,6 +13,7 @@ class TracerProviderBuilder
private ?array $spanProcessors = [];
private ?ResourceInfo $resource = null;
private ?SamplerInterface $sampler = null;
private ?Configurator $configurator = null;
public function addSpanProcessor(SpanProcessorInterface $spanProcessor): self
{
@ -34,12 +36,20 @@ class TracerProviderBuilder
return $this;
}
public function setConfigurator(Configurator $configurator): self
{
$this->configurator = $configurator;
return $this;
}
public function build(): TracerProviderInterface
{
return new TracerProvider(
$this->spanProcessors,
$this->sampler,
$this->resource,
configurator: $this->configurator ?? Configurator::tracer(),
);
}
}

View File

@ -6,8 +6,9 @@ namespace OpenTelemetry\SDK\Trace;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurable;
interface TracerProviderInterface extends API\TracerProviderInterface
interface TracerProviderInterface extends API\TracerProviderInterface, Configurable
{
public function forceFlush(?CancellationInterface $cancellation = null): bool;

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Benchmark;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\MeterConfig;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricExporter\NoopMetricExporter;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Metrics\MetricReaderInterface;
class MetricBench
{
private readonly MeterInterface $enabled;
private readonly MeterInterface $disabled;
private readonly MetricReaderInterface $reader;
public function __construct()
{
$exporter = new NoopMetricExporter();
$this->reader = new ExportingReader($exporter);
$meterProvider = MeterProvider::builder()
->addReader($this->reader)
->setConfigurator(
Configurator::meter()
->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'disabled')
)
->build();
$this->enabled = $meterProvider->getMeter('enabled');
$this->disabled = $meterProvider->getMeter('disabled');
}
/**
* @Revs({100, 1000})
* @Iterations(10)
* @OutputTimeUnit("microseconds")
* @ParamProviders("provideMeasurementCounts")
*/
public function bench_sync_measurements(array $params): void
{
$meter = $params['enabled'] === false ? $this->disabled : $this->enabled;
$counter = $meter->createCounter('a');
for ($i=0; $i < $params['count']; $i++) {
$counter->add(1);
}
}
/**
* @Revs({100, 1000})
* @Iterations(10)
* @OutputTimeUnit("microseconds")
* @ParamProviders("provideMeasurementCounts")
* @Groups("async")
*/
public function bench_async_measurements(array $params): void
{
$meter = $params['enabled'] === false ? $this->disabled : $this->enabled;
$meter->createObservableCounter('b', callbacks: function (ObserverInterface $o) {
$o->observe(1);
});
for ($i=0; $i < $params['count']; $i++) {
$this->reader->collect();
}
}
public function provideMeasurementCounts(): \Generator
{
yield 'disabled+10' => ['enabled' => false, 'count' => 10];
yield 'disabled+100' => ['enabled' => false, 'count' => 100];
yield 'disabled+1000' => ['enabled' => false, 'count' => 1000];
yield 'enabled+10' => ['enabled' => true, 'count' => 10];
yield 'enabled+100' => ['enabled' => true, 'count' => 100];
yield 'enabled+1000' => ['enabled' => true, 'count' => 1000];
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Integration\SDK\Logs;
use ArrayObject;
use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Logs\Exporter\InMemoryExporter;
use OpenTelemetry\SDK\Logs\LoggerConfig;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\Processor\SimpleLogRecordProcessor;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[CoversNothing]
class LoggerConfigTest extends TestCase
{
/**
* If a Logger is disabled, it MUST behave equivalently to No-op Logger
*/
#[Group('logs-compliance')]
public function test_disable_scope_then_enable(): void
{
$storage = new ArrayObject([]);
$exporter = new InMemoryExporter($storage);
$loggerProvider = LoggerProvider::builder()
->addLogRecordProcessor(new SimpleLogRecordProcessor($exporter))
->setConfigurator(
Configurator::logger()
->with(static fn (LoggerConfig $config) => $config->setDisabled(true), name: 'two')
)
->build();
$this->assertInstanceOf(LoggerProvider::class, $loggerProvider);
$logger_one = $loggerProvider->getLogger('one');
$logger_two = $loggerProvider->getLogger('two');
$logger_three = $loggerProvider->getLogger('three');
$this->assertTrue($logger_one->isEnabled());
$this->assertFalse($logger_two->isEnabled());
$this->assertTrue($logger_three->isEnabled());
$this->assertCount(0, $storage);
$logger_one->emit(new LogRecord());
$this->assertCount(1, $storage);
$logger_two->emit(new LogRecord());
$this->assertCount(1, $storage, 'no record emitted');
$loggerProvider->updateConfigurator(Configurator::logger()); //re-enable all
$this->assertTrue($logger_one->isEnabled());
$this->assertTrue($logger_two->isEnabled());
$this->assertTrue($logger_three->isEnabled());
$logger_one->emit(new LogRecord());
$this->assertCount(2, $storage);
$logger_two->emit(new LogRecord());
$this->assertCount(3, $storage, 'logger enabled, record emitted');
}
}

View File

@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Integration\SDK\Metrics;
use OpenTelemetry\API\Common\Time\TestClock;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\Data\Temporality;
use OpenTelemetry\SDK\Metrics\MeterConfig;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricExporter\InMemoryExporter;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[CoversNothing]
class MeterConfigTest extends TestCase
{
const T0 = 0;
const T1 = 1;
const T2 = 2;
public function test_disable_scopes(): void
{
$meterProvider = MeterProvider::builder()
->addReader(new ExportingReader(new InMemoryExporter()))
->setConfigurator(
Configurator::meter()
->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'two')
)
->build();
$this->assertInstanceOf(MeterProvider::class, $meterProvider);
$meter_one = $meterProvider->getMeter('one');
$meter_two = $meterProvider->getMeter('two');
$meter_three = $meterProvider->getMeter('three');
$instruments = [];
$instruments[] = $meter_two->createCounter('a');
$instruments[] = $meter_two->createObservableCounter('b');
$instruments[] = $meter_two->createUpDownCounter('c');
$instruments[] = $meter_two->createObservableUpDownCounter('d');
$instruments[] = $meter_two->createHistogram('e');
$instruments[] = $meter_two->createGauge('f');
$instruments[] = $meter_two->createObservableGauge('g');
foreach ($instruments as $id => $instrument) {
$this->assertFalse($instrument->isEnabled(), sprintf('instrument %s is disabled', $id));
}
$this->assertTrue($meter_one->isEnabled());
$this->assertFalse($meter_two->isEnabled());
$this->assertTrue($meter_three->isEnabled());
$meterProvider->updateConfigurator(Configurator::meter());
$this->assertTrue($meter_two->isEnabled());
foreach ($instruments as $instrument) {
$this->assertTrue($instrument->isEnabled());
}
}
/**
* If a Meter is disabled, it MUST behave equivalently to No-op Meter
*/
#[Group('metrics-compliance')]
public function test_metrics_not_exported_when_disabled(): void
{
$exporter = new InMemoryExporter();
$reader = new ExportingReader($exporter);
$meterProvider = MeterProvider::builder()
->addReader($reader)
->setConfigurator(
Configurator::meter()
->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: '*')
)
->build();
$meter = $meterProvider->getMeter('test');
$this->assertFalse($meter->isEnabled());
$counter = $meter->createCounter('a');
$async_counter = $meter->createObservableCounter('b', callbacks: function (ObserverInterface $o) {
$this->fail('observer from disabled meter should not have been called');
});
$this->assertFalse($counter->isEnabled());
$this->assertFalse($async_counter->isEnabled());
$counter->add(1);
$reader->collect();
$metrics = $exporter->collect(true);
$this->assertSame([], $metrics);
}
/**
* If a meter is disabled, its streams should be dropped. Any previously collected
* data will be lost. If a disabled meter is re-enabled, the streams should be
* recreated.
*/
public function test_streams_recreated_on_enable(): void
{
$this->markTestSkipped('TODO implement drop/create streams'); // @phpstan-ignore-next-line
$clock = new TestClock(self::T0);
$disabledConfigurator = Configurator::meter()
->with(static fn (MeterConfig $config) => $config->setDisabled(false), name: '*');
$exporter = new InMemoryExporter(Temporality::CUMULATIVE);
$reader = new ExportingReader($exporter);
$meterProvider = MeterProvider::builder()
->addReader($reader)
->setClock($clock)
->build();
$c = $meterProvider->getMeter('test')->createCounter('c');
//t0, meter is enabled
$c->add(1);
//t1, disable meter
$clock->setTime(self::T1);
$meterProvider->updateConfigurator($disabledConfigurator);
$c->add(10);
//t2, {sum=100, startTimestamp=t2}; must not export {sum=101, startTimestamp=t0}
$clock->setTime(self::T2);
$meterProvider->updateConfigurator(Configurator::meter());
$c->add(100);
$reader->collect();
$metrics = $exporter->collect();
$this->assertCount(1, $metrics);
$metric = $metrics[0];
$this->assertCount(1, $metric->data->dataPoints);
$dataPoint = $metric->data->dataPoints[0];
$this->assertSame(self::T2, $dataPoint->startTimestamp);
$this->assertSame(100, $dataPoint->value);
}
}

View File

@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Integration\SDK\Trace;
use ArrayObject;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\Tracer;
use OpenTelemetry\SDK\Trace\TracerConfig;
use OpenTelemetry\SDK\Trace\TracerProvider;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[CoversNothing]
class TracerConfigTest extends TestCase
{
/**
* If a Tracer is disabled, it MUST behave equivalently to No-op Tracer
*/
#[Group('trace-compliance')]
public function test_disable_scopes(): void
{
$storage = new ArrayObject();
$exporter = new InMemoryExporter($storage);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(new SimpleSpanProcessor($exporter))
->setConfigurator(
Configurator::tracer()
->with(static fn (TracerConfig $config) => $config->setDisabled(false), name: '*')
->with(static fn (TracerConfig $config) => $config->setDisabled(true), name: 'B')
)
->build();
$tracerA = $tracerProvider->getTracer('A');
$tracerB = $tracerProvider->getTracer('B');
$tracerC = $tracerProvider->getTracer('C');
$parent = $tracerA->spanBuilder('parent')->startSpan();
$this->assertTrue($parent->isRecording());
$parent->setAttribute('a', 1);
$parentScope = $parent->activate();
try {
$child = $tracerB->spanBuilder('child')->startSpan();
$child->setAttribute('b', 1);
$childScope = $child->activate();
try {
$this->assertFalse($child->isRecording());
$grandChild = $tracerC->spanBuilder('grandchild')->startSpan();
$this->assertTrue($grandChild->isRecording());
$grandChild->setAttribute('c', 1);
$grandChild->end();
} finally {
$childScope->detach();
$child->end();
}
} finally {
$parentScope->detach();
$parent->end();
}
// Only tracerA:parent and tracerC:child should be recorded
// tracerC:grandchild should list tracerA:parent as its parent
$this->assertCount(2, $storage, 'only 2 of the 3 spans were recorded');
// @var ImmutableSpan $gc
$gc = $storage->offsetGet(0);
$this->assertSame('grandchild', $gc->getName());
// @var ImmutableSpan $p
$p = $storage->offsetGet(1);
$this->assertSame('parent', $p->getName());
$this->assertSame($p->getTraceId(), $gc->getTraceId(), 'parent and grandchild are in the same trace');
$this->assertSame($gc->getParentContext()->getSpanId(), $p->getContext()->getSpanId(), 'parent is the parent of grandchild');
}
public function test_disable_scope_then_enable(): void
{
$storage = new ArrayObject();
$exporter = new InMemoryExporter($storage);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(new SimpleSpanProcessor($exporter))
->setConfigurator(
Configurator::tracer()
->with(static fn (TracerConfig $config) => $config->setDisabled(true), name: 'B')
)
->build();
$tracerA = $tracerProvider->getTracer('A');
$tracerB = $tracerProvider->getTracer('B');
$tracerC = $tracerProvider->getTracer('C');
$parent = $tracerA->spanBuilder('parent')->startSpan();
$this->assertTrue($parent->isRecording());
$parent->setAttribute('a', 1);
$parentScope = $parent->activate();
try {
$child = $tracerB->spanBuilder('child')->startSpan();
$child->setAttribute('b', 1);
$childScope = $child->activate();
$tracerProvider->updateConfigurator(Configurator::tracer()); //re-enable tracer two
$sibling = $tracerB->spanBuilder('sibling')->startSpan();
$siblingScope = $sibling->activate();
try {
$this->assertFalse($child->isRecording());
$grandChild = $tracerC->spanBuilder('grandchild')->startSpan();
$this->assertTrue($grandChild->isRecording());
$grandChild->setAttribute('c', 1);
$grandChild->end();
} finally {
$siblingScope->detach();
$sibling->end();
$childScope->detach();
$child->end();
}
} finally {
$parentScope->detach();
$parent->end();
}
// tracerA:parent, tracerB:sibling and tracerC:grandchild should be recorded
// tracerC:grandchild should list tracerB:sibling as its parent
$this->assertCount(3, $storage, 'only 3 of the 4 spans were recorded');
// @var ImmutableSpan $gc
$gc = $storage->offsetGet(0);
$this->assertSame('grandchild', $gc->getName());
// @var ImmutableSpan $s
$s = $storage->offsetGet(1);
$this->assertSame('sibling', $s->getName());
// @var ImmutableSpan $p
$p = $storage->offsetGet(2);
$this->assertSame('parent', $p->getName());
$this->assertSame($p->getTraceId(), $gc->getTraceId(), 'parent and grandchild are in the same trace');
$this->assertSame($p->getTraceId(), $s->getTraceId(), 'parent and sibling are in the same trace');
$this->assertSame($gc->getParentContext()->getSpanId(), $s->getContext()->getSpanId(), 'sibling is the parent of grandchild');
$this->assertSame($s->getParentContext()->getSpanId(), $p->getContext()->getSpanId(), 'parent is the parent of sibling');
}
public function test_reenable_enables_tracer(): void
{
$tracerProvider = TracerProvider::builder()
->setConfigurator(
Configurator::tracer()
->with(static fn (TracerConfig $config) => $config->setDisabled(true), name: 'two')
)
->build();
$tracer = $tracerProvider->getTracer(name: 'two');
$this->assertInstanceOf(Tracer::class, $tracer);
$this->assertFalse($tracer->isEnabled());
$tracerProvider->updateConfigurator(Configurator::tracer()); //reset
$this->assertTrue($tracer->isEnabled());
}
}

View File

@ -18,6 +18,6 @@ class NoopLoggerTest extends TestCase
public function test_enabled(): void
{
$this->assertFalse(NoopLogger::getInstance()->enabled());
$this->assertFalse(NoopLogger::getInstance()->isEnabled());
}
}

View File

@ -24,6 +24,6 @@ class NoopTracerTest extends TestCase
public function test_enabled(): void
{
$this->assertFalse(NoopTracer::getInstance()->enabled());
$this->assertFalse(NoopTracer::getInstance()->isEnabled());
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Common\InstrumentationScope;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\ConfigTrait;
use OpenTelemetry\SDK\Trace\TracerConfig;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
#[CoversClass(ConfigTrait::class)]
class ConfigTest extends TestCase
{
private Config $config;
public function setUp(): void
{
$this->config = new class() implements Config {
use ConfigTrait;
};
}
#[DataProvider('enabledProvider')]
public function test_is_enabled(bool $disabled, bool $expected): void
{
$this->config->setDisabled($disabled);
$this->assertSame($expected, $this->config->isEnabled());
}
public static function enabledProvider(): array
{
return [
[false, true],
[true, false],
];
}
public function test_default_is_enabled(): void
{
$config = TracerConfig::default();
$this->assertTrue($config->isEnabled());
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace OpenTelemetry\Tests\SDK\Common\InstrumentationScope;
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(Configurator::class)]
class ConfiguratorTest extends TestCase
{
private Configurator $configurator;
private InstrumentationScope $scope;
public function setUp(): void
{
$config = new class() implements Config {
private bool $disabled = false;
public function setDisabled(bool $disabled): void
{
$this->disabled = $disabled;
}
public function isEnabled(): bool
{
return $this->disabled === false;
}
};
$this->scope = new InstrumentationScope('test', '1.0', 'https://example.org/schema', $this->createMock(AttributesInterface::class));
$this->configurator = (new Configurator(static fn () => $config));
}
public function test_match_name(): void
{
$configurator = $this->configurator->with(static fn (Config $config) => $config->setDisabled(true), name: 'test');
$this->assertFalse($configurator->resolve($this->scope)->isEnabled());
}
public function test_returns_default_on_no_match(): void
{
$config = $this->configurator->resolve($this->scope);
$this->assertTrue($config->isEnabled());
}
}

View File

@ -5,9 +5,14 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
use OpenTelemetry\API\Logs\NoopLogger;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Config;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Logs\Logger;
use OpenTelemetry\SDK\Logs\LoggerConfig;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\LoggerProviderBuilder;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use PHPUnit\Framework\Attributes\CoversClass;
@ -23,19 +28,25 @@ class LoggerProviderTest extends TestCase
{
/** @var LogRecordProcessorInterface&MockObject $processor */
private LogRecordProcessorInterface $processor;
private InstrumentationScopeFactoryInterface $instrumentationScopeFactory;
private LoggerProvider $provider;
/** @var Config&MockObject */
private Config $config;
public function setUp(): void
{
$this->processor = $this->createMock(LogRecordProcessorInterface::class);
$this->instrumentationScopeFactory = $this->createMock(InstrumentationScopeFactoryInterface::class);
$instrumentationScopeFactory = $this->createMock(InstrumentationScopeFactoryInterface::class);
$instrumentationScopeFactory->method('create')->willReturn($this->createMock(InstrumentationScope::class));
$resource = $this->createMock(ResourceInfo::class);
$this->provider = new LoggerProvider($this->processor, $this->instrumentationScopeFactory, $resource);
$this->config = $this->createMock(LoggerConfig::class);
$configurator = $this->createMock(Configurator::class);
$configurator->method('resolve')->willReturn($this->config);
$this->provider = new LoggerProvider($this->processor, $instrumentationScopeFactory, $resource, $configurator);
}
public function test_get_logger(): void
{
$this->config->method('isEnabled')->willReturn(true);
$logger = $this->provider->getLogger('name');
$this->assertInstanceOf(Logger::class, $logger);
}
@ -47,6 +58,13 @@ class LoggerProviderTest extends TestCase
$this->assertInstanceOf(NoopLogger::class, $logger);
}
public function test_get_logger_if_disabled(): void
{
$this->config->method('isEnabled')->willReturn(false);
$logger = $this->provider->getLogger('name');
$this->assertFalse($logger->isEnabled());
}
public function test_shutdown_calls_processor_shutdown(): void
{
$this->processor->expects($this->once())->method('shutdown');
@ -58,4 +76,24 @@ class LoggerProviderTest extends TestCase
$this->processor->expects($this->once())->method('forceFlush');
$this->provider->forceFlush();
}
public function test_builder(): void
{
$this->assertInstanceOf(LoggerProviderBuilder::class, $this->provider->builder());
}
public function test_update_configurator_updates_loggers(): void
{
$lp = LoggerProvider::builder()->build();
$this->assertInstanceOf(LoggerProvider::class, $lp);
$one = $lp->getLogger('one');
$two = $lp->getLogger('two');
$this->assertTrue($one->isEnabled());
$this->assertTrue($two->isEnabled());
$lp->updateConfigurator(Configurator::logger()->with(static fn (LoggerConfig $config) => $config->setDisabled(true), name: '*'));
$this->assertFalse($one->isEnabled());
$this->assertFalse($two->isEnabled());
}
}

View File

@ -10,7 +10,9 @@ use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Logs\Logger;
use OpenTelemetry\SDK\Logs\LoggerConfig;
use OpenTelemetry\SDK\Logs\LoggerSharedState;
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
@ -113,6 +115,21 @@ class LoggerTest extends TestCase
public function test_enabled(): void
{
$logger = new Logger($this->sharedState, $this->scope);
$this->assertTrue($logger->enabled());
$this->assertTrue($logger->isEnabled());
$this->processor->expects($this->once())->method('onEmit');
$logger->emit(new LogRecord());
}
public function test_does_not_log_if_disabled(): void
{
$configurator = Configurator::logger()->with(static fn (LoggerConfig $config) => $config->setDisabled(true), name: 'foo');
$logger = new Logger($this->sharedState, $this->scope, $configurator);
$this->assertFalse($logger->isEnabled());
$this->processor->expects($this->never())->method('onEmit');
$logger->emit(new LogRecord());
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Metrics;
use OpenTelemetry\API\Common\Time\TestClock;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\API\Metrics\ObserverInterface;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Metrics\Aggregation\ExplicitBucketHistogramAggregation;
@ -40,17 +41,24 @@ use WeakMap;
#[CoversClass(ObservableInstrumentTrait::class)]
final class InstrumentTest extends TestCase
{
private MeterInterface $meter;
public function setUp(): void
{
$this->meter = $this->createMock(MeterInterface::class);
$this->meter->method('isEnabled')->willReturn(true);
}
public function test_counter(): void
{
$a = new MetricAggregator(null, new SumAggregation(true));
$s = new SynchronousMetricStream(new SumAggregation(true), 0);
$w = new MetricRegistry(null, Attributes::factory(), new TestClock(1));
$i = new Instrument(InstrumentType::COUNTER, 'test', null, null);
$i = new Instrument(InstrumentType::COUNTER, 'test', null, null, meter: $this->meter);
$n = $w->registerSynchronousStream($i, $s, $a);
$r = $s->register(Temporality::DELTA);
$c = new Counter($w, $i, new NoopStalenessHandler());
$c = new Counter($w, $i, new NoopStalenessHandler(), $this->meter);
$c->add(5);
$c->add(7);
$c->add(3);
@ -75,11 +83,11 @@ final class InstrumentTest extends TestCase
$a = new MetricAggregatorFactory(null, new SumAggregation(true));
$s = new SynchronousMetricStream(new SumAggregation(true), 0);
$w = new MetricRegistry(null, Attributes::factory(), new TestClock(1));
$i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null);
$i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null, meter: $this->meter);
$n = $w->registerAsynchronousStream($i, $s, $a);
$r = $s->register(Temporality::CUMULATIVE);
$c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap());
$c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap(), $this->meter);
$c->observe(static function (ObserverInterface $observer): void {
$observer->observe(5);
});
@ -104,7 +112,7 @@ final class InstrumentTest extends TestCase
$a = new MetricAggregatorFactory(null, new SumAggregation(true));
$s = new SynchronousMetricStream(new SumAggregation(true), 0);
$w = new MetricRegistry(null, Attributes::factory(), new TestClock(1));
$i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null);
$i = new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'test', null, null, meter: $this->meter);
$n = $w->registerAsynchronousStream($i, $s, $a);
$r = $s->register(Temporality::CUMULATIVE);
@ -115,7 +123,7 @@ final class InstrumentTest extends TestCase
}
};
$c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap());
$c = new ObservableCounter($w, $i, new NoopStalenessHandler(), new WeakMap(), $this->meter);
$c->observe($instance);
$instance = null;
@ -132,11 +140,11 @@ final class InstrumentTest extends TestCase
$a = new MetricAggregator(null, new SumAggregation(false));
$s = new SynchronousMetricStream(new SumAggregation(false), 0);
$w = new MetricRegistry(null, Attributes::factory(), new TestClock(1));
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null);
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter);
$n = $w->registerSynchronousStream($i, $s, $a);
$r = $s->register(Temporality::DELTA);
$c = new UpDownCounter($w, $i, new NoopStalenessHandler());
$c = new UpDownCounter($w, $i, new NoopStalenessHandler(), $this->meter);
$c->add(5);
$c->add(7);
$c->add(-8);
@ -161,11 +169,11 @@ final class InstrumentTest extends TestCase
$a = new MetricAggregator(null, new ExplicitBucketHistogramAggregation([3, 6, 9]));
$s = new SynchronousMetricStream(new ExplicitBucketHistogramAggregation([3, 6, 9]), 0);
$w = new MetricRegistry(null, Attributes::factory(), new TestClock(1));
$i = new Instrument(InstrumentType::HISTOGRAM, 'test', null, null);
$i = new Instrument(InstrumentType::HISTOGRAM, 'test', null, null, meter: $this->meter);
$n = $w->registerSynchronousStream($i, $s, $a);
$r = $s->register(Temporality::DELTA);
$h = new Histogram($w, $i, new NoopStalenessHandler());
$h = new Histogram($w, $i, new NoopStalenessHandler(), $this->meter);
$h->record(1);
$h->record(7);
$h->record(9);
@ -242,24 +250,27 @@ final class InstrumentTest extends TestCase
new ObservableCallback($writer, $referenceCounter, 1, $callbackDestructor, new stdClass());
}
public function test_synchronous_enabled(): void
public function test_synchronous_disabled_if_meter_disabled(): void
{
$w = $this->createMock(MetricWriterInterface::class);
$c = $this->createMock(ReferenceCounterInterface::class);
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null);
$counter = new Counter($w, $i, $c);
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter);
$meter = $this->createMock(MeterInterface::class);
$meter->expects($this->once())->method('isEnabled')->willReturn(false);
$counter = new Counter($w, $i, $c, $meter);
$this->assertTrue($counter->enabled());
$this->assertFalse($counter->isEnabled());
}
public function test_asynchronous_enabled(): void
public function test_asynchronous_disabled_if_meter_disabled(): void
{
$w = $this->createMock(MetricWriterInterface::class);
$w->method('enabled')->willReturn(true);
$c = $this->createMock(ReferenceCounterInterface::class);
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null);
$counter = new ObservableCounter($w, $i, $c, new WeakMap());
$i = new Instrument(InstrumentType::UP_DOWN_COUNTER, 'test', null, null, meter: $this->meter);
$meter = $this->createMock(MeterInterface::class);
$meter->expects($this->once())->method('isEnabled')->willReturn(false);
$counter = new ObservableCounter($w, $i, $c, new WeakMap(), $meter);
$this->assertTrue($counter->enabled());
$this->assertFalse($counter->isEnabled());
}
}

View File

@ -10,8 +10,12 @@ use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\DefaultAggregationProviderInterface;
use OpenTelemetry\SDK\Metrics\MeterConfig;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricExporter\InMemoryExporter;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Metrics\MetricReaderInterface;
use OpenTelemetry\SDK\Metrics\MetricSourceRegistryInterface;
use OpenTelemetry\SDK\Metrics\StalenessHandler\ImmediateStalenessHandlerFactory;
@ -104,6 +108,19 @@ final class MeterProviderTest extends TestCase
);
$this->assertTrue($meterProvider->forceFlush());
}
public function test_disable(): void
{
$meterProvider = MeterProvider::builder()->addReader(new ExportingReader(new InMemoryExporter()))->build();
$this->assertInstanceOf(MeterProvider::class, $meterProvider);
$meter = $meterProvider->getMeter('one');
$this->assertTrue($meter->isEnabled());
$counter = $meter->createCounter('A');
$this->assertTrue($counter->isEnabled());
$meterProvider->updateConfigurator(Configurator::meter()->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'one'));
$this->assertFalse($meter->isEnabled());
$this->assertFalse($counter->isEnabled());
}
}
interface MetricReaderSourceRegistryInterface extends MetricReaderInterface, MetricSourceRegistryInterface, DefaultAggregationProviderInterface

View File

@ -9,11 +9,13 @@ use OpenTelemetry\API\Common\Time\Clock;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Metrics\AggregationInterface;
use OpenTelemetry\SDK\Metrics\DefaultAggregationProviderInterface;
use OpenTelemetry\SDK\Metrics\Instrument;
use OpenTelemetry\SDK\Metrics\InstrumentType;
use OpenTelemetry\SDK\Metrics\Meter;
use OpenTelemetry\SDK\Metrics\MeterConfig;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricFactoryInterface;
use OpenTelemetry\SDK\Metrics\MetricReaderInterface;
@ -32,46 +34,48 @@ final class MeterTest extends TestCase
public function test_create_counter(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createCounter('name', 'unit', 'description');
}
public function test_create_histogram(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::HISTOGRAM, 'name', 'unit', 'description'),
new Instrument(InstrumentType::HISTOGRAM, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createHistogram('name', 'unit', 'description');
}
public function test_create_histogram_advisory(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
@ -83,6 +87,7 @@ final class MeterTest extends TestCase
's',
'Measures the duration of inbound HTTP requests.',
['ExplicitBucketBoundaries' => [0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10]],
$meter,
),
$this->anything(),
$this->anything(),
@ -90,8 +95,6 @@ final class MeterTest extends TestCase
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createHistogram(
'http.server.duration',
's',
@ -103,97 +106,97 @@ final class MeterTest extends TestCase
public function test_create_gauge(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::GAUGE, 'name', 'unit', 'description'),
new Instrument(InstrumentType::GAUGE, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createGauge('name', 'unit', 'description');
}
public function test_create_up_down_counter(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::UP_DOWN_COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::UP_DOWN_COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createUpDownCounter('name', 'unit', 'description');
}
public function test_create_observable_counter(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createAsynchronousObserver')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createObservableCounter('name', 'unit', 'description');
}
public function test_create_observable_gauge(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createAsynchronousObserver')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::ASYNCHRONOUS_GAUGE, 'name', 'unit', 'description'),
new Instrument(InstrumentType::ASYNCHRONOUS_GAUGE, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createObservableGauge('name', 'unit', 'description');
}
public function test_create_observable_up_down_counter(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createAsynchronousObserver')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::ASYNCHRONOUS_UP_DOWN_COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createObservableUpDownCounter('name', 'unit', 'description');
}
@ -201,20 +204,20 @@ final class MeterTest extends TestCase
public function test_reuses_writer_when_not_stale(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$counter = $meter->createCounter('name', 'unit', 'description');
$counter = $meter->createCounter('name', 'unit', 'description');
}
@ -222,20 +225,20 @@ final class MeterTest extends TestCase
public function test_releases_writer_on_stale(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->exactly(2))->method('createSynchronousWriter')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createCounter('name', 'unit', 'description');
$meter->createCounter('name', 'unit', 'description');
}
@ -244,19 +247,19 @@ final class MeterTest extends TestCase
public function test_reuses_observer_when_not_stale(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->once())->method('createAsynchronousObserver')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$observer = $meter->createObservableCounter('name', 'unit', 'description');
$observer = $meter->createObservableCounter('name', 'unit', 'description');
}
@ -264,19 +267,19 @@ final class MeterTest extends TestCase
public function test_releases_observer_on_stale(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$metricFactory->expects($this->exactly(2))->method('createAsynchronousObserver')
->with(
$this->anything(),
$this->anything(),
new InstrumentationScope('test', null, null, Attributes::create([])),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description'),
new Instrument(InstrumentType::ASYNCHRONOUS_COUNTER, 'name', 'unit', 'description', meter: $meter),
$this->anything(),
$this->anything(),
)
->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$meter->createObservableCounter('name', 'unit', 'description');
$meter->createObservableCounter('name', 'unit', 'description');
}
@ -373,6 +376,18 @@ final class MeterTest extends TestCase
$meter->createCounter('name');
}
public function test_update_configurator(): void
{
$metricFactory = $this->createMock(MetricFactoryInterface::class);
$metricFactory->method('createSynchronousWriter')->willReturn([]);
$meterProvider = $this->createMeterProviderForMetricFactory($metricFactory);
$meter = $meterProvider->getMeter('test');
$this->assertTrue($meter->isEnabled());
$meterProvider->updateConfigurator(Configurator::meter()->with(static fn (MeterConfig $config) => $config->setDisabled(true), name: 'test'));
$this->assertFalse($meter->isEnabled());
}
/**
* @param iterable<MetricReaderInterface&MetricSourceRegistryInterface&DefaultAggregationProviderInterface> $metricReaders
*/

View File

@ -148,6 +148,7 @@ final class ExportingReaderTest extends TestCase
$source = $this->createMock(MetricSourceInterface::class);
$source->expects($this->once())->method('collect')->willReturn($metric);
$source->method('isEnabled')->willReturn(true);
$provider = $this->createMock(MetricSourceProviderInterface::class);
$provider->expects($this->once())->method('create')->willReturn($source);
$metricMetadata = $this->createMock(MetricMetadataInterface::class);
@ -183,6 +184,7 @@ final class ExportingReaderTest extends TestCase
$provider = $this->createMock(MetricSourceProviderInterface::class);
$source = $this->createMock(MetricSourceInterface::class);
$source->method('collect')->willReturn($this->createMock(Metric::class));
$source->method('isEnabled')->willReturn(true);
$provider->method('create')->willReturn($source);
$exporter->method('temporality')->willReturn('foo');
$exporter->expects($this->once())->method('export')->willReturn(true);

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Metrics\View;
use OpenTelemetry\API\Metrics\MeterInterface;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
use OpenTelemetry\SDK\Metrics\Instrument;
@ -30,14 +31,22 @@ final class SelectionCriteriaTest extends TestCase
{
use ProphecyTrait;
private MeterInterface $meter;
public function setUp(): void
{
$this->meter = $this->createMock(MeterInterface::class);
$this->meter->method('isEnabled')->willReturn(true);
}
public function test_instrument_scope_name_criteria(): void
{
$this->assertTrue((new InstrumentationScopeNameCriteria('scopeName'))->accepts(
new Instrument(InstrumentType::COUNTER, 'name', null, null),
new Instrument(InstrumentType::COUNTER, 'name', null, null, meter: $this->meter),
new InstrumentationScope('scopeName', null, null, Attributes::create([])),
));
$this->assertFalse((new InstrumentationScopeNameCriteria('scopeName'))->accepts(
new Instrument(InstrumentType::COUNTER, 'name', null, null),
new Instrument(InstrumentType::COUNTER, 'name', null, null, meter: $this->meter),
new InstrumentationScope('scope-name', null, null, Attributes::create([])),
));
}

View File

@ -5,7 +5,9 @@ declare(strict_types=1);
namespace OpenTelemetry\Tests\Unit\SDK\Trace;
use OpenTelemetry\API\Trace\NoopTracer;
use OpenTelemetry\SDK\Common\InstrumentationScope\Configurator;
use OpenTelemetry\SDK\Trace\SamplerInterface;
use OpenTelemetry\SDK\Trace\TracerConfig;
use OpenTelemetry\SDK\Trace\TracerProvider;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
@ -93,4 +95,19 @@ class TracerProviderTest extends TestCase
$provider->getTracer('foo')
);
}
public function test_update_configurator_updates_tracers(): void
{
$tp = TracerProvider::builder()->build();
$this->assertInstanceOf(TracerProvider::class, $tp);
$one = $tp->getTracer('one');
$two = $tp->getTracer('two');
$this->assertTrue($one->isEnabled());
$this->assertTrue($two->isEnabled());
$tp->updateConfigurator(Configurator::tracer()->with(static fn (TracerConfig $config) => $config->setDisabled(true), name: '*'));
$this->assertFalse($one->isEnabled());
$this->assertFalse($two->isEnabled());
}
}

View File

@ -66,6 +66,6 @@ class TracerTest extends TestCase
public function test_enabled(): void
{
$this->assertTrue($this->tracer->enabled());
$this->assertTrue($this->tracer->isEnabled());
}
}