adding logs signal (#934)
* initial work on logs signal * supressing psalm error * "fixing" 7.4 segfault through trial and error, I found that it was consistently segfaulting in the sdk autoloader tests, but by running them in a different order, the failures went away. * adding logs tests * rename example per feedback * move log context injection into processors, per spec * correctly set context on logs * update logger name per feedback * adding variables, map psr3 to otel severity, fix timestamp * psr-3 loggers * tests, psr3 v3 fix * remove LogRecordDate and refactor SDK log record classes * tests, tidy, improvements * remove todos * documentation * check for valid span context * removing psr-3 compatibility, per spec and feedback * use InstrumentationScopeFactoryInterface * apply attribute limits on readablelogrecord creation per feedback, this should be more memory efficient for the processors * group log record by resource/scope * remove psr3 references from readme * ignoring psalm error * add trace context in Logger rather than processors per feedback, and following the example in java and python SIGs, we can add trace context to logs once, in the logger, rather than having processors do it. The Context passed to processors is no longer used, but retained for spec compliance.
This commit is contained in:
parent
c058a646f6
commit
615917f5d4
21
README.md
21
README.md
|
|
@ -81,7 +81,7 @@ Additional packages, demos and tools are hosted or distributed in the [OpenTelem
|
|||
|---------|--------|---------|
|
||||
| Traces | Beta | N/A |
|
||||
| Metrics | Beta | N/A |
|
||||
| Logs | N/A | N/A |
|
||||
| Logs | Alpha | N/A |
|
||||
|
||||
## Specification conformance
|
||||
We attempt to keep the [OpenTelemetry Specification Matrix](https://github.com/open-telemetry/opentelemetry-specification/blob/master/spec-compliance-matrix.md) up to date in order to show which features are available and which have not yet been implemented.
|
||||
|
|
@ -357,7 +357,24 @@ Meters must be obtained from a `MeterProvider`
|
|||
See [basic example](./examples/metrics/basic.php)
|
||||
|
||||
## Log signals
|
||||
_frozen pending delivery of tracing and metrics_
|
||||
|
||||
Loggers must be obtained from a `LoggerProvider`.
|
||||
|
||||
As logging is a mature and well-established function, the
|
||||
[OpenTelemetry approach](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/README.md#introduction)
|
||||
is a little different for this signal than traces or metrics.
|
||||
|
||||
The OpenTelemetry logger is not designed to be used directly, but rather to be integrated into existing
|
||||
logging libraries as a handler. In this way you can choose to have your application logs sent to an
|
||||
opentelemetry collector and further distributed from there.
|
||||
|
||||
The [monolog-otel-integration example](./examples/logs/features/monolog-otel-integration.php) demonstrates
|
||||
using the popular Monolog logger to send some logs to a stream (in their usual format), as well as sending
|
||||
some logs to an OpenTelemetry collector.
|
||||
|
||||
### Logging examples
|
||||
|
||||
See [getting started example](./examples/logs/getting_started.php)
|
||||
|
||||
# Versioning
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"src/Contrib/Newrelic/_register.php",
|
||||
"src/Contrib/Zipkin/_register.php",
|
||||
"src/Extension/Propagator/B3/_register.php",
|
||||
"src/SDK/Logs/Exporter/_register.php",
|
||||
"src/SDK/Metrics/MetricExporter/_register.php",
|
||||
"src/SDK/Propagation/_register.php",
|
||||
"src/SDK/Trace/SpanExporter/_register.php",
|
||||
|
|
|
|||
|
|
@ -21,3 +21,8 @@ services:
|
|||
ports:
|
||||
- 9412:9412
|
||||
- 16686:16686
|
||||
collector:
|
||||
image: otel/opentelemetry-collector-contrib
|
||||
command: [ "--config=/etc/otel-collector-config.yml" ]
|
||||
volumes:
|
||||
- ./files/collector/otel-collector-config.yml:/etc/otel-collector-config.yml
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use OpenTelemetry\API\Common\Log\LoggerHolder;
|
||||
use OpenTelemetry\API\Common\Signal\Signals;
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
|
||||
use OpenTelemetry\Contrib\Otlp\LogsExporter;
|
||||
use OpenTelemetry\Contrib\Otlp\OtlpUtil;
|
||||
use Opentelemetry\Proto\Logs\V1\SeverityNumber;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockFactory;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProvider;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
|
||||
use OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
require __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
LoggerHolder::set(
|
||||
new Logger('otel-php', [new StreamHandler(STDOUT, LogLevel::DEBUG)])
|
||||
);
|
||||
|
||||
$transport = (new GrpcTransportFactory())->create('http://collector:4317' . OtlpUtil::method(Signals::LOGS));
|
||||
$exporter = new LogsExporter($transport);
|
||||
|
||||
$loggerProvider = new LoggerProvider(
|
||||
new BatchLogsProcessor(
|
||||
$exporter,
|
||||
ClockFactory::getDefault()
|
||||
),
|
||||
new InstrumentationScopeFactory(
|
||||
(new LogRecordLimitsBuilder())->build()->getAttributeFactory()
|
||||
)
|
||||
);
|
||||
$logger = $loggerProvider->getLogger('demo', '1.0', 'http://schema.url', true, ['foo' => 'bar']);
|
||||
$eventLogger = new EventLogger($logger, 'my-domain');
|
||||
|
||||
$eventLogger->logEvent(
|
||||
'foo',
|
||||
(new LogRecord(['foo' => 'bar', 'baz' => 'bat', 'msg' => 'hello world']))
|
||||
->setSeverityText('INFO')
|
||||
->setSeverityNumber(SeverityNumber::SEVERITY_NUMBER_INFO)
|
||||
);
|
||||
|
||||
$eventLogger->logEvent(
|
||||
'foo',
|
||||
new LogRecord('otel is great')
|
||||
);
|
||||
|
||||
$loggerProvider->shutdown();
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use OpenTelemetry\API\Common\Log\LoggerHolder;
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\Contrib\Otlp\LogsExporter;
|
||||
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
|
||||
use Opentelemetry\Proto\Logs\V1\SeverityNumber;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProvider;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
|
||||
use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
require __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
LoggerHolder::set(
|
||||
new Logger('otel-php', [new StreamHandler(STDOUT, LogLevel::DEBUG)])
|
||||
);
|
||||
|
||||
$transport = (new OtlpHttpTransportFactory())->create('http://collector:4318/v1/logs', 'application/json');
|
||||
$exporter = new LogsExporter($transport);
|
||||
|
||||
$loggerProvider = new LoggerProvider(
|
||||
new SimpleLogsProcessor(
|
||||
$exporter
|
||||
),
|
||||
new InstrumentationScopeFactory(
|
||||
(new LogRecordLimitsBuilder())->build()->getAttributeFactory()
|
||||
)
|
||||
);
|
||||
$logger = $loggerProvider->getLogger('demo', '1.0', 'http://schema.url', true, ['foo' => 'bar']);
|
||||
$eventLogger = new EventLogger($logger, 'my-domain');
|
||||
|
||||
$record = (new LogRecord(['foo' => 'bar', 'baz' => 'bat', 'msg' => 'hello world']))
|
||||
->setSeverityText('INFO')
|
||||
->setSeverityNumber(SeverityNumber::SEVERITY_NUMBER_INFO);
|
||||
|
||||
$eventLogger->logEvent('foo', $record);
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockFactory;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProvider;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
|
||||
use OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor;
|
||||
use OpenTelemetry\SDK\Trace\TracerProvider;
|
||||
|
||||
require __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
$loggerProvider = new LoggerProvider(
|
||||
new BatchLogsProcessor(
|
||||
new ConsoleExporter(
|
||||
(new StreamTransportFactory())->create(STDOUT, '')
|
||||
),
|
||||
ClockFactory::getDefault()
|
||||
),
|
||||
new InstrumentationScopeFactory(
|
||||
(new LogRecordLimitsBuilder())->build()->getAttributeFactory()
|
||||
)
|
||||
);
|
||||
$tracerProvider = new TracerProvider();
|
||||
$tracer = $tracerProvider->getTracer('demo-tracer');
|
||||
|
||||
//start and activate a span
|
||||
$span = $tracer->spanBuilder('root')->startSpan();
|
||||
$scope = $span->activate();
|
||||
echo 'Trace id: ' . $span->getContext()->getTraceId() . PHP_EOL;
|
||||
echo 'Span id: ' . $span->getContext()->getSpanId() . PHP_EOL;
|
||||
|
||||
//get a logger, and emit a log record from an EventLogger. The active context (trace id + span id) will be
|
||||
//attached to the log record
|
||||
$logger = $loggerProvider->getLogger('demo', '1.0', 'http://schema.url', true, ['foo' => 'bar']);
|
||||
$eventLogger = new EventLogger($logger, 'my-domain');
|
||||
|
||||
$record = (new LogRecord(['foo' => 'bar', 'baz' => 'bat', 'msg' => 'hello world']))
|
||||
->setSeverityText('INFO')
|
||||
->setSeverityNumber(9);
|
||||
|
||||
$eventLogger->logEvent('foo', $record);
|
||||
$eventLogger->logEvent('bar', (new LogRecord('hello world')));
|
||||
|
||||
//end span
|
||||
$span->end();
|
||||
$scope->detach();
|
||||
|
||||
//shut down logger provider
|
||||
$loggerProvider->shutdown();
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use OpenTelemetry\API\Common\Instrumentation\Globals;
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\API\Logs\Map\Psr3;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* This example creates a monolog handler which integrates with opentelemetry, as described in:
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/bridge-api.md#usage
|
||||
*/
|
||||
|
||||
putenv('OTEL_PHP_AUTOLOAD_ENABLED=true');
|
||||
putenv('OTEL_METRICS_EXPORTER=none');
|
||||
putenv('OTEL_LOGS_EXPORTER=otlp');
|
||||
putenv('OTEL_LOGS_PROCESSOR=batch');
|
||||
putenv('OTEL_EXPORTER_OTLP_PROTOCOL=grpc');
|
||||
putenv('OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317');
|
||||
|
||||
require __DIR__ . '/../../../vendor/autoload.php';
|
||||
$streamHandler = new StreamHandler(STDOUT, LogLevel::DEBUG);
|
||||
$tracer = Globals::tracerProvider()->getTracer('monolog-demo');
|
||||
|
||||
//otel handler for Monolog v2, which ignores logs < INFO
|
||||
$otelHandler = new class('demo', 'demo-domain', LogLevel::INFO) extends AbstractProcessingHandler {
|
||||
private EventLogger $eventLogger;
|
||||
|
||||
public function __construct(string $name, string $domain, string $level, bool $bubble = true, ?LoggerProviderInterface $loggerProvider = null)
|
||||
{
|
||||
parent::__construct($level, $bubble);
|
||||
$loggerProvider ??= Globals::loggerProvider();
|
||||
$this->eventLogger = new EventLogger($loggerProvider->getLogger($name), $domain);
|
||||
}
|
||||
|
||||
protected function write(array $record): void
|
||||
{
|
||||
$this->eventLogger->logEvent('foo', $this->convert($record));
|
||||
}
|
||||
|
||||
private function convert(array $record): LogRecord
|
||||
{
|
||||
return (new LogRecord($record['message']))
|
||||
->setSeverityText($record['level_name'])
|
||||
->setTimestamp((int) (microtime(true) * LogRecord::NANOS_PER_SECOND))
|
||||
->setObservedTimestamp($record['datetime']->format('U') * LogRecord::NANOS_PER_SECOND)
|
||||
->setSeverityNumber(Psr3::severityNumber($record['level_name']))
|
||||
->setAttributes($record['context'] + $record['extra']);
|
||||
}
|
||||
};
|
||||
|
||||
//start a span so that logs contain span context
|
||||
$span = $tracer->spanBuilder('foo')->startSpan();
|
||||
$scope = $span->activate();
|
||||
|
||||
$monolog = new Logger('otel-php-monolog', [$otelHandler, $streamHandler]);
|
||||
|
||||
$monolog->debug('debug message');
|
||||
$monolog->info('hello world', ['extra_one' => 'value_one']);
|
||||
$monolog->alert('foo', ['extra_two' => 'value_two']);
|
||||
|
||||
$scope->detach();
|
||||
$span->end();
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProvider;
|
||||
use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
|
||||
use OpenTelemetry\SDK\Trace\TracerProvider;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
$loggerProvider = new LoggerProvider(
|
||||
new SimpleLogsProcessor(
|
||||
new ConsoleExporter()
|
||||
)
|
||||
);
|
||||
$tracerProvider = new TracerProvider();
|
||||
$tracer = $tracerProvider->getTracer('demo-tracer');
|
||||
|
||||
//start and activate a span
|
||||
$span = $tracer->spanBuilder('root')->startSpan();
|
||||
$scope = $span->activate();
|
||||
echo 'Trace id: ' . $span->getContext()->getTraceId() . PHP_EOL;
|
||||
echo 'Span id: ' . $span->getContext()->getSpanId() . PHP_EOL;
|
||||
|
||||
//get a logger, and emit a log record from an EventLogger. The active context (trace id + span id) will be
|
||||
//attached to the log record
|
||||
$logger = $loggerProvider->getLogger('demo', '1.0', 'http://schema.url', true, ['foo' => 'bar']);
|
||||
$eventLogger = new EventLogger($logger, 'my-domain');
|
||||
|
||||
$record = (new LogRecord(['foo' => 'bar', 'baz' => 'bat', 'msg' => 'hello world']))
|
||||
->setSeverityText('INFO')
|
||||
->setSeverityNumber(9);
|
||||
|
||||
$eventLogger->logEvent('foo', $record);
|
||||
|
||||
//end span
|
||||
$span->end();
|
||||
$scope->detach();
|
||||
|
|
@ -9,7 +9,7 @@ exporters:
|
|||
zipkin:
|
||||
endpoint: "http://zipkin:9411/api/v2/spans"
|
||||
logging:
|
||||
loglevel: debug
|
||||
verbosity: detailed
|
||||
|
||||
processors:
|
||||
batch:
|
||||
|
|
@ -21,11 +21,18 @@ extensions:
|
|||
|
||||
service:
|
||||
extensions: [pprof, zpages, health_check]
|
||||
telemetry:
|
||||
logs:
|
||||
level: "debug"
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp, zipkin]
|
||||
exporters: [zipkin, logging]
|
||||
exporters: [logging]
|
||||
processors: [batch]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
exporters: [logging]
|
||||
logs:
|
||||
receivers: [ otlp ]
|
||||
processors: [ batch ]
|
||||
exporters: [ logging ]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ namespace OpenTelemetry\API\Common\Instrumentation;
|
|||
use ArrayAccess;
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use OpenTelemetry\API\Logs\LoggerInterface;
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\MeterInterface;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Trace\TracerInterface;
|
||||
|
|
@ -31,6 +33,8 @@ final class CachedInstrumentation
|
|||
private ?ArrayAccess $tracers;
|
||||
/** @var ArrayAccess<MeterProviderInterface, MeterInterface>|null */
|
||||
private ?ArrayAccess $meters;
|
||||
/** @var ArrayAccess<LoggerProviderInterface, LoggerInterface>|null */
|
||||
private ?ArrayAccess $loggers;
|
||||
|
||||
public function __construct(string $name, ?string $version = null, ?string $schemaUrl = null, iterable $attributes = [])
|
||||
{
|
||||
|
|
@ -40,6 +44,7 @@ final class CachedInstrumentation
|
|||
$this->attributes = $attributes;
|
||||
$this->tracers = self::createWeakMap();
|
||||
$this->meters = self::createWeakMap();
|
||||
$this->loggers = self::createWeakMap();
|
||||
}
|
||||
|
||||
private static function createWeakMap(): ?ArrayAccess
|
||||
|
|
@ -78,4 +83,15 @@ final class CachedInstrumentation
|
|||
|
||||
return $this->meters[$meterProvider] ??= $meterProvider->getMeter($this->name, $this->version, $this->schemaUrl, $this->attributes);
|
||||
}
|
||||
public function logger(): LoggerInterface
|
||||
{
|
||||
$loggerProvider = Globals::loggerProvider();
|
||||
|
||||
if ($this->loggers === null) {
|
||||
//@todo configurable includeTraceContext?
|
||||
return $loggerProvider->getLogger($this->name, $this->version, $this->schemaUrl, true, $this->attributes);
|
||||
}
|
||||
|
||||
return $this->loggers[$loggerProvider] ??= $loggerProvider->getLogger($this->name, $this->version, $this->schemaUrl, true, $this->attributes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace OpenTelemetry\API\Common\Instrumentation;
|
||||
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Logs\NoopLoggerProvider;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\Noop\NoopMeterProvider;
|
||||
use OpenTelemetry\API\Trace\NoopTracerProvider;
|
||||
|
|
@ -25,6 +27,7 @@ final class Configurator implements ImplicitContextKeyedInterface
|
|||
private ?TracerProviderInterface $tracerProvider = null;
|
||||
private ?MeterProviderInterface $meterProvider = null;
|
||||
private ?TextMapPropagatorInterface $propagator = null;
|
||||
private ?LoggerProviderInterface $loggerProvider = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
|
|
@ -47,6 +50,7 @@ final class Configurator implements ImplicitContextKeyedInterface
|
|||
->withTracerProvider(new NoopTracerProvider())
|
||||
->withMeterProvider(new NoopMeterProvider())
|
||||
->withPropagator(new NoopTextMapPropagator())
|
||||
->withLoggerProvider(new NoopLoggerProvider())
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +72,9 @@ final class Configurator implements ImplicitContextKeyedInterface
|
|||
if ($this->propagator !== null) {
|
||||
$context = $context->with(ContextKeys::propagator(), $this->propagator);
|
||||
}
|
||||
if ($this->loggerProvider !== null) {
|
||||
$context = $context->with(ContextKeys::loggerProvider(), $this->loggerProvider);
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
|
@ -93,6 +100,13 @@ final class Configurator implements ImplicitContextKeyedInterface
|
|||
$self = clone $this;
|
||||
$self->propagator = $propagator;
|
||||
|
||||
return $self;
|
||||
}
|
||||
public function withLoggerProvider(?LoggerProviderInterface $loggerProvider): Configurator
|
||||
{
|
||||
$self = clone $this;
|
||||
$self->loggerProvider = $loggerProvider;
|
||||
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OpenTelemetry\API\Common\Instrumentation;
|
||||
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Trace\TracerProviderInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
|
|
@ -44,4 +45,14 @@ final class ContextKeys
|
|||
|
||||
return $instance ??= Context::createKey(TextMapPropagatorInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContextKeyInterface<LoggerProviderInterface>
|
||||
*/
|
||||
public static function loggerProvider(): ContextKeyInterface
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= Context::createKey(LoggerProviderInterface::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace OpenTelemetry\API\Common\Instrumentation;
|
|||
use function assert;
|
||||
use Closure;
|
||||
use const E_USER_WARNING;
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Trace\TracerProviderInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
|
|
@ -27,14 +28,17 @@ final class Globals
|
|||
private TracerProviderInterface $tracerProvider;
|
||||
private MeterProviderInterface $meterProvider;
|
||||
private TextMapPropagatorInterface $propagator;
|
||||
private LoggerProviderInterface $loggerProvider;
|
||||
|
||||
public function __construct(
|
||||
TracerProviderInterface $tracerProvider,
|
||||
MeterProviderInterface $meterProvider,
|
||||
LoggerProviderInterface $loggerProvider,
|
||||
TextMapPropagatorInterface $propagator
|
||||
) {
|
||||
$this->tracerProvider = $tracerProvider;
|
||||
$this->meterProvider = $meterProvider;
|
||||
$this->loggerProvider = $loggerProvider;
|
||||
$this->propagator = $propagator;
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +57,11 @@ final class Globals
|
|||
return Context::getCurrent()->get(ContextKeys::propagator()) ?? self::globals()->propagator;
|
||||
}
|
||||
|
||||
public static function loggerProvider(): LoggerProviderInterface
|
||||
{
|
||||
return Context::getCurrent()->get(ContextKeys::loggerProvider()) ?? self::globals()->loggerProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure(Configurator): Configurator $initializer
|
||||
*
|
||||
|
|
@ -92,10 +101,11 @@ final class Globals
|
|||
$tracerProvider = $context->get(ContextKeys::tracerProvider());
|
||||
$meterProvider = $context->get(ContextKeys::meterProvider());
|
||||
$propagator = $context->get(ContextKeys::propagator());
|
||||
$loggerProvider = $context->get(ContextKeys::loggerProvider());
|
||||
|
||||
assert(isset($tracerProvider, $meterProvider, $propagator));
|
||||
assert(isset($tracerProvider, $meterProvider, $loggerProvider, $propagator));
|
||||
|
||||
return self::$globals = new self($tracerProvider, $meterProvider, $propagator);
|
||||
return self::$globals = new self($tracerProvider, $meterProvider, $loggerProvider, $propagator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,5 +114,6 @@ final class Globals
|
|||
public static function reset(): void
|
||||
{
|
||||
self::$globals = null;
|
||||
self::$initializers = [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
class EventLogger implements EventLoggerInterface
|
||||
{
|
||||
private LoggerInterface $logger;
|
||||
private string $domain;
|
||||
|
||||
public function __construct(LoggerInterface $logger, string $domain)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
public function logEvent(string $eventName, LogRecord $logRecord): void
|
||||
{
|
||||
$logRecord->setAttributes([
|
||||
'event.name' => $eventName,
|
||||
'event.domain' => $this->domain,
|
||||
]);
|
||||
$this->logger->logRecord($logRecord);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/event-api.md#events-api-interface
|
||||
*/
|
||||
interface EventLoggerInterface
|
||||
{
|
||||
public function logEvent(string $eventName, LogRecord $logRecord): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
|
||||
class LogRecord
|
||||
{
|
||||
public const NANOS_PER_SECOND = 1_000_000_000;
|
||||
|
||||
protected ?int $timestamp = null;
|
||||
protected ?int $observedTimestamp = null;
|
||||
protected ?ContextInterface $context = null;
|
||||
protected int $severityNumber = 0;
|
||||
protected ?string $severityText = null;
|
||||
protected $body = null;
|
||||
protected array $attributes = [];
|
||||
|
||||
public function __construct($body = null)
|
||||
{
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function setTimestamp(int $timestamp): self
|
||||
{
|
||||
$this->timestamp = $timestamp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setContext(?ContextInterface $context = null): self
|
||||
{
|
||||
$this->context = $context;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSeverityNumber(int $severityNumber): self
|
||||
{
|
||||
$this->severityNumber = $severityNumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSeverityText(string $severityText): self
|
||||
{
|
||||
$this->severityText = $severityText;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAttributes(iterable $attributes): self
|
||||
{
|
||||
foreach ($attributes as $name => $value) {
|
||||
$this->setAttribute($name, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAttribute(string $name, $value): self
|
||||
{
|
||||
$this->attributes[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBody($body = null): self
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setObservedTimestamp(int $observedTimestamp = null): self
|
||||
{
|
||||
$this->observedTimestamp = $observedTimestamp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
interface LoggerInterface
|
||||
{
|
||||
public function logRecord(LogRecord $logRecord): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/bridge-api.md#get-a-logger
|
||||
*/
|
||||
interface LoggerProviderInterface
|
||||
{
|
||||
public function getLogger(
|
||||
string $name,
|
||||
?string $version = null,
|
||||
?string $schemaUrl = null,
|
||||
bool $includeTraceContext = true,
|
||||
iterable $attributes = [] //instrumentation scope attributes
|
||||
): LoggerInterface;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs\Map;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
class Psr3
|
||||
{
|
||||
/**
|
||||
* Maps PSR-3 severity level (string) to the appropriate opentelemetry severity number
|
||||
*
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model-appendix.md#appendix-b-severitynumber-example-mappings
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-severitynumber
|
||||
*/
|
||||
public static function severityNumber(string $level): int
|
||||
{
|
||||
switch (strtolower($level)) {
|
||||
case LogLevel::DEBUG:
|
||||
return 5;
|
||||
case LogLevel::INFO:
|
||||
return 9;
|
||||
case LogLevel::NOTICE:
|
||||
return 10;
|
||||
case LogLevel::WARNING:
|
||||
return 13;
|
||||
case LogLevel::ERROR:
|
||||
return 17;
|
||||
case LogLevel::CRITICAL:
|
||||
return 18;
|
||||
case LogLevel::ALERT:
|
||||
return 19;
|
||||
case LogLevel::EMERGENCY:
|
||||
return 21;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
use Psr\Log\LoggerTrait;
|
||||
|
||||
class NoopLogger implements LoggerInterface
|
||||
{
|
||||
use LoggerTrait;
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function logRecord(LogRecord $logRecord): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function log($level, $message, array $context = []): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\API\Logs;
|
||||
|
||||
class NoopLoggerProvider implements LoggerProviderInterface
|
||||
{
|
||||
public static function getInstance(): self
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= new self();
|
||||
}
|
||||
|
||||
public function getLogger(string $name, ?string $version = null, ?string $schemaUrl = null, bool $includeTraceContext = true, iterable $attributes = []): LoggerInterface
|
||||
{
|
||||
return NoopLogger::getInstance();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Contrib\Otlp;
|
||||
|
||||
use Opentelemetry\Proto\Collector\Logs\V1\ExportLogsServiceRequest;
|
||||
use Opentelemetry\Proto\Common\V1\InstrumentationScope;
|
||||
use Opentelemetry\Proto\Common\V1\KeyValue;
|
||||
use Opentelemetry\Proto\Logs\V1\LogRecord;
|
||||
use Opentelemetry\Proto\Logs\V1\ResourceLogs;
|
||||
use Opentelemetry\Proto\Logs\V1\ScopeLogs;
|
||||
use Opentelemetry\Proto\Resource\V1\Resource as Resource_;
|
||||
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
|
||||
class LogsConverter
|
||||
{
|
||||
/**
|
||||
* @param iterable<ReadableLogRecord> $logs
|
||||
* @psalm-suppress InvalidArgument
|
||||
*/
|
||||
public function convert(iterable $logs): ExportLogsServiceRequest
|
||||
{
|
||||
$pExportLogsServiceRequest = new ExportLogsServiceRequest();
|
||||
$scopeLogs = [];
|
||||
$resourceLogs = [];
|
||||
$resourceCache = [];
|
||||
$scopeCache = [];
|
||||
|
||||
foreach ($logs as $log) {
|
||||
$resource = $log->getResource();
|
||||
$instrumentationScope = $log->getInstrumentationScope();
|
||||
|
||||
$resourceId = $resourceCache[spl_object_id($resource)] ??= serialize([
|
||||
$resource->getSchemaUrl(),
|
||||
$resource->getAttributes()->toArray(),
|
||||
$resource->getAttributes()->getDroppedAttributesCount(),
|
||||
]);
|
||||
$instrumentationScopeId = $scopeCache[spl_object_id($instrumentationScope)] ??= serialize([
|
||||
$instrumentationScope->getName(),
|
||||
$instrumentationScope->getVersion(),
|
||||
$instrumentationScope->getSchemaUrl(),
|
||||
$instrumentationScope->getAttributes()->toArray(),
|
||||
$instrumentationScope->getAttributes()->getDroppedAttributesCount(),
|
||||
]);
|
||||
|
||||
if (($pResourceLogs = $resourceLogs[$resourceId] ?? null) === null) {
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$pExportLogsServiceRequest->getResourceLogs()[]
|
||||
= $resourceLogs[$resourceId]
|
||||
= $pResourceLogs
|
||||
= $this->convertResourceLogs($resource);
|
||||
}
|
||||
|
||||
if (($pScopeLogs = $scopeLogs[$resourceId][$instrumentationScopeId] ?? null) === null) {
|
||||
$pResourceLogs->getScopeLogs()[]
|
||||
= $scopeLogs[$resourceId][$instrumentationScopeId]
|
||||
= $pScopeLogs
|
||||
= $this->convertInstrumentationScope($instrumentationScope);
|
||||
}
|
||||
|
||||
$pScopeLogs->getLogRecords()[] = $this->convertLogRecord($log);
|
||||
}
|
||||
|
||||
return $pExportLogsServiceRequest;
|
||||
}
|
||||
|
||||
private function convertLogRecord(ReadableLogRecord $record): LogRecord
|
||||
{
|
||||
$pLogRecord = new LogRecord();
|
||||
$pLogRecord->setBody(AttributesConverter::convertAnyValue($record->getBody()));
|
||||
$pLogRecord->setTimeUnixNano($record->getTimestamp() ?? 0);
|
||||
$pLogRecord->setObservedTimeUnixNano($record->getObservedTimestamp() ?? 0);
|
||||
$spanContext = $record->getSpanContext();
|
||||
if ($spanContext !== null && $spanContext->isValid()) {
|
||||
$pLogRecord->setTraceId($spanContext->getTraceIdBinary());
|
||||
$pLogRecord->setSpanId($spanContext->getSpanIdBinary());
|
||||
$pLogRecord->setFlags($spanContext->getTraceFlags());
|
||||
}
|
||||
$severityNumber = $record->getSeverityNumber();
|
||||
if ($severityNumber !== null) {
|
||||
$pLogRecord->setSeverityNumber($severityNumber);
|
||||
}
|
||||
$severityText = $record->getSeverityText();
|
||||
if ($severityText !== null) {
|
||||
$pLogRecord->setSeverityText($severityText);
|
||||
}
|
||||
$this->setAttributes($pLogRecord, $record->getAttributes());
|
||||
$pLogRecord->setDroppedAttributesCount($record->getAttributes()->getDroppedAttributesCount());
|
||||
|
||||
return $pLogRecord;
|
||||
}
|
||||
|
||||
private function convertInstrumentationScope(InstrumentationScopeInterface $instrumentationScope): ScopeLogs
|
||||
{
|
||||
$pScopeLogs = new ScopeLogs();
|
||||
$pInstrumentationScope = new InstrumentationScope();
|
||||
$pInstrumentationScope->setName($instrumentationScope->getName());
|
||||
$pInstrumentationScope->setVersion((string) $instrumentationScope->getVersion());
|
||||
$this->setAttributes($pInstrumentationScope, $instrumentationScope->getAttributes());
|
||||
$pInstrumentationScope->setDroppedAttributesCount($instrumentationScope->getAttributes()->getDroppedAttributesCount());
|
||||
$pScopeLogs->setScope($pInstrumentationScope);
|
||||
|
||||
return $pScopeLogs;
|
||||
}
|
||||
|
||||
private function convertResourceLogs(ResourceInfo $resource): ResourceLogs
|
||||
{
|
||||
$pResourceLogs = new ResourceLogs();
|
||||
$pResource = new Resource_();
|
||||
$this->setAttributes($pResource, $resource->getAttributes());
|
||||
$pResource->setDroppedAttributesCount($resource->getAttributes()->getDroppedAttributesCount());
|
||||
$pResourceLogs->setResource($pResource);
|
||||
|
||||
return $pResourceLogs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Resource_|LogRecord|InstrumentationScope $pElement
|
||||
*/
|
||||
private function setAttributes($pElement, AttributesInterface $attributes): void
|
||||
{
|
||||
foreach ($attributes as $key => $value) {
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$pElement->getAttributes()[] = (new KeyValue())
|
||||
->setKey($key)
|
||||
->setValue(AttributesConverter::convertAnyValue($value));
|
||||
}
|
||||
$pElement->setDroppedAttributesCount($attributes->getDroppedAttributesCount());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Contrib\Otlp;
|
||||
|
||||
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
|
||||
use Opentelemetry\Proto\Collector\Logs\V1\ExportLogsServiceResponse;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\FutureInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @psalm-import-type SUPPORTED_CONTENT_TYPES from ProtobufSerializer
|
||||
*/
|
||||
class LogsExporter implements LogRecordExporterInterface
|
||||
{
|
||||
use LogsMessagesTrait;
|
||||
|
||||
private TransportInterface $transport;
|
||||
private ProtobufSerializer $serializer;
|
||||
|
||||
/**
|
||||
* @psalm-param TransportInterface<SUPPORTED_CONTENT_TYPES> $transport
|
||||
*/
|
||||
public function __construct(TransportInterface $transport)
|
||||
{
|
||||
$this->transport = $transport;
|
||||
$this->serializer = ProtobufSerializer::forTransport($transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<ReadableLogRecord> $batch
|
||||
*/
|
||||
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
|
||||
{
|
||||
return $this->transport
|
||||
->send($this->serializer->serialize((new LogsConverter())->convert($batch)), $cancellation)
|
||||
->map(function (?string $payload): bool {
|
||||
if ($payload === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$serviceResponse = new ExportLogsServiceResponse();
|
||||
$this->serializer->hydrate($serviceResponse, $payload);
|
||||
|
||||
$partialSuccess = $serviceResponse->getPartialSuccess();
|
||||
if ($partialSuccess !== null && $partialSuccess->getRejectedLogRecords()) {
|
||||
self::logError('Export partial success', [
|
||||
'rejected_logs' => $partialSuccess->getRejectedLogRecords(),
|
||||
'error_message' => $partialSuccess->getErrorMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($partialSuccess !== null && $partialSuccess->getErrorMessage()) {
|
||||
self::logWarning('Export success with warnings/suggestions', ['error_message' => $partialSuccess->getErrorMessage()]);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->catch(static function (Throwable $throwable): bool {
|
||||
self::logError('Export failure', ['exception' => $throwable]);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->transport->forceFlush($cancellation);
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->transport->shutdown($cancellation);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Contrib\Otlp;
|
||||
|
||||
use OpenTelemetry\API\Common\Signal\Signals;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Defaults;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use OpenTelemetry\SDK\Common\Otlp\HttpEndpointResolver;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Registry;
|
||||
|
||||
class LogsExporterFactory implements LogRecordExporterFactoryInterface
|
||||
{
|
||||
private const DEFAULT_COMPRESSION = 'none';
|
||||
|
||||
private ?TransportFactoryInterface $transportFactory;
|
||||
|
||||
public function __construct(?TransportFactoryInterface $transportFactory = null)
|
||||
{
|
||||
$this->transportFactory = $transportFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
*/
|
||||
public function create(): LogRecordExporterInterface
|
||||
{
|
||||
$protocol = Configuration::has(Variables::OTEL_EXPORTER_OTLP_LOGS_PROTOCOL)
|
||||
? Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_LOGS_PROTOCOL)
|
||||
: Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_PROTOCOL);
|
||||
|
||||
return new LogsExporter($this->buildTransport($protocol));
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
private function buildTransport(string $protocol): TransportInterface
|
||||
{
|
||||
$endpoint = $this->getEndpoint($protocol);
|
||||
|
||||
$headers = Configuration::has(Variables::OTEL_EXPORTER_OTLP_LOGS_HEADERS)
|
||||
? Configuration::getMap(Variables::OTEL_EXPORTER_OTLP_LOGS_HEADERS)
|
||||
: Configuration::getMap(Variables::OTEL_EXPORTER_OTLP_HEADERS);
|
||||
$headers += OtlpUtil::getUserAgentHeader();
|
||||
$compression = $this->getCompression();
|
||||
|
||||
$factoryClass = Registry::transportFactory($protocol);
|
||||
$factory = $this->transportFactory ?: new $factoryClass();
|
||||
|
||||
return $factory->create(
|
||||
$endpoint,
|
||||
Protocols::contentType($protocol),
|
||||
$headers,
|
||||
$compression,
|
||||
);
|
||||
}
|
||||
|
||||
private function getCompression(): string
|
||||
{
|
||||
return Configuration::has(Variables::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION) ?
|
||||
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION) :
|
||||
Configuration::getEnum(Variables::OTEL_EXPORTER_OTLP_COMPRESSION, self::DEFAULT_COMPRESSION);
|
||||
}
|
||||
|
||||
private function getEndpoint(string $protocol): string
|
||||
{
|
||||
if (Configuration::has(Variables::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)) {
|
||||
return Configuration::getString(Variables::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT);
|
||||
}
|
||||
$endpoint = Configuration::has(Variables::OTEL_EXPORTER_OTLP_ENDPOINT)
|
||||
? Configuration::getString(Variables::OTEL_EXPORTER_OTLP_ENDPOINT)
|
||||
: Defaults::OTEL_EXPORTER_OTLP_ENDPOINT;
|
||||
if ($protocol === Protocols::GRPC) {
|
||||
return $endpoint . OtlpUtil::method(Signals::LOGS);
|
||||
}
|
||||
|
||||
return HttpEndpointResolver::create()->resolveToString($endpoint, Signals::LOGS);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,3 +5,5 @@ declare(strict_types=1);
|
|||
\OpenTelemetry\SDK\Registry::registerMetricExporterFactory('otlp', \OpenTelemetry\Contrib\Otlp\MetricExporterFactory::class);
|
||||
|
||||
\OpenTelemetry\SDK\Registry::registerTransportFactory('http', \OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory::class);
|
||||
|
||||
\OpenTelemetry\SDK\Registry::registerLogRecordExporterFactory('otlp', \OpenTelemetry\Contrib\Otlp\LogsExporterFactory::class);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ final class AttributesBuilder implements AttributesBuilderInterface
|
|||
}
|
||||
|
||||
$this->attributes[$offset] = $this->normalizeValue($value);
|
||||
//@todo "There SHOULD be a message printed in the SDK's log to indicate to the user that an attribute was
|
||||
// discarded due to such a limit. To prevent excessive logging, the message MUST be printed at most
|
||||
// once per <thing> (i.e., not per discarded attribute)."
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ interface Defaults
|
|||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits
|
||||
*/
|
||||
public const OTEL_ATTRIBUTE_COUNT_LIMIT = 128;
|
||||
public const OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = PHP_INT_MAX;
|
||||
/**
|
||||
* Span Limits
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#span-limits-
|
||||
|
|
@ -40,6 +41,11 @@ interface Defaults
|
|||
public const OTEL_SPAN_LINK_COUNT_LIMIT = 128;
|
||||
public const OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT = 128;
|
||||
public const OTEL_LINK_ATTRIBUTE_COUNT_LIMIT = 128;
|
||||
/**
|
||||
* LogRecord Limits
|
||||
*/
|
||||
public const OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT = PHP_INT_MAX;
|
||||
public const OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT = 128;
|
||||
/**
|
||||
* OTLP Exporter
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options
|
||||
|
|
@ -48,18 +54,22 @@ interface Defaults
|
|||
public const OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:4318';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'http://localhost:4318';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://localhost:4318';
|
||||
// Insecure
|
||||
public const OTEL_EXPORTER_OTLP_INSECURE = 'false';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'false';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_INSECURE = 'false';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_INSECURE = 'false';
|
||||
// Timeout (seconds)
|
||||
public const OTEL_EXPORTER_OTLP_TIMEOUT = 10;
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = 10;
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_TIMEOUT = 10;
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = 10;
|
||||
// Protocol
|
||||
public const OTEL_EXPORTER_OTLP_PROTOCOL = 'http/protobuf';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'http/protobuf';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = 'http/protobuf';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'http/protobuf';
|
||||
/**
|
||||
* Zipkin Exporter
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#zipkin-exporter
|
||||
|
|
@ -95,4 +105,5 @@ interface Defaults
|
|||
public const OTEL_PHP_DETECTORS = 'all';
|
||||
public const OTEL_PHP_AUTOLOAD_ENABLED = 'false';
|
||||
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = [];
|
||||
public const OTEL_PHP_LOGS_PROCESSOR = 'batch';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ interface Variables
|
|||
*/
|
||||
public const OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT';
|
||||
public const OTEL_ATTRIBUTE_COUNT_LIMIT = 'OTEL_ATTRIBUTE_COUNT_LIMIT';
|
||||
/**
|
||||
* LogRecord limits
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#logrecord-limits
|
||||
*/
|
||||
public const OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT = 'OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT';
|
||||
public const OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT = 'OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT';
|
||||
/**
|
||||
* Span Limits
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#span-limits-
|
||||
|
|
@ -53,30 +59,37 @@ interface Variables
|
|||
public const OTEL_EXPORTER_OTLP_ENDPOINT = 'OTEL_EXPORTER_OTLP_ENDPOINT';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT';
|
||||
// Insecure
|
||||
public const OTEL_EXPORTER_OTLP_INSECURE = 'OTEL_EXPORTER_OTLP_INSECURE';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_INSECURE = 'OTEL_EXPORTER_OTLP_TRACES_INSECURE';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_INSECURE = 'OTEL_EXPORTER_OTLP_METRICS_INSECURE';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_INSECURE = 'OTEL_EXPORTER_OTLP_LOGS_INSECURE';
|
||||
// Certificate File
|
||||
public const OTEL_EXPORTER_OTLP_CERTIFICATE = 'OTEL_EXPORTER_OTLP_CERTIFICATE';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE = 'OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE = 'OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE = 'OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE';
|
||||
// Headers
|
||||
public const OTEL_EXPORTER_OTLP_HEADERS = 'OTEL_EXPORTER_OTLP_HEADERS';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'OTEL_EXPORTER_OTLP_TRACES_HEADERS';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_HEADERS = 'OTEL_EXPORTER_OTLP_METRICS_HEADERS';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'OTEL_EXPORTER_OTLP_LOGS_HEADERS';
|
||||
// Compression
|
||||
public const OTEL_EXPORTER_OTLP_COMPRESSION = 'OTEL_EXPORTER_OTLP_COMPRESSION';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_COMPRESSION = 'OTEL_EXPORTER_OTLP_TRACES_COMPRESSION';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_COMPRESSION = 'OTEL_EXPORTER_OTLP_METRICS_COMPRESSION';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_COMPRESSION = 'OTEL_EXPORTER_OTLP_LOGS_COMPRESSION';
|
||||
// Timeout
|
||||
public const OTEL_EXPORTER_OTLP_TIMEOUT = 'OTEL_EXPORTER_OTLP_TIMEOUT';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_TIMEOUT = 'OTEL_EXPORTER_OTLP_TRACES_TIMEOUT';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_TIMEOUT = 'OTEL_EXPORTER_OTLP_METRICS_TIMEOUT';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_TIMEOUT = 'OTEL_EXPORTER_OTLP_LOGS_TIMEOUT';
|
||||
// Protocol
|
||||
public const OTEL_EXPORTER_OTLP_PROTOCOL = 'OTEL_EXPORTER_OTLP_PROTOCOL';
|
||||
public const OTEL_EXPORTER_OTLP_TRACES_PROTOCOL = 'OTEL_EXPORTER_OTLP_TRACES_PROTOCOL';
|
||||
public const OTEL_EXPORTER_OTLP_METRICS_PROTOCOL = 'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL';
|
||||
public const OTEL_EXPORTER_OTLP_LOGS_PROTOCOL = 'OTEL_EXPORTER_OTLP_LOGS_PROTOCOL';
|
||||
/**
|
||||
* Zipkin Exporter
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#zipkin-exporter
|
||||
|
|
@ -109,6 +122,7 @@ interface Variables
|
|||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#language-specific-environment-variables
|
||||
*/
|
||||
public const OTEL_PHP_TRACES_PROCESSOR = 'OTEL_PHP_TRACES_PROCESSOR';
|
||||
public const OTEL_PHP_LOGS_PROCESSOR = 'OTEL_PHP_LOGS_PROCESSOR';
|
||||
public const OTEL_PHP_DETECTORS = 'OTEL_PHP_DETECTORS';
|
||||
public const OTEL_PHP_AUTOLOAD_ENABLED = 'OTEL_PHP_AUTOLOAD_ENABLED';
|
||||
public const OTEL_PHP_DISABLED_INSTRUMENTATIONS = 'OTEL_PHP_DISABLED_INSTRUMENTATIONS';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
|
||||
use OpenTelemetry\SDK\Common\Future\FutureInterface;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
|
||||
class ConsoleExporter implements LogRecordExporterInterface
|
||||
{
|
||||
private TransportInterface $transport;
|
||||
|
||||
public function __construct(TransportInterface $transport)
|
||||
{
|
||||
$this->transport = $transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable<mixed, ReadableLogRecord> $batch
|
||||
*/
|
||||
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
|
||||
{
|
||||
$resource = null;
|
||||
$scope = null;
|
||||
foreach ($batch as $record) {
|
||||
if (!$resource) {
|
||||
$resource = $this->convertResource($record->getResource());
|
||||
}
|
||||
if (!$scope) {
|
||||
$scope = $this->convertInstrumentationScope($record->getInstrumentationScope());
|
||||
$scope['logs'] = [];
|
||||
}
|
||||
$scope['logs'][] = $this->convertLogRecord($record);
|
||||
}
|
||||
$output = [
|
||||
'resource' => $resource,
|
||||
'scope' => $scope,
|
||||
];
|
||||
$this->transport->send(json_encode($output, JSON_PRETTY_PRINT));
|
||||
|
||||
return new CompletedFuture(true);
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
private function convertLogRecord(ReadableLogRecord $record): array
|
||||
{
|
||||
$spanContext = $record->getSpanContext();
|
||||
|
||||
return [
|
||||
'timestamp' => $record->getTimestamp(),
|
||||
'observed_timestamp' => $record->getObservedTimestamp(),
|
||||
'severity_number' => $record->getSeverityNumber(),
|
||||
'severity_text' => $record->getSeverityText(),
|
||||
'body' => $record->getBody(),
|
||||
'trace_id' => $spanContext !== null ? $spanContext->getTraceId() : '',
|
||||
'span_id' => $spanContext !== null ? $spanContext->getSpanId() : '',
|
||||
'trace_flags' => $spanContext !== null ? $spanContext->getTraceFlags() : null,
|
||||
'attributes' => $record->getAttributes()->toArray(),
|
||||
'dropped_attributes_count' => $record->getAttributes()->getDroppedAttributesCount(),
|
||||
];
|
||||
}
|
||||
|
||||
private function convertResource(ResourceInfo $resource): array
|
||||
{
|
||||
return [
|
||||
'attributes' => $resource->getAttributes()->toArray(),
|
||||
'dropped_attributes_count' => $resource->getAttributes()->getDroppedAttributesCount(),
|
||||
];
|
||||
}
|
||||
private function convertInstrumentationScope(InstrumentationScopeInterface $scope): array
|
||||
{
|
||||
return [
|
||||
'name' => $scope->getName(),
|
||||
'version' => $scope->getVersion(),
|
||||
'attributes' => $scope->getAttributes()->toArray(),
|
||||
'dropped_attributes_count' => $scope->getAttributes()->getDroppedAttributesCount(),
|
||||
'schema_url' => $scope->getSchemaUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Registry;
|
||||
|
||||
class ConsoleExporterFactory implements LogRecordExporterFactoryInterface
|
||||
{
|
||||
public function create(): LogRecordExporterInterface
|
||||
{
|
||||
$transport = Registry::transportFactory('stream')->create('php://stdout', 'application/json');
|
||||
|
||||
return new ConsoleExporter($transport);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
|
||||
use OpenTelemetry\SDK\Common\Future\FutureInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
|
||||
class NoopExporter implements LogRecordExporterInterface
|
||||
{
|
||||
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
|
||||
{
|
||||
return new CompletedFuture(true);
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
\OpenTelemetry\SDK\Registry::registerLogRecordExporterFactory('console', \OpenTelemetry\SDK\Logs\Exporter\ConsoleExporterFactory::class);
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\NoopExporter;
|
||||
use OpenTelemetry\SDK\Registry;
|
||||
|
||||
class ExporterFactory
|
||||
{
|
||||
public function create(): LogRecordExporterInterface
|
||||
{
|
||||
$exporters = Configuration::getList(Variables::OTEL_LOGS_EXPORTER);
|
||||
if (1 !== count($exporters)) {
|
||||
throw new InvalidArgumentException(sprintf('Configuration %s requires exactly 1 exporter', Variables::OTEL_TRACES_EXPORTER));
|
||||
}
|
||||
$exporter = $exporters[0];
|
||||
if ($exporter === 'none') {
|
||||
return new NoopExporter();
|
||||
}
|
||||
$factory = Registry::logRecordExporterFactory($exporter);
|
||||
|
||||
return $factory->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
interface LogRecordExporterFactoryInterface
|
||||
{
|
||||
public function create(): LogRecordExporterInterface;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\FutureInterface;
|
||||
|
||||
interface LogRecordExporterInterface
|
||||
{
|
||||
/**
|
||||
* @param iterable<ReadableLogRecord> $batch
|
||||
*/
|
||||
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface;
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool;
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Attribute\AttributesFactoryInterface;
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#logrecord-limits
|
||||
*/
|
||||
class LogRecordLimits
|
||||
{
|
||||
private AttributesFactoryInterface $attributesFactory;
|
||||
|
||||
/**
|
||||
* @internal Use {@see SpanLimitsBuilder} to create {@see SpanLimits} instance.
|
||||
*/
|
||||
public function __construct(
|
||||
AttributesFactoryInterface $attributesFactory
|
||||
) {
|
||||
$this->attributesFactory = $attributesFactory;
|
||||
}
|
||||
|
||||
public function getAttributeFactory(): AttributesFactoryInterface
|
||||
{
|
||||
return $this->attributesFactory;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Attribute\Attributes;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
class LogRecordLimitsBuilder
|
||||
{
|
||||
/** @var ?int Maximum allowed attribute count per record */
|
||||
private ?int $attributeCountLimit = null;
|
||||
|
||||
/** @var ?int Maximum allowed attribute value length */
|
||||
private ?int $attributeValueLengthLimit = null;
|
||||
|
||||
/**
|
||||
* @param int $attributeCountLimit Maximum allowed attribute count per record
|
||||
*/
|
||||
public function setAttributeCountLimit(int $attributeCountLimit): LogRecordLimitsBuilder
|
||||
{
|
||||
$this->attributeCountLimit = $attributeCountLimit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $attributeValueLengthLimit Maximum allowed attribute value length
|
||||
*/
|
||||
public function setAttributeValueLengthLimit(int $attributeValueLengthLimit): LogRecordLimitsBuilder
|
||||
{
|
||||
$this->attributeValueLengthLimit = $attributeValueLengthLimit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits
|
||||
*/
|
||||
public function build(): LogRecordLimits
|
||||
{
|
||||
$attributeCountLimit = $this->attributeCountLimit
|
||||
?: Configuration::getInt(Variables::OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT);
|
||||
$attributeValueLengthLimit = $this->attributeValueLengthLimit
|
||||
?: Configuration::getInt(Variables::OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT);
|
||||
|
||||
if ($attributeValueLengthLimit === PHP_INT_MAX) {
|
||||
$attributeValueLengthLimit = null;
|
||||
}
|
||||
|
||||
$attributesFactory = Attributes::factory($attributeCountLimit, $attributeValueLengthLimit);
|
||||
|
||||
return new LogRecordLimits($attributesFactory);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
|
||||
use OpenTelemetry\SDK\Common\Configuration\KnownValues as Values;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockFactory;
|
||||
use OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
|
||||
|
||||
class LogRecordProcessorFactory
|
||||
{
|
||||
public function create(LogRecordExporterInterface $exporter, ?MeterProviderInterface $meterProvider = null): LogRecordProcessorInterface
|
||||
{
|
||||
$name = Configuration::getEnum(Variables::OTEL_PHP_LOGS_PROCESSOR);
|
||||
switch ($name) {
|
||||
case KnownValues::VALUE_BATCH:
|
||||
return new BatchLogsProcessor(
|
||||
$exporter,
|
||||
ClockFactory::getDefault(),
|
||||
BatchLogsProcessor::DEFAULT_MAX_QUEUE_SIZE,
|
||||
BatchLogsProcessor::DEFAULT_SCHEDULE_DELAY,
|
||||
BatchLogsProcessor::DEFAULT_EXPORT_TIMEOUT,
|
||||
BatchLogsProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
|
||||
true,
|
||||
$meterProvider,
|
||||
);
|
||||
case KnownValues::VALUE_SIMPLE:
|
||||
return new SimpleLogsProcessor($exporter);
|
||||
case Values::VALUE_NOOP:
|
||||
case Values::VALUE_NONE:
|
||||
return NoopLogsProcessor::getInstance();
|
||||
default:
|
||||
throw new InvalidArgumentException('Unknown processor: ' . $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
|
||||
interface LogRecordProcessorInterface
|
||||
{
|
||||
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void;
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool;
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LoggerInterface;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
|
||||
/**
|
||||
* Note that this logger class is deliberately NOT psr-3 compatible, per spec: "Note: this document defines a log
|
||||
* backend API. The API is not intended to be called by application developers directly."
|
||||
*
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/bridge-api.md
|
||||
*/
|
||||
class Logger implements LoggerInterface
|
||||
{
|
||||
private InstrumentationScopeInterface $scope;
|
||||
private LoggerSharedState $loggerSharedState;
|
||||
private bool $includeTraceContext;
|
||||
|
||||
public function __construct(LoggerSharedState $loggerSharedState, InstrumentationScopeInterface $scope, bool $includeTraceContext)
|
||||
{
|
||||
$this->loggerSharedState = $loggerSharedState;
|
||||
$this->scope = $scope;
|
||||
$this->includeTraceContext = $includeTraceContext;
|
||||
}
|
||||
|
||||
public function logRecord(LogRecord $logRecord): void
|
||||
{
|
||||
$readWriteLogRecord = new ReadWriteLogRecord($this->scope, $this->loggerSharedState, $logRecord, $this->includeTraceContext);
|
||||
// @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#onemit
|
||||
$this->loggerSharedState->getProcessor()->onEmit(
|
||||
$readWriteLogRecord,
|
||||
$readWriteLogRecord->getContext(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
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\Resource\ResourceInfo;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
|
||||
|
||||
class LoggerProvider implements LoggerProviderInterface
|
||||
{
|
||||
private LoggerSharedState $loggerSharedState;
|
||||
private InstrumentationScopeFactoryInterface $instrumentationScopeFactory;
|
||||
|
||||
public function __construct(LogRecordProcessorInterface $processor, InstrumentationScopeFactoryInterface $instrumentationScopeFactory, ?ResourceInfo $resource = null)
|
||||
{
|
||||
$this->loggerSharedState = new LoggerSharedState(
|
||||
$resource ?? ResourceInfoFactory::defaultResource(),
|
||||
(new LogRecordLimitsBuilder())->build(),
|
||||
$processor
|
||||
);
|
||||
$this->instrumentationScopeFactory = $instrumentationScopeFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#logger-creation
|
||||
*/
|
||||
public function getLogger(string $name, ?string $version = null, ?string $schemaUrl = null, bool $includeTraceContext = true, iterable $attributes = []): LoggerInterface
|
||||
{
|
||||
if ($this->loggerSharedState->hasShutdown()) {
|
||||
return NoopLogger::getInstance();
|
||||
}
|
||||
$scope = $this->instrumentationScopeFactory->create($name, $version, $schemaUrl, $attributes);
|
||||
|
||||
return new Logger($this->loggerSharedState, $scope, $includeTraceContext);
|
||||
}
|
||||
|
||||
public function shutdown(CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->loggerSharedState->shutdown($cancellation);
|
||||
}
|
||||
|
||||
public function forceFlush(CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->loggerSharedState->getProcessor()->forceFlush($cancellation);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
|
||||
use OpenTelemetry\SDK\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\SDK\Sdk;
|
||||
|
||||
class LoggerProviderFactory
|
||||
{
|
||||
public function create(?MeterProviderInterface $meterProvider = null): LoggerProviderInterface
|
||||
{
|
||||
if (Sdk::isDisabled()) {
|
||||
return NoopLoggerProvider::getInstance();
|
||||
}
|
||||
$exporter = (new ExporterFactory())->create();
|
||||
$processor = (new LogRecordProcessorFactory())->create($exporter, $meterProvider);
|
||||
$instrumentationScopeFactory = new InstrumentationScopeFactory((new LogRecordLimitsBuilder())->build()->getAttributeFactory());
|
||||
|
||||
return new LoggerProvider($processor, $instrumentationScopeFactory);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs as API;
|
||||
|
||||
interface LoggerProviderInterface extends API\LoggerProviderInterface
|
||||
{
|
||||
public function shutdown(): bool;
|
||||
public function forceFlush(): bool;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
|
||||
class LoggerSharedState
|
||||
{
|
||||
private ResourceInfo $resource;
|
||||
private LogRecordProcessorInterface $processor;
|
||||
private LogRecordLimits $limits;
|
||||
private ?bool $shutdownResult = null;
|
||||
|
||||
public function __construct(
|
||||
ResourceInfo $resource,
|
||||
LogRecordLimits $limits,
|
||||
LogRecordProcessorInterface $processor
|
||||
) {
|
||||
$this->resource = $resource;
|
||||
$this->limits = $limits;
|
||||
$this->processor = $processor;
|
||||
}
|
||||
public function hasShutdown(): bool
|
||||
{
|
||||
return null !== $this->shutdownResult;
|
||||
}
|
||||
|
||||
public function getResource(): ResourceInfo
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
public function getProcessor(): LogRecordProcessorInterface
|
||||
{
|
||||
return $this->processor;
|
||||
}
|
||||
|
||||
public function getLogRecordLimits(): LogRecordLimits
|
||||
{
|
||||
return $this->limits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `false` if the provider is already shutdown, otherwise `true`.
|
||||
*/
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->shutdownResult ?? ($this->shutdownResult = $this->processor->shutdown($cancellation));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LoggerInterface;
|
||||
use OpenTelemetry\API\Logs\NoopLogger;
|
||||
|
||||
class NoopLoggerProvider implements LoggerProviderInterface
|
||||
{
|
||||
public static function getInstance(): self
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= new self();
|
||||
}
|
||||
|
||||
public function getLogger(string $name, ?string $version = null, ?string $schemaUrl = null, bool $includeTraceContext = true, iterable $attributes = []): LoggerInterface
|
||||
{
|
||||
return NoopLogger::getInstance();
|
||||
}
|
||||
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function forceFlush(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Processor;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use OpenTelemetry\API\Behavior\LogsMessagesTrait;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\ObserverInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
use SplQueue;
|
||||
use Throwable;
|
||||
|
||||
class BatchLogsProcessor implements LogRecordProcessorInterface
|
||||
{
|
||||
use LogsMessagesTrait;
|
||||
|
||||
public const DEFAULT_SCHEDULE_DELAY = 5000;
|
||||
public const DEFAULT_EXPORT_TIMEOUT = 30000;
|
||||
public const DEFAULT_MAX_QUEUE_SIZE = 2048;
|
||||
public const DEFAULT_MAX_EXPORT_BATCH_SIZE = 512;
|
||||
|
||||
private const ATTRIBUTES_PROCESSOR = ['processor' => 'batching'];
|
||||
private const ATTRIBUTES_QUEUED = self::ATTRIBUTES_PROCESSOR + ['state' => 'queued'];
|
||||
private const ATTRIBUTES_PENDING = self::ATTRIBUTES_PROCESSOR + ['state' => 'pending'];
|
||||
private const ATTRIBUTES_PROCESSED = self::ATTRIBUTES_PROCESSOR + ['state' => 'processed'];
|
||||
private const ATTRIBUTES_DROPPED = self::ATTRIBUTES_PROCESSOR + ['state' => 'dropped'];
|
||||
private const ATTRIBUTES_FREE = self::ATTRIBUTES_PROCESSOR + ['state' => 'free'];
|
||||
|
||||
private LogRecordExporterInterface $exporter;
|
||||
private ClockInterface $clock;
|
||||
private int $maxQueueSize;
|
||||
private int $scheduledDelayNanos;
|
||||
private int $maxExportBatchSize;
|
||||
private bool $autoFlush;
|
||||
private ContextInterface $exportContext;
|
||||
|
||||
private ?int $nextScheduledRun = null;
|
||||
private bool $running = false;
|
||||
private int $dropped = 0;
|
||||
private int $processed = 0;
|
||||
private int $batchId = 0;
|
||||
private int $queueSize = 0;
|
||||
/** @var list<ReadWriteLogRecord> */
|
||||
private array $batch = [];
|
||||
/** @var SplQueue<list<ReadWriteLogRecord>> */
|
||||
private SplQueue $queue;
|
||||
/** @var SplQueue<array{int, string, ?CancellationInterface, bool, ContextInterface}> */
|
||||
private SplQueue $flush;
|
||||
|
||||
private bool $closed = false;
|
||||
|
||||
public function __construct(
|
||||
LogRecordExporterInterface $exporter,
|
||||
ClockInterface $clock,
|
||||
int $maxQueueSize = self::DEFAULT_MAX_QUEUE_SIZE,
|
||||
int $scheduledDelayMillis = self::DEFAULT_SCHEDULE_DELAY,
|
||||
int $exportTimeoutMillis = self::DEFAULT_EXPORT_TIMEOUT,
|
||||
int $maxExportBatchSize = self::DEFAULT_MAX_EXPORT_BATCH_SIZE,
|
||||
bool $autoFlush = true,
|
||||
?MeterProviderInterface $meterProvider = null
|
||||
) {
|
||||
if ($maxQueueSize <= 0) {
|
||||
throw new InvalidArgumentException(sprintf('Maximum queue size (%d) must be greater than zero', $maxQueueSize));
|
||||
}
|
||||
if ($scheduledDelayMillis <= 0) {
|
||||
throw new InvalidArgumentException(sprintf('Scheduled delay (%d) must be greater than zero', $scheduledDelayMillis));
|
||||
}
|
||||
if ($exportTimeoutMillis <= 0) {
|
||||
throw new InvalidArgumentException(sprintf('Export timeout (%d) must be greater than zero', $exportTimeoutMillis));
|
||||
}
|
||||
if ($maxExportBatchSize <= 0) {
|
||||
throw new InvalidArgumentException(sprintf('Maximum export batch size (%d) must be greater than zero', $maxExportBatchSize));
|
||||
}
|
||||
if ($maxExportBatchSize > $maxQueueSize) {
|
||||
throw new InvalidArgumentException(sprintf('Maximum export batch size (%d) must be less than or equal to maximum queue size (%d)', $maxExportBatchSize, $maxQueueSize));
|
||||
}
|
||||
|
||||
$this->exporter = $exporter;
|
||||
$this->clock = $clock;
|
||||
$this->maxQueueSize = $maxQueueSize;
|
||||
$this->scheduledDelayNanos = $scheduledDelayMillis * 1_000_000;
|
||||
$this->maxExportBatchSize = $maxExportBatchSize;
|
||||
$this->autoFlush = $autoFlush;
|
||||
|
||||
$this->exportContext = Context::getCurrent();
|
||||
$this->queue = new SplQueue();
|
||||
$this->flush = new SplQueue();
|
||||
|
||||
if ($meterProvider === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$meter = $meterProvider->getMeter('io.opentelemetry.sdk');
|
||||
$meter
|
||||
->createObservableUpDownCounter(
|
||||
'otel.logs.log_processor.logs',
|
||||
'{logs}',
|
||||
'The number of log records received by the processor',
|
||||
)
|
||||
->observe(function (ObserverInterface $observer): void {
|
||||
$queued = $this->queue->count() * $this->maxExportBatchSize + count($this->batch);
|
||||
$pending = $this->queueSize - $queued;
|
||||
$processed = $this->processed;
|
||||
$dropped = $this->dropped;
|
||||
|
||||
$observer->observe($queued, self::ATTRIBUTES_QUEUED);
|
||||
$observer->observe($pending, self::ATTRIBUTES_PENDING);
|
||||
$observer->observe($processed, self::ATTRIBUTES_PROCESSED);
|
||||
$observer->observe($dropped, self::ATTRIBUTES_DROPPED);
|
||||
}, true);
|
||||
$meter
|
||||
->createObservableUpDownCounter(
|
||||
'otel.logs.log_processor.queue.limit',
|
||||
'{logs}',
|
||||
'The queue size limit',
|
||||
)
|
||||
->observe(function (ObserverInterface $observer): void {
|
||||
$observer->observe($this->maxQueueSize, self::ATTRIBUTES_PROCESSOR);
|
||||
}, true);
|
||||
$meter
|
||||
->createObservableUpDownCounter(
|
||||
'otel.logs.log_processor.queue.usage',
|
||||
'{logs}',
|
||||
'The current queue usage',
|
||||
)
|
||||
->observe(function (ObserverInterface $observer): void {
|
||||
$queued = $this->queue->count() * $this->maxExportBatchSize + count($this->batch);
|
||||
$pending = $this->queueSize - $queued;
|
||||
$free = $this->maxQueueSize - $this->queueSize;
|
||||
|
||||
$observer->observe($queued, self::ATTRIBUTES_QUEUED);
|
||||
$observer->observe($pending, self::ATTRIBUTES_PENDING);
|
||||
$observer->observe($free, self::ATTRIBUTES_FREE);
|
||||
}, true);
|
||||
}
|
||||
|
||||
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->queueSize === $this->maxQueueSize) {
|
||||
$this->dropped++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queueSize++;
|
||||
$this->batch[] = $record;
|
||||
$this->nextScheduledRun ??= $this->clock->now() + $this->scheduledDelayNanos;
|
||||
|
||||
if (count($this->batch) === $this->maxExportBatchSize) {
|
||||
$this->enqueueBatch();
|
||||
}
|
||||
if ($this->autoFlush) {
|
||||
$this->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
if ($this->closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->flush(__FUNCTION__, $cancellation);
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
if ($this->closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
return $this->flush(__FUNCTION__, $cancellation);
|
||||
}
|
||||
|
||||
private function flush(?string $flushMethod = null, ?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
if ($flushMethod !== null) {
|
||||
$flushId = $this->batchId + $this->queue->count() + (int) (bool) $this->batch;
|
||||
$this->flush->enqueue([$flushId, $flushMethod, $cancellation, !$this->running, Context::getCurrent()]);
|
||||
}
|
||||
|
||||
if ($this->running) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$success = true;
|
||||
$exception = null;
|
||||
$this->running = true;
|
||||
|
||||
try {
|
||||
for (;;) {
|
||||
while (!$this->flush->isEmpty() && $this->flush->bottom()[0] <= $this->batchId) {
|
||||
[, $flushMethod, $cancellation, $propagateResult, $context] = $this->flush->dequeue();
|
||||
$scope = $context->activate();
|
||||
|
||||
try {
|
||||
$result = $this->exporter->$flushMethod($cancellation);
|
||||
if ($propagateResult) {
|
||||
$success = $result;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
if ($propagateResult) {
|
||||
$exception = $e;
|
||||
} else {
|
||||
self::logError(sprintf('Unhandled %s error', $flushMethod), ['exception' => $e]);
|
||||
}
|
||||
} finally {
|
||||
$scope->detach();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->shouldFlush()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->queue->isEmpty()) {
|
||||
$this->enqueueBatch();
|
||||
}
|
||||
$batchSize = count($this->queue->bottom());
|
||||
$this->batchId++;
|
||||
$scope = $this->exportContext->activate();
|
||||
|
||||
try {
|
||||
$this->exporter->export($this->queue->dequeue())->await();
|
||||
} catch (Throwable $e) {
|
||||
self::logError('Unhandled export error', ['exception' => $e]);
|
||||
} finally {
|
||||
$this->processed += $batchSize;
|
||||
$this->queueSize -= $batchSize;
|
||||
$scope->detach();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->running = false;
|
||||
}
|
||||
|
||||
if ($exception !== null) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
private function shouldFlush(): bool
|
||||
{
|
||||
return !$this->flush->isEmpty()
|
||||
|| $this->autoFlush && !$this->queue->isEmpty()
|
||||
|| $this->autoFlush && $this->nextScheduledRun !== null && $this->clock->now() > $this->nextScheduledRun;
|
||||
}
|
||||
|
||||
private function enqueueBatch(): void
|
||||
{
|
||||
assert($this->batch !== []);
|
||||
|
||||
$this->queue->enqueue($this->batch);
|
||||
$this->batch = [];
|
||||
$this->nextScheduledRun = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Processor;
|
||||
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
|
||||
class NoopLogsProcessor implements LogRecordProcessorInterface
|
||||
{
|
||||
public static function getInstance(): self
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
|
||||
{
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs\Processor;
|
||||
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
|
||||
class SimpleLogsProcessor implements LogRecordProcessorInterface
|
||||
{
|
||||
private LogRecordExporterInterface $exporter;
|
||||
public function __construct(LogRecordExporterInterface $exporter)
|
||||
{
|
||||
$this->exporter = $exporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#onemit
|
||||
*/
|
||||
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
|
||||
{
|
||||
$this->exporter->export([$record]);
|
||||
}
|
||||
|
||||
public function shutdown(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->exporter->shutdown($cancellation);
|
||||
}
|
||||
|
||||
public function forceFlush(?CancellationInterface $cancellation = null): bool
|
||||
{
|
||||
return $this->exporter->forceFlush($cancellation);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
class ReadWriteLogRecord extends ReadableLogRecord
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\API\Trace\Span;
|
||||
use OpenTelemetry\API\Trace\SpanContextInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
|
||||
/**
|
||||
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#log-and-event-record-definition
|
||||
* "Note: Typically this will be implemented with a new interface or (immutable) value type."
|
||||
*/
|
||||
class ReadableLogRecord extends LogRecord
|
||||
{
|
||||
private InstrumentationScopeInterface $scope;
|
||||
private LoggerSharedState $loggerSharedState;
|
||||
protected AttributesInterface $convertedAttributes;
|
||||
protected SpanContextInterface $spanContext;
|
||||
|
||||
public function __construct(InstrumentationScopeInterface $scope, LoggerSharedState $loggerSharedState, LogRecord $logRecord, bool $includeTraceContext)
|
||||
{
|
||||
$this->scope = $scope;
|
||||
$this->loggerSharedState = $loggerSharedState;
|
||||
|
||||
parent::__construct($logRecord->body);
|
||||
$this->timestamp = $logRecord->timestamp;
|
||||
$this->observedTimestamp = $logRecord->observedTimestamp;
|
||||
$this->context = $logRecord->context;
|
||||
if ($includeTraceContext) {
|
||||
$context = $this->context ?? Context::getCurrent();
|
||||
$this->spanContext = Span::fromContext($context)->getContext();
|
||||
};
|
||||
$this->severityNumber = $logRecord->severityNumber;
|
||||
$this->severityText = $logRecord->severityText;
|
||||
|
||||
//convert attributes now so that excess data is not sent to processors
|
||||
$this->convertedAttributes = $this->loggerSharedState
|
||||
->getLogRecordLimits()
|
||||
->getAttributeFactory()
|
||||
->builder($logRecord->attributes)
|
||||
->build();
|
||||
}
|
||||
|
||||
public function getInstrumentationScope(): InstrumentationScopeInterface
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
public function getResource(): ResourceInfo
|
||||
{
|
||||
return $this->loggerSharedState->getResource();
|
||||
}
|
||||
|
||||
public function getTimestamp(): ?int
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
public function getObservedTimestamp(): ?int
|
||||
{
|
||||
return $this->observedTimestamp;
|
||||
}
|
||||
|
||||
public function getContext(): ?ContextInterface
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function getSpanContext(): ?SpanContextInterface
|
||||
{
|
||||
return $this->spanContext;
|
||||
}
|
||||
|
||||
public function getSeverityNumber(): ?int
|
||||
{
|
||||
return $this->severityNumber;
|
||||
}
|
||||
|
||||
public function getSeverityText(): ?string
|
||||
{
|
||||
return $this->severityText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getAttributes(): AttributesInterface
|
||||
{
|
||||
return $this->convertedAttributes;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ namespace OpenTelemetry\SDK;
|
|||
|
||||
use OpenTelemetry\Context\Propagation\TextMapPropagatorInterface;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterFactoryInterface;
|
||||
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
|
||||
use OpenTelemetry\SDK\Trace\SpanExporter\SpanExporterFactoryInterface;
|
||||
use RuntimeException;
|
||||
|
|
@ -20,6 +21,7 @@ class Registry
|
|||
private static array $transportFactories = [];
|
||||
private static array $metricExporterFactories = [];
|
||||
private static array $textMapPropagators = [];
|
||||
private static array $logRecordExporterFactories = [];
|
||||
|
||||
/**
|
||||
* @param TransportFactoryInterface|class-string<TransportFactoryInterface> $factory
|
||||
|
|
@ -89,6 +91,25 @@ class Registry
|
|||
}
|
||||
self::$metricExporterFactories[$exporter] = $factory;
|
||||
}
|
||||
public static function registerLogRecordExporterFactory(string $exporter, $factory, bool $clobber = false): void
|
||||
{
|
||||
if (!$clobber && array_key_exists($exporter, self::$logRecordExporterFactories)) {
|
||||
return;
|
||||
}
|
||||
if (!is_subclass_of($factory, LogRecordExporterFactoryInterface::class)) {
|
||||
trigger_error(
|
||||
sprintf(
|
||||
'Cannot register LogRecord exporter factory: %s must exist and implement %s',
|
||||
is_string($factory) ? $factory : get_class($factory),
|
||||
LogRecordExporterFactoryInterface::class
|
||||
),
|
||||
E_USER_WARNING
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
self::$logRecordExporterFactories[$exporter] = $factory;
|
||||
}
|
||||
|
||||
public static function registerTextMapPropagator(string $name, TextMapPropagatorInterface $propagator, bool $clobber = false): void
|
||||
{
|
||||
|
|
@ -110,6 +131,18 @@ class Registry
|
|||
return $factory;
|
||||
}
|
||||
|
||||
public static function logRecordExporterFactory(string $exporter): LogRecordExporterFactoryInterface
|
||||
{
|
||||
if (!array_key_exists($exporter, self::$logRecordExporterFactories)) {
|
||||
throw new RuntimeException('LogRecord exporter factory not defined for: ' . $exporter);
|
||||
}
|
||||
$class = self::$logRecordExporterFactories[$exporter];
|
||||
$factory = (is_callable($class)) ? $class : new $class();
|
||||
assert($factory instanceof LogRecordExporterFactoryInterface);
|
||||
|
||||
return $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transport factory registered for protocol. If $protocol contains a content-type eg `http/xyz` then
|
||||
* only the first part, `http`, is used.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use OpenTelemetry\API\Common\Instrumentation\Globals;
|
|||
use OpenTelemetry\SDK\Common\Configuration\Configuration;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Common\Util\ShutdownHandler;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProviderFactory;
|
||||
use OpenTelemetry\SDK\Metrics\MeterProviderFactory;
|
||||
use OpenTelemetry\SDK\Propagation\PropagatorFactory;
|
||||
use OpenTelemetry\SDK\Trace\ExporterFactory;
|
||||
|
|
@ -47,12 +48,16 @@ class SdkAutoloader
|
|||
->setSampler((new SamplerFactory())->create())
|
||||
->build();
|
||||
|
||||
$loggerProvider = (new LoggerProviderFactory())->create($meterProvider);
|
||||
|
||||
ShutdownHandler::register([$tracerProvider, 'shutdown']);
|
||||
ShutdownHandler::register([$meterProvider, 'shutdown']);
|
||||
ShutdownHandler::register([$loggerProvider, 'shutdown']);
|
||||
|
||||
return $configurator
|
||||
->withTracerProvider($tracerProvider)
|
||||
->withMeterProvider($meterProvider)
|
||||
->withLoggerProvider($loggerProvider)
|
||||
->withPropagator($propagator);
|
||||
});
|
||||
|
||||
|
|
@ -62,7 +67,7 @@ class SdkAutoloader
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function shutdown(): void
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$enabled = null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
},
|
||||
"files": [
|
||||
"Common/Util/functions.php",
|
||||
"Logs/Exporter/_register.php",
|
||||
"Metrics/MetricExporter/_register.php",
|
||||
"Propagation/_register.php",
|
||||
"Trace/SpanExporter/_register.php",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ class SpanContextTest extends TestCase
|
|||
|
||||
/**
|
||||
* @group trace-compliance
|
||||
* @covers ::isValid
|
||||
*/
|
||||
public function test_valid_span(): void
|
||||
{
|
||||
|
|
@ -52,7 +51,6 @@ class SpanContextTest extends TestCase
|
|||
|
||||
/**
|
||||
* @group trace-compliance
|
||||
* @covers ::isRemote
|
||||
*/
|
||||
public function test_context_is_remote_from_restore(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\API\Baggage\Propagation;
|
||||
namespace OpenTelemetry\Tests\Unit\API\Baggage\Propagation;
|
||||
|
||||
use OpenTelemetry\API\Baggage\BaggageBuilderInterface;
|
||||
use OpenTelemetry\API\Baggage\Metadata;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ namespace OpenTelemetry\Tests\Unit\API\Common\Instrumentation;
|
|||
use OpenTelemetry\API\Common\Instrumentation\CachedInstrumentation;
|
||||
use OpenTelemetry\API\Common\Instrumentation\Configurator;
|
||||
use OpenTelemetry\API\Common\Instrumentation\Globals;
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\API\Logs\NoopLoggerProvider;
|
||||
use OpenTelemetry\API\Metrics\MeterInterface;
|
||||
use OpenTelemetry\API\Metrics\MeterProviderInterface;
|
||||
use OpenTelemetry\API\Metrics\Noop\NoopMeter;
|
||||
|
|
@ -32,6 +34,7 @@ final class InstrumentationTest extends TestCase
|
|||
$this->assertInstanceOf(NoopTracerProvider::class, Globals::tracerProvider());
|
||||
$this->assertInstanceOf(NoopMeterProvider::class, Globals::meterProvider());
|
||||
$this->assertInstanceOf(NoopTextMapPropagator::class, Globals::propagator());
|
||||
$this->assertInstanceOf(NoopLoggerProvider::class, Globals::loggerProvider());
|
||||
}
|
||||
|
||||
public function test_globals_returns_configured_instances(): void
|
||||
|
|
@ -39,17 +42,20 @@ final class InstrumentationTest extends TestCase
|
|||
$tracerProvider = $this->createMock(TracerProviderInterface::class);
|
||||
$meterProvider = $this->createMock(MeterProviderInterface::class);
|
||||
$propagator = $this->createMock(TextMapPropagatorInterface::class);
|
||||
$loggerProvider = $this->createMock(LoggerProviderInterface::class);
|
||||
|
||||
$scope = Configurator::create()
|
||||
->withTracerProvider($tracerProvider)
|
||||
->withMeterProvider($meterProvider)
|
||||
->withPropagator($propagator)
|
||||
->withLoggerProvider($loggerProvider)
|
||||
->activate();
|
||||
|
||||
try {
|
||||
$this->assertSame($tracerProvider, Globals::tracerProvider());
|
||||
$this->assertSame($meterProvider, Globals::meterProvider());
|
||||
$this->assertSame($propagator, Globals::propagator());
|
||||
$this->assertSame($loggerProvider, Globals::loggerProvider());
|
||||
} finally {
|
||||
$scope->detach();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\API\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\EventLogger;
|
||||
use OpenTelemetry\API\Logs\LoggerInterface;
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\API\Logs\EventLogger
|
||||
*/
|
||||
class EventLoggerTest extends TestCase
|
||||
{
|
||||
public function test_log_event(): void
|
||||
{
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$domain = 'some.domain';
|
||||
$logRecord = $this->createMock(LogRecord::class);
|
||||
$eventLogger = new EventLogger($logger, $domain);
|
||||
$logRecord->expects($this->once())->method('setAttributes');
|
||||
$logger->expects($this->once())->method('logRecord')->with($this->equalTo($logRecord));
|
||||
|
||||
$eventLogger->logEvent('some.event', $logRecord);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\API\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\API\Logs\LogRecord
|
||||
*/
|
||||
class LogRecordTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider settersProvider
|
||||
*/
|
||||
public function test_setters(string $method, string $propertyName, $value): void
|
||||
{
|
||||
$record = new LogRecord();
|
||||
$record->{$method}($value);
|
||||
|
||||
$reflection = new \ReflectionClass($record);
|
||||
$property = $reflection->getProperty($propertyName);
|
||||
$property->setAccessible(true);
|
||||
$this->assertSame($value, $property->getValue($record));
|
||||
}
|
||||
|
||||
public static function settersProvider(): array
|
||||
{
|
||||
return [
|
||||
['setBody', 'body', 'foo'],
|
||||
['setAttributes', 'attributes', ['foo' => 'bar']],
|
||||
['setSeverityNumber', 'severityNumber', 5],
|
||||
['setSeverityText', 'severityText', 'info'],
|
||||
['setObservedTimestamp', 'observedTimestamp', 999],
|
||||
['setTimestamp', 'timestamp', 888],
|
||||
['setContext', 'context', Context::getCurrent()],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\API\Logs\Map;
|
||||
|
||||
use OpenTelemetry\API\Logs\Map\Psr3;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\API\Logs\Map\Psr3
|
||||
*/
|
||||
class Psr3Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider levelProvider
|
||||
*/
|
||||
public function test_severity_number(string $level): void
|
||||
{
|
||||
$this->assertNotNull(Psr3::severityNumber($level));
|
||||
}
|
||||
|
||||
public static function levelProvider(): array
|
||||
{
|
||||
return [
|
||||
[LogLevel::EMERGENCY],
|
||||
[LogLevel::ALERT],
|
||||
[LogLevel::CRITICAL],
|
||||
[LogLevel::ERROR],
|
||||
[LogLevel::WARNING],
|
||||
[LogLevel::NOTICE],
|
||||
[LogLevel::INFO],
|
||||
[LogLevel::DEBUG],
|
||||
['unknown'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\API\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\NoopLogger;
|
||||
use OpenTelemetry\API\Logs\NoopLoggerProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\API\Logs\NoopLoggerProvider
|
||||
*/
|
||||
class NoopLoggerProviderTest extends TestCase
|
||||
{
|
||||
public function test_provides_logger(): void
|
||||
{
|
||||
$logger = (new NoopLoggerProvider())->getLogger('foo');
|
||||
$this->assertInstanceOf(NoopLogger::class, $logger);
|
||||
}
|
||||
|
||||
public function test_get_instance(): void
|
||||
{
|
||||
$this->assertInstanceOf(NoopLoggerProvider::class, NoopLoggerProvider::getInstance());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\API\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\NoopLogger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\API\Logs\NoopLogger
|
||||
*/
|
||||
class NoopLoggerTest extends TestCase
|
||||
{
|
||||
public function test_get_instance(): void
|
||||
{
|
||||
$this->assertInstanceOf(NoopLogger::class, NoopLogger::getInstance());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\Contrib\Otlp;
|
||||
|
||||
use OpenTelemetry\API\Trace\SpanContext;
|
||||
use OpenTelemetry\API\Trace\SpanInterface;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\Context\ContextKeys;
|
||||
use OpenTelemetry\Contrib\Otlp\LogsConverter;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\Contrib\Otlp\LogsConverter
|
||||
*/
|
||||
class LogsConverterTest extends TestCase
|
||||
{
|
||||
private const TRACE_ID_BASE16 = 'ff000000000000000000000000000041';
|
||||
private const SPAN_ID_BASE16 = 'ff00000000000041';
|
||||
private const FLAGS = 12;
|
||||
/** @var ReadableLogRecord&MockObject $record */
|
||||
private $record;
|
||||
private LogsConverter $converter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->converter = new LogsConverter();
|
||||
$this->record = $this->createMock(ReadableLogRecord::class);
|
||||
}
|
||||
|
||||
public function test_convert(): void
|
||||
{
|
||||
$this->record->method('getBody')->willReturn('body');
|
||||
|
||||
$request = $this->converter->convert([$this->record]);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$row = $request->getResourceLogs()[0]->getScopeLogs()[0]->getLogRecords()[0];
|
||||
$this->assertSame('body', $row->getBody()->getStringValue());
|
||||
}
|
||||
|
||||
public function test_convert_with_context(): void
|
||||
{
|
||||
$spanContext = SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, self::FLAGS);
|
||||
$span = $this->createMock(SpanInterface::class);
|
||||
$context = Context::getCurrent()->with(ContextKeys::span(), $span);
|
||||
$span->method('getContext')->willReturn($spanContext);
|
||||
$this->record->method('getSpanContext')->willReturn($spanContext);
|
||||
$request = $this->converter->convert([$this->record]);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$row = $request->getResourceLogs()[0]->getScopeLogs()[0]->getLogRecords()[0];
|
||||
$this->assertSame(self::TRACE_ID_BASE16, bin2hex($row->getTraceId()));
|
||||
$this->assertSame(self::SPAN_ID_BASE16, bin2hex($row->getSpanId()));
|
||||
$this->assertSame(self::FLAGS, $row->getFlags());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\Contrib\Otlp;
|
||||
|
||||
use AssertWell\PHPUnitGlobalState\EnvironmentVariables;
|
||||
use OpenTelemetry\Contrib\Otlp\LogsExporterFactory;
|
||||
use OpenTelemetry\SDK\Common\Configuration\KnownValues;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Variables;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\Contrib\Otlp\LogsExporterFactory
|
||||
*/
|
||||
class LogsExporterFactoryTest extends TestCase
|
||||
{
|
||||
use EnvironmentVariables;
|
||||
|
||||
/** @var TransportFactoryInterface&MockObject $record */
|
||||
private TransportFactoryInterface $transportFactory;
|
||||
/** @var TransportInterface&MockObject $record */
|
||||
private TransportInterface $transport;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->transportFactory = $this->createMock(TransportFactoryInterface::class);
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->restoreEnvironmentVariables();
|
||||
}
|
||||
|
||||
public function test_unknown_protocol_exception(): void
|
||||
{
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->setEnvironmentVariable(Variables::OTEL_EXPORTER_OTLP_PROTOCOL, 'foo');
|
||||
$factory = new LogsExporterFactory();
|
||||
$factory->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider configProvider
|
||||
*/
|
||||
public function test_create(array $env, string $endpoint, string $protocol, string $compression, array $headerKeys = []): void
|
||||
{
|
||||
foreach ($env as $k => $v) {
|
||||
$this->setEnvironmentVariable($k, $v);
|
||||
}
|
||||
$factory = new LogsExporterFactory($this->transportFactory);
|
||||
// @phpstan-ignore-next-line
|
||||
$this->transportFactory
|
||||
->expects($this->once())
|
||||
->method('create')
|
||||
->with(
|
||||
$this->equalTo($endpoint),
|
||||
$this->equalTo($protocol),
|
||||
$this->callback(function ($headers) use ($headerKeys) {
|
||||
$this->assertEqualsCanonicalizing($headerKeys, array_keys($headers));
|
||||
|
||||
return true;
|
||||
}),
|
||||
$this->equalTo($compression)
|
||||
)
|
||||
->willReturn($this->transport);
|
||||
// @phpstan-ignore-next-line
|
||||
$this->transport->method('contentType')->willReturn($protocol);
|
||||
|
||||
$factory->create();
|
||||
}
|
||||
|
||||
public function configProvider(): array
|
||||
{
|
||||
$defaultHeaderKeys = ['User-Agent'];
|
||||
|
||||
return [
|
||||
'signal-specific endpoint unchanged' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_LOGS_PROTOCOL => KnownValues::VALUE_GRPC,
|
||||
Variables::OTEL_EXPORTER_OTLP_LOGS_ENDPOINT => 'http://collector:4317/foo/bar', //should not be changed, per spec
|
||||
],
|
||||
'endpoint' => 'http://collector:4317/foo/bar',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'endpoint has path appended' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_LOGS_PROTOCOL => KnownValues::VALUE_GRPC,
|
||||
Variables::OTEL_EXPORTER_OTLP_ENDPOINT => 'http://collector:4317',
|
||||
],
|
||||
'endpoint' => 'http://collector:4317/opentelemetry.proto.collector.logs.v1.LogsService/Export',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'protocol' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_PROTOCOL => KnownValues::VALUE_HTTP_NDJSON,
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-ndjson',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'signal-specific protocol' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_PROTOCOL => KnownValues::VALUE_HTTP_JSON,
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/json',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'defaults' => [
|
||||
'env' => [],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'compression' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_COMPRESSION => 'gzip',
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'gzip',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'signal-specific compression' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_LOGS_COMPRESSION => 'gzip',
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'gzip',
|
||||
'headerKeys' => $defaultHeaderKeys,
|
||||
],
|
||||
'headers' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_HEADERS => 'key1=foo,key2=bar',
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => array_merge($defaultHeaderKeys, ['key1', 'key2']),
|
||||
],
|
||||
'signal-specific headers' => [
|
||||
'env' => [
|
||||
Variables::OTEL_EXPORTER_OTLP_LOGS_HEADERS => 'key3=foo,key4=bar',
|
||||
],
|
||||
'endpoint' => 'http://localhost:4318/v1/logs',
|
||||
'protocol' => 'application/x-protobuf',
|
||||
'compression' => 'none',
|
||||
'headerKeys' => array_merge($defaultHeaderKeys, ['key3', 'key4']),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\Contrib\Otlp;
|
||||
|
||||
use OpenTelemetry\Contrib\Otlp\LogsExporter;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
|
||||
use OpenTelemetry\SDK\Common\Future\ErrorFuture;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\Contrib\Otlp\LogsExporter
|
||||
*/
|
||||
class LogsExporterTest extends TestCase
|
||||
{
|
||||
private MockObject $transport;
|
||||
private LogsExporter $exporter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
$this->transport->method('contentType')->willReturn('application/x-protobuf');
|
||||
$this->exporter = new LogsExporter($this->transport);
|
||||
}
|
||||
|
||||
public function test_export_with_transport_failure(): void
|
||||
{
|
||||
$future = new ErrorFuture(new \Exception('foo'));
|
||||
$this->transport->method('send')->willReturn($future);
|
||||
$result = $this->exporter->export([]);
|
||||
$this->assertFalse($result->await());
|
||||
}
|
||||
|
||||
public function test_export_with_invalid_response(): void
|
||||
{
|
||||
$future = new CompletedFuture('invalid.grpc.payload');
|
||||
$this->transport->method('send')->willReturn($future);
|
||||
$result = $this->exporter->export([]);
|
||||
$this->assertFalse($result->await());
|
||||
}
|
||||
|
||||
public function test_export_success(): void
|
||||
{
|
||||
$future = new CompletedFuture('');
|
||||
$this->transport->method('send')->willReturn($future);
|
||||
$result = $this->exporter->export([]);
|
||||
$this->assertTrue($result->await());
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->transport->expects($this->once())->method('shutdown');
|
||||
$this->exporter->shutdown();
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->transport->expects($this->once())->method('forceFlush');
|
||||
$this->exporter->forceFlush();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ use PHPUnit\Framework\TestCase;
|
|||
/**
|
||||
* @covers \OpenTelemetry\Contrib\Otlp\SpanConverter
|
||||
*/
|
||||
class OTLPSpanConverterTest extends TestCase
|
||||
class SpanConverterTest extends TestCase
|
||||
{
|
||||
public function test_convert_span_to_payload(): void
|
||||
{
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\SDK\Common\Configuration\Resolver;
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Common\Configuration\Resolver;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Configuration\Resolver\PhpIniAccessor;
|
||||
use OpenTelemetry\SDK\Common\Configuration\Resolver\PhpIniResolver;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\SDK\Common\Export\Stream;
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Common\Export\Stream;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\SDK;
|
||||
namespace OpenTelemetry\Tests\Unit\SDK;
|
||||
|
||||
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
|
||||
use OpenTelemetry\SDK\Metrics\MetricExporterFactoryInterface;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporterFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Exporter\ConsoleExporterFactory
|
||||
*/
|
||||
class ConsoleExporterFactoryTest extends TestCase
|
||||
{
|
||||
public function test_create(): void
|
||||
{
|
||||
$factory = new ConsoleExporterFactory();
|
||||
|
||||
$this->assertInstanceOf(ConsoleExporter::class, $factory->create());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\SDK\Common\Export\TransportInterface;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
|
||||
use OpenTelemetry\SDK\Logs\LoggerSharedState;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
*/
|
||||
class ConsoleExporterTest extends TestCase
|
||||
{
|
||||
private TransportInterface $transport;
|
||||
private ConsoleExporter $exporter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->transport = $this->createMock(TransportInterface::class);
|
||||
$this->exporter = new ConsoleExporter($this->transport);
|
||||
}
|
||||
|
||||
public function test_export(): void
|
||||
{
|
||||
$batch = [
|
||||
(new ReadableLogRecord(
|
||||
$this->createMock(InstrumentationScopeInterface::class),
|
||||
$this->createMock(LoggerSharedState::class),
|
||||
(new LogRecord('foo')),
|
||||
true,
|
||||
)),
|
||||
];
|
||||
|
||||
$this->transport->expects($this->once())->method('send');
|
||||
|
||||
$this->exporter->export($batch);
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->assertTrue($this->exporter->forceFlush());
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->assertTrue($this->exporter->shutdown());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Exporter;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\Exporter\NoopExporter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Exporter\NoopExporter
|
||||
*/
|
||||
class NoopExporterTest extends TestCase
|
||||
{
|
||||
private NoopExporter $exporter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->exporter = new NoopExporter();
|
||||
}
|
||||
|
||||
public function test_export(): void
|
||||
{
|
||||
$this->assertTrue($this->exporter->export([])->await());
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->assertTrue($this->exporter->forceFlush());
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->assertTrue($this->exporter->shutdown());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use AssertWell\PHPUnitGlobalState\EnvironmentVariables;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
|
||||
use OpenTelemetry\SDK\Logs\Exporter\NoopExporter;
|
||||
use OpenTelemetry\SDK\Logs\ExporterFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\ExporterFactory
|
||||
*/
|
||||
class ExporterFactoryTest extends TestCase
|
||||
{
|
||||
use EnvironmentVariables;
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->restoreEnvironmentVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider exporterProvider
|
||||
*/
|
||||
public function test_create($name, $expected): void
|
||||
{
|
||||
$this->setEnvironmentVariable('OTEL_LOGS_EXPORTER', $name);
|
||||
$exporter = (new ExporterFactory())->create();
|
||||
|
||||
$this->assertInstanceOf($expected, $exporter);
|
||||
}
|
||||
|
||||
public static function exporterProvider(): array
|
||||
{
|
||||
return [
|
||||
['console', ConsoleExporter::class],
|
||||
['none', NoopExporter::class],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_rejects_multiple(): void
|
||||
{
|
||||
$this->setEnvironmentVariable('OTEL_LOGS_EXPORTER', 'one,two');
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
(new ExporterFactory())->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder
|
||||
* @covers \OpenTelemetry\SDK\Logs\LogRecordLimits
|
||||
*/
|
||||
class LogRecordLimitsBuilderTest extends TestCase
|
||||
{
|
||||
public function test_builder(): void
|
||||
{
|
||||
$limits = (new LogRecordLimitsBuilder())
|
||||
->setAttributeCountLimit(2)
|
||||
->setAttributeValueLengthLimit(5)
|
||||
->build();
|
||||
|
||||
$attributes = $limits->getAttributeFactory()->builder([
|
||||
'foo' => 'bar', //allowed, <5 chars
|
||||
'long' => 'long-attribute-value', //trimmed, >5 chars
|
||||
'bar' => 'baz', //dropped, exceeds count
|
||||
])->build();
|
||||
|
||||
$this->assertSame(1, $attributes->getDroppedAttributesCount());
|
||||
$this->assertCount(2, $attributes);
|
||||
$this->assertSame('long-', $attributes->get('long'));
|
||||
$this->assertSame('bar', $attributes->get('foo'));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use AssertWell\PHPUnitGlobalState\EnvironmentVariables;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorFactory;
|
||||
use OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\LogRecordProcessorFactory
|
||||
*/
|
||||
class LogRecordProcessorFactoryTest extends TestCase
|
||||
{
|
||||
use EnvironmentVariables;
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
$this->restoreEnvironmentVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider exporterProvider
|
||||
*/
|
||||
public function test_create($name, $expected): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$this->setEnvironmentVariable('OTEL_PHP_LOGS_PROCESSOR', $name);
|
||||
$processor = (new LogRecordProcessorFactory())->create($exporter);
|
||||
|
||||
$this->assertInstanceOf($expected, $processor);
|
||||
}
|
||||
|
||||
public static function exporterProvider(): array
|
||||
{
|
||||
return [
|
||||
['batch', BatchLogsProcessor::class],
|
||||
['simple', SimpleLogsProcessor::class],
|
||||
['noop', NoopLogsProcessor::class],
|
||||
['none', NoopLogsProcessor::class],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_create_invalid(): void
|
||||
{
|
||||
$this->setEnvironmentVariable('OTEL_PHP_LOGS_PROCESSOR', 'baz');
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
(new LogRecordProcessorFactory())->create($this->createMock(LogRecordExporterInterface::class));
|
||||
}
|
||||
|
||||
public function test_rejects_multiple(): void
|
||||
{
|
||||
$this->setEnvironmentVariable('OTEL_PHP_LOGS_PROCESSOR', 'one,two');
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
(new LogRecordProcessorFactory())->create($this->createMock(LogRecordExporterInterface::class));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LoggerProviderInterface;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProviderFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\LoggerProviderFactory
|
||||
*/
|
||||
class LoggerProviderFactoryTest extends TestCase
|
||||
{
|
||||
public function test_create(): void
|
||||
{
|
||||
$factory = new LoggerProviderFactory();
|
||||
$this->assertInstanceOf(LoggerProviderInterface::class, $factory->create());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\NoopLogger;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactoryInterface;
|
||||
use OpenTelemetry\SDK\Logs\Logger;
|
||||
use OpenTelemetry\SDK\Logs\LoggerProvider;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\LoggerProvider
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
* @psalm-suppress PossiblyUndefinedMethod
|
||||
*/
|
||||
class LoggerProviderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var LogRecordProcessorInterface|(LogRecordProcessorInterface&\PHPUnit\Framework\MockObject\MockObject)|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private LogRecordProcessorInterface $processor;
|
||||
private InstrumentationScopeFactoryInterface $instrumentationScopeFactory;
|
||||
private LoggerProvider $provider;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->processor = $this->createMock(LogRecordProcessorInterface::class);
|
||||
$this->instrumentationScopeFactory = $this->createMock(InstrumentationScopeFactoryInterface::class);
|
||||
$resource = $this->createMock(ResourceInfo::class);
|
||||
$this->provider = new LoggerProvider($this->processor, $this->instrumentationScopeFactory, $resource);
|
||||
}
|
||||
|
||||
public function test_get_logger(): void
|
||||
{
|
||||
$logger = $this->provider->getLogger('name');
|
||||
$this->assertInstanceOf(Logger::class, $logger);
|
||||
}
|
||||
|
||||
public function test_get_logger_after_shutdown(): void
|
||||
{
|
||||
$this->provider->shutdown();
|
||||
$logger = $this->provider->getLogger('name');
|
||||
$this->assertInstanceOf(NoopLogger::class, $logger);
|
||||
}
|
||||
|
||||
public function test_shutdown_calls_processor_shutdown(): void
|
||||
{
|
||||
$this->processor->expects($this->once())->method('shutdown');
|
||||
$this->provider->shutdown();
|
||||
}
|
||||
|
||||
public function test_force_flush_calls_processor_force_flush(): void
|
||||
{
|
||||
$this->processor->expects($this->once())->method('forceFlush');
|
||||
$this->provider->forceFlush();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\LoggerSharedState;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimits;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\LoggerSharedState
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
*/
|
||||
class LoggerSharedStateTest extends TestCase
|
||||
{
|
||||
private ResourceInfo $resource;
|
||||
private LogRecordProcessorInterface $processor;
|
||||
private LogRecordLimits $limits;
|
||||
private LoggerSharedState $loggerSharedState;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->resource = $this->createMock(ResourceInfo::class);
|
||||
$this->processor = $this->createMock(LogRecordProcessorInterface::class);
|
||||
$this->limits = $this->createMock(LogRecordLimits::class);
|
||||
$this->loggerSharedState = new LoggerSharedState(
|
||||
$this->resource,
|
||||
$this->limits,
|
||||
$this->processor,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_get_resource(): void
|
||||
{
|
||||
$this->assertSame($this->resource, $this->loggerSharedState->getResource());
|
||||
}
|
||||
|
||||
public function test_get_processor(): void
|
||||
{
|
||||
$this->assertSame($this->processor, $this->loggerSharedState->getProcessor());
|
||||
}
|
||||
|
||||
public function test_get_log_record_limits(): void
|
||||
{
|
||||
$this->assertSame($this->limits, $this->loggerSharedState->getLogRecordLimits());
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->processor->expects($this->once())->method('shutdown')->willReturn(true);
|
||||
|
||||
$this->assertFalse($this->loggerSharedState->hasShutdown());
|
||||
$this->loggerSharedState->shutdown();
|
||||
$this->assertTrue($this->loggerSharedState->hasShutdown());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Attribute\Attributes;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
|
||||
use OpenTelemetry\SDK\Logs\Logger;
|
||||
use OpenTelemetry\SDK\Logs\LoggerSharedState;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Logger
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
*/
|
||||
class LoggerTest extends TestCase
|
||||
{
|
||||
private LoggerSharedState $sharedState;
|
||||
private LogRecordProcessorInterface $processor;
|
||||
private InstrumentationScope $scope;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->sharedState = $this->createMock(LoggerSharedState::class);
|
||||
$this->processor = $this->createMock(LogRecordProcessorInterface::class);
|
||||
$this->sharedState->method('getProcessor')->willReturn($this->processor);
|
||||
$this->scope = new InstrumentationScope('foo', '1.0', 'schema.url', Attributes::create([])); //final
|
||||
}
|
||||
|
||||
public function test_log_record(): void
|
||||
{
|
||||
$logger = new Logger($this->sharedState, $this->scope, true);
|
||||
$record = (new LogRecord())->setContext($this->createMock(ContextInterface::class));
|
||||
|
||||
$this->processor->expects($this->once())->method('onEmit')
|
||||
->with(
|
||||
$this->isInstanceOf(ReadWriteLogRecord::class),
|
||||
$this->isInstanceOf(ContextInterface::class)
|
||||
);
|
||||
|
||||
$logger->logRecord($record);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\NoopLogger;
|
||||
use OpenTelemetry\SDK\Logs\NoopLoggerProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\NoopLoggerProvider
|
||||
*/
|
||||
class NoopLoggerProviderTest extends TestCase
|
||||
{
|
||||
public function test_get_instance(): void
|
||||
{
|
||||
$this->assertInstanceOf(NoopLoggerProvider::class, NoopLoggerProvider::getInstance());
|
||||
}
|
||||
|
||||
public function test_get_logger(): void
|
||||
{
|
||||
$this->assertInstanceOf(NoopLogger::class, NoopLoggerProvider::getInstance()->getLogger('foo'));
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->assertTrue(NoopLoggerProvider::getInstance()->shutdown());
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->assertTrue(NoopLoggerProvider::getInstance()->forceFlush());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,493 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Processor;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use Mockery;
|
||||
use Mockery\Adapter\Phpunit\MockeryTestCase;
|
||||
use OpenTelemetry\API\Common\Log\LoggerHolder;
|
||||
use OpenTelemetry\Context\Context;
|
||||
use OpenTelemetry\SDK\Common\Attribute\Attributes;
|
||||
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockFactory;
|
||||
use OpenTelemetry\SDK\Common\Time\ClockInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
use OpenTelemetry\SDK\Metrics\MeterProvider;
|
||||
use OpenTelemetry\SDK\Metrics\MetricExporter\InMemoryExporter;
|
||||
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
|
||||
use OpenTelemetry\SDK\Metrics\StalenessHandler\ImmediateStalenessHandlerFactory;
|
||||
use OpenTelemetry\SDK\Metrics\View\CriteriaViewRegistry;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
|
||||
use OpenTelemetry\Tests\Unit\SDK\Util\TestClock;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Processor\BatchLogsProcessor
|
||||
*/
|
||||
class BatchLogsProcessorTest extends MockeryTestCase
|
||||
{
|
||||
private TestClock $testClock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
LoggerHolder::set(new NullLogger());
|
||||
$this->testClock = new TestClock();
|
||||
|
||||
ClockFactory::setDefault($this->testClock);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
ClockFactory::setDefault(null);
|
||||
}
|
||||
|
||||
public function test_export_batch_size_met(): void
|
||||
{
|
||||
$batchSize = 3;
|
||||
$queueSize = 5; // queue is larger than batch
|
||||
$exportDelay = 3;
|
||||
$logs = [];
|
||||
$timeout = 3000;
|
||||
|
||||
for ($i = 0; $i < $batchSize; $i++) {
|
||||
$logs[] = $this->createMock(ReadWriteLogRecord::class);
|
||||
}
|
||||
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->expects($this->atLeastOnce())->method('export');
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
$this->testClock,
|
||||
$queueSize,
|
||||
$exportDelay,
|
||||
$timeout,
|
||||
$batchSize
|
||||
);
|
||||
|
||||
foreach ($logs as $record) {
|
||||
$processor->onEmit($record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider scheduledDelayProvider
|
||||
*/
|
||||
public function test_export_scheduled_delay(int $exportDelay, int $advanceByNano, bool $expectedFlush): void
|
||||
{
|
||||
$batchSize = 2;
|
||||
$queueSize = 5;
|
||||
$timeout = 3000;
|
||||
$logs = [];
|
||||
|
||||
for ($i = 0; $i < $batchSize; $i++) {
|
||||
$logs[] = $this->createMock(ReadWriteLogRecord::class);
|
||||
}
|
||||
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->expects($this->exactly($expectedFlush ? 1 : 0))->method('export');
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
$this->testClock,
|
||||
$queueSize,
|
||||
$exportDelay,
|
||||
$timeout,
|
||||
$batchSize + 1
|
||||
);
|
||||
|
||||
foreach ($logs as $i => $record) {
|
||||
if (1 === $i) {
|
||||
$this->testClock->advance($advanceByNano);
|
||||
}
|
||||
$processor->onEmit($record);
|
||||
}
|
||||
}
|
||||
|
||||
public function scheduledDelayProvider(): array
|
||||
{
|
||||
return [
|
||||
'no clock advance' => [1000, 0, false],
|
||||
'clock advance less than threshold' => [1000, 999 * ClockInterface::NANOS_PER_MILLISECOND, false],
|
||||
'clock advance equals threshold' => [1000, 1000 * ClockInterface::NANOS_PER_MILLISECOND, false],
|
||||
'clock advance exceeds threshold' => [1000, 1001 * ClockInterface::NANOS_PER_MILLISECOND, true],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_export_delay_limit_reached_partially_filled_batch(): void
|
||||
{
|
||||
$batchSize = 4;
|
||||
$queueSize = 5;
|
||||
$exportDelay = 1;
|
||||
$timeout = 3000;
|
||||
|
||||
$logs = [];
|
||||
for ($i = 0; $i < $batchSize - 1; $i++) {
|
||||
$logs[] = $this->createMock(ReadWriteLogRecord::class);
|
||||
}
|
||||
|
||||
$exporter = Mockery::mock(LogRecordExporterInterface::class);
|
||||
$exporter
|
||||
->expects('export')
|
||||
->with(
|
||||
Mockery::on(
|
||||
function (array $records) {
|
||||
$this->assertCount(3, $records);
|
||||
$this->assertInstanceOf(ReadWriteLogRecord::class, $records[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
)
|
||||
->andReturn(new CompletedFuture(0));
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
$this->testClock,
|
||||
$queueSize,
|
||||
$exportDelay,
|
||||
$timeout,
|
||||
$batchSize
|
||||
);
|
||||
|
||||
foreach ($logs as $idx => $record) {
|
||||
$processor->onEmit($record);
|
||||
|
||||
if (1 === $idx) {
|
||||
// Advance the clock to force a timeout flush.
|
||||
$this->testClock->advanceSeconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function test_export_delay_limit_not_reached_partially_filled_batch(): void
|
||||
{
|
||||
$batchSize = 3;
|
||||
$queueSize = 5;
|
||||
$exportDelay = 2;
|
||||
$timeout = 3000;
|
||||
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->expects($this->never())->method('export');
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
$this->testClock,
|
||||
$queueSize,
|
||||
$exportDelay,
|
||||
$timeout,
|
||||
$batchSize
|
||||
);
|
||||
|
||||
for ($i = 0; $i < $batchSize - 1; $i++) {
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_export_includes_force_flush_on_shutdown(): void
|
||||
{
|
||||
$batchSize = 3;
|
||||
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->expects($this->once())->method('export');
|
||||
$exporter->expects($this->once())->method('shutdown');
|
||||
|
||||
$proc = new BatchLogsProcessor($exporter, $this->createMock(ClockInterface::class));
|
||||
|
||||
for ($i = 0; $i < $batchSize - 1; $i++) {
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$proc->onEmit($record);
|
||||
}
|
||||
|
||||
$proc->shutdown();
|
||||
}
|
||||
|
||||
public function test_export_after_shutdown(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->expects($this->atLeastOnce())->method('shutdown');
|
||||
|
||||
$proc = new BatchLogsProcessor($exporter, $this->createMock(ClockInterface::class));
|
||||
$proc->shutdown();
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$proc->onEmit($record, Context::getCurrent());
|
||||
$proc->forceFlush();
|
||||
$proc->shutdown();
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$batchSize = 3;
|
||||
$queueSize = 3;
|
||||
$exportDelay = 2;
|
||||
$timeout = 3000;
|
||||
|
||||
$exporter = Mockery::mock(LogRecordExporterInterface::class);
|
||||
$exporter->expects('forceFlush');
|
||||
$exporter
|
||||
->expects('export')
|
||||
->with(
|
||||
Mockery::on(
|
||||
function (array $records) {
|
||||
$this->assertCount(2, $records);
|
||||
$this->assertInstanceOf(ReadWriteLogRecord::class, $records[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
)
|
||||
)
|
||||
->andReturn(new CompletedFuture(0));
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
$this->testClock,
|
||||
$queueSize,
|
||||
$exportDelay,
|
||||
$timeout,
|
||||
$batchSize
|
||||
);
|
||||
|
||||
for ($i = 0; $i < $batchSize - 1; $i++) {
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
}
|
||||
|
||||
$processor->forceFlush();
|
||||
}
|
||||
|
||||
public function test_queue_size_exceeded_drops_spans(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock, 5, 5000, 30000, 5);
|
||||
|
||||
$exporter->expects($this->exactly(2))->method('export')->willReturnCallback(function (iterable $batch) use ($processor, &$i) {
|
||||
if ($i) {
|
||||
$this->assertCount(3, $batch);
|
||||
} else {
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$span = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($span);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$processor->forceFlush();
|
||||
$processor->forceFlush();
|
||||
}
|
||||
|
||||
public function test_force_flush_applies_only_to_current_logs(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
|
||||
$exporter->expects($this->exactly(1))->method('export')->willReturnCallback(function (iterable $batch) use ($processor) {
|
||||
$this->assertCount(1, $batch);
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record); //arrives after flush started, so not flushed yet
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$processor->forceFlush();
|
||||
}
|
||||
|
||||
public function test_shutdown_shutdowns_exporter(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
|
||||
$exporter->expects($this->once())->method('shutdown');
|
||||
$processor->shutdown();
|
||||
}
|
||||
|
||||
public function test_throwing_exporter_export(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->method('forceFlush')->willReturn(true);
|
||||
$exporter->method('export')->willThrowException(new LogicException());
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('log')->with(LogLevel::ERROR);
|
||||
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$previousLogger = LoggerHolder::get();
|
||||
LoggerHolder::set($logger);
|
||||
|
||||
try {
|
||||
$processor->forceFlush();
|
||||
} finally {
|
||||
LoggerHolder::set($previousLogger);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_throwing_exporter_flush(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->method('forceFlush')->willThrowException(new LogicException());
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$processor->forceFlush();
|
||||
}
|
||||
|
||||
public function test_throwing_exporter_flush_cannot_rethrow_in_original_caller_logs_error(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->method('forceFlush')->willReturnCallback(function () use (&$processor) {
|
||||
/** @var LogRecordProcessorInterface $processor */
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
return $processor->shutdown();
|
||||
});
|
||||
$exporter->method('shutdown')->willThrowException(new LogicException());
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('log')->with(LogLevel::ERROR);
|
||||
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$previousLogger = LoggerHolder::get();
|
||||
LoggerHolder::set($logger);
|
||||
|
||||
try {
|
||||
$processor->forceFlush();
|
||||
} finally {
|
||||
LoggerHolder::set($previousLogger);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_throwing_exporter_flush_rethrows_in_original_caller(): void
|
||||
{
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$exporter->method('forceFlush')->willReturnCallback(function () use (&$processor) {
|
||||
/** @var LogRecordProcessorInterface $processor */
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
$processor->shutdown();
|
||||
|
||||
throw new LogicException();
|
||||
});
|
||||
$exporter->expects($this->once())->method('shutdown');
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
|
||||
$processor = new BatchLogsProcessor($exporter, $this->testClock);
|
||||
|
||||
$record = $this->createMock(ReadWriteLogRecord::class);
|
||||
$processor->onEmit($record);
|
||||
|
||||
$processor->forceFlush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires PHP >= 8.0
|
||||
*/
|
||||
public function test_self_diagnostics(): void
|
||||
{
|
||||
$clock = new TestClock();
|
||||
$metrics = new InMemoryExporter();
|
||||
$reader = new ExportingReader($metrics, $clock);
|
||||
$meterProvider = new MeterProvider(
|
||||
null,
|
||||
ResourceInfoFactory::emptyResource(),
|
||||
$clock,
|
||||
Attributes::factory(),
|
||||
new InstrumentationScopeFactory(Attributes::factory()),
|
||||
[$reader],
|
||||
new CriteriaViewRegistry(),
|
||||
null,
|
||||
new ImmediateStalenessHandlerFactory(),
|
||||
);
|
||||
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
|
||||
$processor = new BatchLogsProcessor(
|
||||
$exporter,
|
||||
ClockFactory::getDefault(),
|
||||
2048,
|
||||
5000,
|
||||
30000,
|
||||
512,
|
||||
false,
|
||||
$meterProvider,
|
||||
);
|
||||
|
||||
$reader->collect();
|
||||
$this->assertEquals(
|
||||
[
|
||||
'otel.logs.log_processor.logs',
|
||||
'otel.logs.log_processor.queue.limit',
|
||||
'otel.logs.log_processor.queue.usage',
|
||||
],
|
||||
array_column($metrics->collect(), 'name'),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_logs_processor_throws_on_invalid_max_queue_size(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
new BatchLogsProcessor($exporter, $this->testClock, -1);
|
||||
}
|
||||
|
||||
public function test_logs_processor_throws_on_invalid_scheduled_delay(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
new BatchLogsProcessor($exporter, $this->testClock, 2048, -1);
|
||||
}
|
||||
|
||||
public function test_logs_processor_throws_on_invalid_export_timeout(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
new BatchLogsProcessor($exporter, $this->testClock, 2048, 5000, -1);
|
||||
}
|
||||
|
||||
public function test_logs_processor_throws_on_invalid_max_export_batch_size(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
new BatchLogsProcessor($exporter, $this->testClock, 2048, 5000, 30000, -1);
|
||||
}
|
||||
|
||||
public function test_logs_processor_throws_on_invalid_max_export_batch_size_exceeding_max_queue_size(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
new BatchLogsProcessor($exporter, $this->testClock, 2, 5000, 30000, 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Processor;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
|
||||
use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor
|
||||
*/
|
||||
class NoopLogsProcessorTest extends TestCase
|
||||
{
|
||||
public function test_get_instance(): void
|
||||
{
|
||||
$this->assertInstanceOf(LogRecordProcessorInterface::class, NoopLogsProcessor::getInstance());
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->assertTrue(NoopLogsProcessor::getInstance()->shutdown());
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->assertTrue(NoopLogsProcessor::getInstance()->forceFlush());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs\Processor;
|
||||
|
||||
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
|
||||
use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
|
||||
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
* @psalm-suppress PossiblyUndefinedMethod
|
||||
*/
|
||||
class SimpleLogsProcessorTest extends TestCase
|
||||
{
|
||||
private SimpleLogsProcessor $processor;
|
||||
/**
|
||||
* @var LogRecordExporterInterface|(LogRecordExporterInterface&\PHPUnit\Framework\MockObject\MockObject)|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private LogRecordExporterInterface $exporter;
|
||||
private ReadWriteLogRecord $readWriteLogRecord;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->exporter = $this->createMock(LogRecordExporterInterface::class);
|
||||
$this->readWriteLogRecord = $this->createMock(ReadWriteLogRecord::class);
|
||||
$this->processor = new SimpleLogsProcessor($this->exporter);
|
||||
}
|
||||
|
||||
public function test_on_emit(): void
|
||||
{
|
||||
$this->exporter->expects($this->once())->method('export')->with($this->equalTo([$this->readWriteLogRecord]));
|
||||
|
||||
$this->processor->onEmit($this->readWriteLogRecord);
|
||||
}
|
||||
|
||||
public function test_shutdown(): void
|
||||
{
|
||||
$this->exporter->expects($this->once())->method('shutdown');
|
||||
|
||||
$this->processor->shutdown();
|
||||
}
|
||||
|
||||
public function test_force_flush(): void
|
||||
{
|
||||
$this->exporter->expects($this->once())->method('forceFlush');
|
||||
|
||||
$this->processor->forceFlush();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Logs;
|
||||
|
||||
use OpenTelemetry\API\Logs\LogRecord;
|
||||
use OpenTelemetry\Context\ContextInterface;
|
||||
use OpenTelemetry\SDK\Common\Attribute\AttributesFactory;
|
||||
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
|
||||
use OpenTelemetry\SDK\Logs\LoggerSharedState;
|
||||
use OpenTelemetry\SDK\Logs\LogRecordLimits;
|
||||
use OpenTelemetry\SDK\Logs\ReadableLogRecord;
|
||||
use OpenTelemetry\SDK\Resource\ResourceInfo;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @covers \OpenTelemetry\SDK\Logs\ReadableLogRecord
|
||||
*/
|
||||
class ReadableLogRecordTest extends TestCase
|
||||
{
|
||||
public function test_getters(): void
|
||||
{
|
||||
$scope = $this->createMock(InstrumentationScopeInterface::class);
|
||||
$sharedState = $this->createMock(LoggerSharedState::class);
|
||||
$context = $this->createMock(ContextInterface::class);
|
||||
$resource = $this->createMock(ResourceInfo::class);
|
||||
$limits = $this->createMock(LogRecordLimits::class);
|
||||
$attributeFactory = new AttributesFactory();
|
||||
$limits->method('getAttributeFactory')->willReturn($attributeFactory); //final
|
||||
$sharedState->method('getResource')->willReturn($resource);
|
||||
$sharedState->method('getLogRecordLimits')->willReturn($limits);
|
||||
$logRecord = (new LogRecord('body'))
|
||||
->setSeverityNumber(5)
|
||||
->setSeverityText('info')
|
||||
->setTimestamp(11)
|
||||
->setObservedTimestamp(22)
|
||||
->setAttributes(['foo' => 'bar'])
|
||||
->setContext($context);
|
||||
$record = new ReadableLogRecord($scope, $sharedState, $logRecord, true);
|
||||
|
||||
$this->assertSame($scope, $record->getInstrumentationScope());
|
||||
$this->assertSame($resource, $record->getResource());
|
||||
$this->assertSame(11, $record->getTimestamp());
|
||||
$this->assertSame(22, $record->getObservedTimestamp());
|
||||
$this->assertSame($context, $record->getContext());
|
||||
$this->assertSame(5, $record->getSeverityNumber());
|
||||
$this->assertSame('info', $record->getSeverityText());
|
||||
$this->assertSame('body', $record->getBody());
|
||||
$this->assertEquals(['foo' => 'bar'], $record->getAttributes()->toArray());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\SDK\Metrics;
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Metrics;
|
||||
|
||||
use AssertWell\PHPUnitGlobalState\EnvironmentVariables;
|
||||
use OpenTelemetry\API\Common\Log\LoggerHolder;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OpenTelemetry\Example\Unit\SDK\Resource\Detectors;
|
||||
namespace OpenTelemetry\Tests\Unit\SDK\Resource\Detectors;
|
||||
|
||||
use OpenTelemetry\SDK\Resource\Detectors\Container;
|
||||
use OpenTelemetry\SemConv\ResourceAttributes;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace OpenTelemetry\Tests\Unit\SDK;
|
|||
use AssertWell\PHPUnitGlobalState\EnvironmentVariables;
|
||||
use OpenTelemetry\API\Common\Instrumentation\Globals;
|
||||
use OpenTelemetry\API\Common\Log\LoggerHolder;
|
||||
use OpenTelemetry\API\Logs\NoopLoggerProvider;
|
||||
use OpenTelemetry\API\Metrics\Noop\NoopMeterProvider;
|
||||
use OpenTelemetry\API\Trace\NoopTracerProvider;
|
||||
use OpenTelemetry\Context\Propagation\NoopTextMapPropagator;
|
||||
|
|
@ -25,12 +26,12 @@ class SdkAutoloaderTest extends TestCase
|
|||
public function setUp(): void
|
||||
{
|
||||
LoggerHolder::set(new NullLogger());
|
||||
Globals::reset();
|
||||
SdkAutoloader::reset();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
SdkAutoloader::shutdown();
|
||||
Globals::reset();
|
||||
$this->restoreEnvironmentVariables();
|
||||
}
|
||||
|
||||
|
|
@ -42,13 +43,10 @@ class SdkAutoloaderTest extends TestCase
|
|||
$this->assertInstanceOf(NoopTextMapPropagator::class, Globals::propagator(), 'propagator not initialized by disabled autoloader');
|
||||
}
|
||||
|
||||
public function test_enabled_by_configuration(): void
|
||||
public function test_disabled_with_invalid_flag(): void
|
||||
{
|
||||
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'true');
|
||||
SdkAutoloader::autoload();
|
||||
$this->assertNotInstanceOf(NoopTextMapPropagator::class, Globals::propagator());
|
||||
$this->assertNotInstanceOf(NoopMeterProvider::class, Globals::meterProvider());
|
||||
$this->assertNotInstanceOf(NoopTracerProvider::class, Globals::tracerProvider());
|
||||
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'invalid-value');
|
||||
$this->assertFalse(SdkAutoloader::autoload());
|
||||
}
|
||||
|
||||
public function test_sdk_disabled_does_not_disable_propagator(): void
|
||||
|
|
@ -61,9 +59,13 @@ class SdkAutoloaderTest extends TestCase
|
|||
$this->assertInstanceOf(NoopTracerProvider::class, Globals::tracerProvider());
|
||||
}
|
||||
|
||||
public function test_disabled_with_invalid_flag(): void
|
||||
public function test_enabled_by_configuration(): void
|
||||
{
|
||||
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'invalid-value');
|
||||
$this->assertFalse(SdkAutoloader::autoload());
|
||||
$this->setEnvironmentVariable(Variables::OTEL_PHP_AUTOLOAD_ENABLED, 'true');
|
||||
SdkAutoloader::autoload();
|
||||
$this->assertNotInstanceOf(NoopTextMapPropagator::class, Globals::propagator());
|
||||
$this->assertNotInstanceOf(NoopMeterProvider::class, Globals::meterProvider());
|
||||
$this->assertNotInstanceOf(NoopTracerProvider::class, Globals::tracerProvider());
|
||||
$this->assertNotInstanceOf(NoopLoggerProvider::class, Globals::loggerProvider());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue