Repository: open-telemetry/opentelemetry-php (php)

Issue:
add support for multiple log record processors
The logging spec indicates that a logger provider can be configured with multiple `LogRecordProcessor`s:
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#logrecordprocessor

Pull request description:
Multiple log processors
adding support for multiple logs processors, per spec

Closes #962 

Patch:
diff --git a/Makefile b/Makefile
index a2bfc795..ee2ea803 100644
--- a/Makefile
+++ b/Makefile
@@ -59,7 +59,7 @@ smoke-test-exporter-examples: FORCE ## Run (some) exporter smoke test examples
 # Note this does not include every exporter at the moment
 	$(DOCKER_COMPOSE) up -d --remove-orphans
 	$(DC_RUN_PHP) php ./examples/traces/exporters/zipkin.php
-	$(DC_RUN_PHP) php ./examples/traces/parent_span_example.php
+	$(DC_RUN_PHP) php ./examples/traces/features/parent_span_example.php
 # The following examples do not use the DC_RUN_PHP global because they need environment variables.
 	$(DOCKER_COMPOSE) run -e NEW_RELIC_ENDPOINT -e NEW_RELIC_INSERT_KEY --rm php php ./examples/traces/exporters/newrelic.php
 	$(DOCKER_COMPOSE) run -e NEW_RELIC_ENDPOINT -e NEW_RELIC_INSERT_KEY --rm php php ./examples/traces/exporters/zipkin_to_newrelic.php
diff --git a/examples/logs/exporters/otlp_grpc.php b/examples/logs/exporters/otlp_grpc.php
index 31daa443..85c9e7bd 100644
--- a/examples/logs/exporters/otlp_grpc.php
+++ b/examples/logs/exporters/otlp_grpc.php
@@ -27,7 +27,6 @@ LoggerHolder::set(
 
 $transport = (new GrpcTransportFactory())->create('http://collector:4317' . OtlpUtil::method(Signals::LOGS));
 $exporter = new LogsExporter($transport);
-
 $loggerProvider = new LoggerProvider(
     new BatchLogsProcessor(
         $exporter,
diff --git a/examples/logs/getting_started.php b/examples/logs/getting_started.php
index 0d67f3c4..35dc0a0d 100644
--- a/examples/logs/getting_started.php
+++ b/examples/logs/getting_started.php
@@ -4,11 +4,10 @@ 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\Attribute\Attributes;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
-use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporter;
+use OpenTelemetry\SDK\Logs\Exporter\ConsoleExporterFactory;
 use OpenTelemetry\SDK\Logs\LoggerProvider;
-use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
 use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
 use OpenTelemetry\SDK\Trace\TracerProvider;
 
@@ -16,9 +15,9 @@ require __DIR__ . '/../../vendor/autoload.php';
 
 $loggerProvider = new LoggerProvider(
     new SimpleLogsProcessor(
-        new ConsoleExporter((new StreamTransportFactory())->create(STDOUT, 'text'))
+        (new ConsoleExporterFactory())->create()
     ),
-    new InstrumentationScopeFactory((new LogRecordLimitsBuilder())->build()->getAttributeFactory())
+    new InstrumentationScopeFactory(Attributes::factory())
 );
 $tracerProvider = new TracerProvider();
 $tracer = $tracerProvider->getTracer('demo-tracer');
diff --git a/examples/sdk_builder.php b/examples/sdk_builder.php
index 184377a9..3aae891e 100644
--- a/examples/sdk_builder.php
+++ b/examples/sdk_builder.php
@@ -45,7 +45,7 @@ $meterProvider = MeterProvider::builder()
     ->build();
 
 $loggerProvider = LoggerProvider::builder()
-    ->setLogRecordProcessor(
+    ->addLogRecordProcessor(
         new SimpleLogsProcessor($logRecordExporter)
     )
     ->build();
diff --git a/examples/traces/features/parent_span_example.php b/examples/traces/features/parent_span_example.php
index d940ed25..c8beb660 100644
--- a/examples/traces/features/parent_span_example.php
+++ b/examples/traces/features/parent_span_example.php
@@ -14,8 +14,7 @@ $sampler = new AlwaysOnSampler();
 
 // zipkin exporter
 $zipkinExporter = new ZipkinExporter(
-    $serviceName,
-    PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans')
+    PsrTransportFactory::discover()->create('http://zipkin:9411/api/v2/spans', 'application/json')
 );
 
 $tracerProvider =  new TracerProvider(
diff --git a/src/Contrib/Newrelic/SpanConverter.php b/src/Contrib/Newrelic/SpanConverter.php
index fb9069ce..9a70ec08 100644
--- a/src/Contrib/Newrelic/SpanConverter.php
+++ b/src/Contrib/Newrelic/SpanConverter.php
@@ -17,6 +17,7 @@ class SpanConverter implements SpanConverterInterface
 {
     const STATUS_CODE_TAG_KEY = 'otel.status_code';
     const STATUS_DESCRIPTION_TAG_KEY = 'otel.status_description';
+    const KEY_DROPPED_ATTRIBUTES_COUNT = 'otel.dropped_attributes_count';
 
     private string $defaultServiceName;
 
@@ -71,6 +72,14 @@ class SpanConverter implements SpanConverterInterface
             $row['attributes'][$k] = $v;
         }
 
+        $droppedAttributes = $span->getAttributes()->getDroppedAttributesCount()
+            + $span->getInstrumentationScope()->getAttributes()->getDroppedAttributesCount()
+            + $span->getResource()->getAttributes()->getDroppedAttributesCount();
+
+        if ($droppedAttributes > 0) {
+            $row['attributes'][self::KEY_DROPPED_ATTRIBUTES_COUNT] = $droppedAttributes;
+        }
+
         /*
         foreach ($span->getEvents() as $event) {
             if (!array_key_exists('annotations', $row)) {
diff --git a/src/Contrib/Zipkin/SpanConverter.php b/src/Contrib/Zipkin/SpanConverter.php
index 041c8921..70fa9ad4 100644
--- a/src/Contrib/Zipkin/SpanConverter.php
+++ b/src/Contrib/Zipkin/SpanConverter.php
@@ -21,6 +21,9 @@ class SpanConverter implements SpanConverterInterface
     const STATUS_DESCRIPTION_TAG_KEY = 'error';
     const KEY_INSTRUMENTATION_SCOPE_NAME = 'otel.scope.name';
     const KEY_INSTRUMENTATION_SCOPE_VERSION = 'otel.scope.version';
+    const KEY_DROPPED_ATTRIBUTES_COUNT = 'otel.dropped_attributes_count';
+    const KEY_DROPPED_EVENTS_COUNT = 'otel.dropped_events_count';
+    const KEY_DROPPED_LINKS_COUNT = 'otel.dropped_links_count';
 
     const REMOTE_ENDPOINT_PREFERRED_ATTRIBUTE_TO_RANK_MAP = [
         'peer.service' => 1,
@@ -136,6 +139,22 @@ class SpanConverter implements SpanConverterInterface
             $row['annotations'][] = SpanConverter::toAnnotation($event);
         }
 
+        if ($span->getTotalDroppedEvents() > 0) {
+            $row['tags'][self::KEY_DROPPED_EVENTS_COUNT] = $span->getTotalDroppedEvents();
+        }
+
+        if ($span->getTotalDroppedLinks() > 0) {
+            $row['tags'][self::KEY_DROPPED_LINKS_COUNT] = $span->getTotalDroppedLinks();
+        }
+
+        $droppedAttributes = $span->getAttributes()->getDroppedAttributesCount()
+            + $span->getInstrumentationScope()->getAttributes()->getDroppedAttributesCount()
+            + $span->getResource()->getAttributes()->getDroppedAttributesCount();
+
+        if ($droppedAttributes > 0) {
+            $row['tags'][self::KEY_DROPPED_ATTRIBUTES_COUNT] = $droppedAttributes;
+        }
+
         if (($span->getKind() === SpanKind::KIND_CLIENT) || ($span->getKind() === SpanKind::KIND_PRODUCER)) {
             $remoteEndpointData = SpanConverter::toRemoteEndpoint($span);
             if ($remoteEndpointData !== null) {
@@ -175,10 +194,15 @@ class SpanConverter implements SpanConverterInterface
 
         $value = ($attributesAsJson !== null) ? sprintf('"%s": %s', $eventName, $attributesAsJson) : sprintf('"%s"', $eventName);
 
-        return [
+        $annotation = [
             'timestamp' => TimeUtil::nanosToMicros($event->getEpochNanos()),
             'value' => $value,
         ];
+        if ($event->getAttributes()->getDroppedAttributesCount() > 0) {
+            $annotation[self::KEY_DROPPED_ATTRIBUTES_COUNT] = $event->getAttributes()->getDroppedAttributesCount();
+        }
+
+        return $annotation;
     }
 
     private static function convertEventAttributesToJson(EventInterface $event): ?string
diff --git a/src/SDK/Logs/LogRecordProcessorFactory.php b/src/SDK/Logs/LogRecordProcessorFactory.php
index 88d681a7..9b16566f 100644
--- a/src/SDK/Logs/LogRecordProcessorFactory.php
+++ b/src/SDK/Logs/LogRecordProcessorFactory.php
@@ -12,6 +12,7 @@ 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\MultiLogsProcessor;
 use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
 use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
 
@@ -19,7 +20,24 @@ class LogRecordProcessorFactory
 {
     public function create(LogRecordExporterInterface $exporter, ?MeterProviderInterface $meterProvider = null): LogRecordProcessorInterface
     {
-        $name = Configuration::getEnum(Variables::OTEL_PHP_LOGS_PROCESSOR);
+        $processors = [];
+        $list = Configuration::getList(Variables::OTEL_PHP_LOGS_PROCESSOR);
+        foreach ($list as $name) {
+            $processors[] = $this->createProcessor($name, $exporter, $meterProvider);
+        }
+
+        switch (count($processors)) {
+            case 0:
+                return NoopLogsProcessor::getInstance();
+            case 1:
+                return $processors[0];
+            default:
+                return new MultiLogsProcessor($processors);
+        }
+    }
+
+    private function createProcessor($name, LogRecordExporterInterface $exporter, ?MeterProviderInterface $meterProvider = null): LogRecordProcessorInterface
+    {
         switch ($name) {
             case KnownValues::VALUE_BATCH:
                 return new BatchLogsProcessor(
diff --git a/src/SDK/Logs/LoggerProvider.php b/src/SDK/Logs/LoggerProvider.php
index 8465e8ef..a4c2023e 100644
--- a/src/SDK/Logs/LoggerProvider.php
+++ b/src/SDK/Logs/LoggerProvider.php
@@ -46,7 +46,7 @@ class LoggerProvider implements LoggerProviderInterface
 
     public function forceFlush(CancellationInterface $cancellation = null): bool
     {
-        return $this->loggerSharedState->getProcessor()->forceFlush($cancellation);
+        return $this->loggerSharedState->forceFlush($cancellation);
     }
 
     public static function builder(): LoggerProviderBuilder
diff --git a/src/SDK/Logs/LoggerProviderBuilder.php b/src/SDK/Logs/LoggerProviderBuilder.php
index aea9b531..74afc0a6 100644
--- a/src/SDK/Logs/LoggerProviderBuilder.php
+++ b/src/SDK/Logs/LoggerProviderBuilder.php
@@ -6,22 +6,21 @@ namespace OpenTelemetry\SDK\Logs;
 
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeFactory;
+use OpenTelemetry\SDK\Logs\Processor\MultiLogsProcessor;
 use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
 
 class LoggerProviderBuilder
 {
-    private LogRecordProcessorInterface $processor;
+    /**
+     * @var array<LogRecordProcessorInterface>
+     */
+    private array $processors = [];
     private ?ResourceInfo $resource = null;
 
-    public function __construct()
+    public function addLogRecordProcessor(LogRecordProcessorInterface $processor): self
     {
-        $this->processor = new NoopLogsProcessor();
-    }
-
-    public function setLogRecordProcessor(LogRecordProcessorInterface $processor): self
-    {
-        $this->processor = $processor;
+        $this->processors[] = $processor;
 
         return $this;
     }
@@ -36,9 +35,21 @@ class LoggerProviderBuilder
     public function build(): LoggerProviderInterface
     {
         return new LoggerProvider(
-            $this->processor,
+            $this->buildProcessor(),
             new InstrumentationScopeFactory(Attributes::factory()),
             $this->resource
         );
     }
+
+    private function buildProcessor(): LogRecordProcessorInterface
+    {
+        switch (count($this->processors)) {
+            case 0:
+                return NoopLogsProcessor::getInstance();
+            case 1:
+                return $this->processors[0];
+            default:
+                return new MultiLogsProcessor($this->processors);
+        }
+    }
 }
diff --git a/src/SDK/Logs/LoggerSharedState.php b/src/SDK/Logs/LoggerSharedState.php
index eac6b5fd..aeeb4551 100644
--- a/src/SDK/Logs/LoggerSharedState.php
+++ b/src/SDK/Logs/LoggerSharedState.php
@@ -43,11 +43,18 @@ class LoggerSharedState
         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));
+        if ($this->shutdownResult !== null) {
+            return $this->shutdownResult;
+        }
+        $this->shutdownResult = $this->processor->shutdown($cancellation);
+
+        return $this->shutdownResult;
+    }
+
+    public function forceFlush(?CancellationInterface $cancellation = null): bool
+    {
+        return $this->processor->forceFlush($cancellation);
     }
 }
diff --git a/src/SDK/Logs/Processor/MultiLogsProcessor.php b/src/SDK/Logs/Processor/MultiLogsProcessor.php
new file mode 100644
index 00000000..798b483e
--- /dev/null
+++ b/src/SDK/Logs/Processor/MultiLogsProcessor.php
@@ -0,0 +1,62 @@
+<?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 MultiLogsProcessor implements LogRecordProcessorInterface
+{
+    // @var LogRecordProcessorInterface[]
+    private array $processors = [];
+
+    public function __construct(array $processors)
+    {
+        foreach ($processors as $processor) {
+            assert($processor instanceof LogRecordProcessorInterface);
+            $this->processors[] = $processor;
+        }
+    }
+
+    public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
+    {
+        foreach ($this->processors as $processor) {
+            $processor->onEmit($record, $context);
+        }
+    }
+
+    /**
+     * Returns `true` if all processors shut down successfully, else `false`
+     * Subsequent calls to `shutdown` are a no-op.
+     */
+    public function shutdown(?CancellationInterface $cancellation = null): bool
+    {
+        $result = true;
+        foreach ($this->processors as $processor) {
+            if (!$processor->shutdown($cancellation)) {
+                $result = false;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns `true` if all processors flush successfully, else `false`.
+     */
+    public function forceFlush(?CancellationInterface $cancellation = null): bool
+    {
+        $result = true;
+        foreach ($this->processors as $processor) {
+            if (!$processor->forceFlush($cancellation)) {
+                $result = false;
+            }
+        }
+
+        return $result;
+    }
+}


Test patch:
diff --git a/tests/Unit/Contrib/Newrelic/NewrelicSpanConverterTest.php b/tests/Unit/Contrib/Newrelic/NewrelicSpanConverterTest.php
index 13276f19..313935be 100644
--- a/tests/Unit/Contrib/Newrelic/NewrelicSpanConverterTest.php
+++ b/tests/Unit/Contrib/Newrelic/NewrelicSpanConverterTest.php
@@ -6,8 +6,10 @@ namespace OpenTelemetry\Tests\Unit\Contrib\Newrelic;
 
 use OpenTelemetry\Contrib\Newrelic\SpanConverter;
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
+use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
 use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
+use OpenTelemetry\SDK\Trace\SpanDataInterface;
 use OpenTelemetry\SemConv\ResourceAttributes;
 use OpenTelemetry\Tests\Unit\SDK\Util\SpanData;
 use PHPUnit\Framework\TestCase;
@@ -101,4 +103,32 @@ class NewrelicSpanConverterTest extends TestCase
         // containing multiple value types from being passed to the Exporter.
         $this->assertSame($listOfRandoms, $attributes['list-of-random']);
     }
+
+    /**
+     * @dataProvider droppedProvider
+     */
+    public function test_displays_non_zero_dropped_counts(int $dropped, bool $expected): void
+    {
+        $attributes = $this->createMock(AttributesInterface::class);
+        $attributes->method('getDroppedAttributesCount')->willReturn($dropped);
+        $spanData = $this->createMock(SpanDataInterface::class);
+        $spanData->method('getAttributes')->willReturn($attributes);
+
+        $converter = new SpanConverter();
+        $converted = $converter->convert([$spanData])[0];
+
+        if ($expected) {
+            $this->assertArrayHasKey(SpanConverter::KEY_DROPPED_ATTRIBUTES_COUNT, $converted['attributes']);
+        } else {
+            $this->assertArrayNotHasKey(SpanConverter::KEY_DROPPED_ATTRIBUTES_COUNT, $converted['attributes']);
+        }
+    }
+
+    public static function droppedProvider(): array
+    {
+        return [
+            'no dropped' => [0, false],
+            'some dropped' => [1, true],
+        ];
+    }
 }
diff --git a/tests/Unit/Contrib/Zipkin/ZipkinSpanConverterTest.php b/tests/Unit/Contrib/Zipkin/ZipkinSpanConverterTest.php
index 552e5cb2..fce00cae 100644
--- a/tests/Unit/Contrib/Zipkin/ZipkinSpanConverterTest.php
+++ b/tests/Unit/Contrib/Zipkin/ZipkinSpanConverterTest.php
@@ -10,8 +10,10 @@ use OpenTelemetry\API\Trace\StatusCode;
 use OpenTelemetry\Contrib\Zipkin\SpanConverter;
 use OpenTelemetry\Contrib\Zipkin\SpanKind as ZipkinSpanKind;
 use OpenTelemetry\SDK\Common\Attribute\Attributes;
+use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
 use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScope;
 use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Trace\SpanDataInterface;
 use OpenTelemetry\SDK\Trace\StatusData;
 use OpenTelemetry\Tests\Unit\SDK\Util\SpanData;
 use PHPUnit\Framework\TestCase;
@@ -247,4 +249,42 @@ class ZipkinSpanConverterTest extends TestCase
         // containing multiple value types from being passed to the Exporter.
         $this->assertSame('true,1,2,3,false,string-1,3.1415', $tags['list-of-random']);
     }
+
+    /**
+     * @dataProvider droppedProvider
+     * @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/common/mapping-to-non-otlp.md#dropped-attributes-count
+     */
+    public function test_displays_non_zero_dropped_counts(int $dropped, bool $expected): void
+    {
+        $attributes = $this->createMock(AttributesInterface::class);
+        $attributes->method('getDroppedAttributesCount')->willReturn($dropped);
+        $spanData = $this->createMock(SpanDataInterface::class);
+        $spanData->method('getAttributes')->willReturn($attributes);
+        $spanData->method('getLinks')->willReturn([]);
+        $spanData->method('getEvents')->willReturn([]);
+        $spanData->method('getTotalDroppedEvents')->willReturn($dropped);
+        $spanData->method('getTotalDroppedLinks')->willReturn($dropped);
+
+        $converter = new SpanConverter();
+        $converted = $converter->convert([$spanData])[0];
+        $tags = $converted['tags'];
+
+        if ($expected) {
+            $this->assertArrayHasKey(SpanConverter::KEY_DROPPED_EVENTS_COUNT, $tags);
+            $this->assertArrayHasKey(SpanConverter::KEY_DROPPED_LINKS_COUNT, $tags);
+            $this->assertArrayHasKey(SpanConverter::KEY_DROPPED_ATTRIBUTES_COUNT, $tags);
+        } else {
+            $this->assertArrayNotHasKey(SpanConverter::KEY_DROPPED_EVENTS_COUNT, $tags);
+            $this->assertArrayNotHasKey(SpanConverter::KEY_DROPPED_LINKS_COUNT, $tags);
+            $this->assertArrayNotHasKey(SpanConverter::KEY_DROPPED_ATTRIBUTES_COUNT, $tags);
+        }
+    }
+
+    public static function droppedProvider(): array
+    {
+        return [
+            'no dropped' => [0, false],
+            'some dropped' => [1, true],
+        ];
+    }
 }
diff --git a/tests/Unit/SDK/Logs/LogRecordProcessorFactoryTest.php b/tests/Unit/SDK/Logs/LogRecordProcessorFactoryTest.php
index ee3589cc..86356038 100644
--- a/tests/Unit/SDK/Logs/LogRecordProcessorFactoryTest.php
+++ b/tests/Unit/SDK/Logs/LogRecordProcessorFactoryTest.php
@@ -8,6 +8,7 @@ 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\MultiLogsProcessor;
 use OpenTelemetry\SDK\Logs\Processor\NoopLogsProcessor;
 use OpenTelemetry\SDK\Logs\Processor\SimpleLogsProcessor;
 use PHPUnit\Framework\TestCase;
@@ -54,11 +55,11 @@ class LogRecordProcessorFactoryTest extends TestCase
         (new LogRecordProcessorFactory())->create($this->createMock(LogRecordExporterInterface::class));
     }
 
-    public function test_rejects_multiple(): void
+    public function test_create_multiple(): void
     {
-        $this->setEnvironmentVariable('OTEL_PHP_LOGS_PROCESSOR', 'one,two');
-        $this->expectException(\InvalidArgumentException::class);
+        $this->setEnvironmentVariable('OTEL_PHP_LOGS_PROCESSOR', 'batch,simple');
+        $processor = (new LogRecordProcessorFactory())->create($this->createMock(LogRecordExporterInterface::class));
 
-        (new LogRecordProcessorFactory())->create($this->createMock(LogRecordExporterInterface::class));
+        $this->assertInstanceOf(MultiLogsProcessor::class, $processor);
     }
 }
diff --git a/tests/Unit/SDK/Logs/LoggerProviderBuilderTest.php b/tests/Unit/SDK/Logs/LoggerProviderBuilderTest.php
index 1187ac02..e9c55d4b 100644
--- a/tests/Unit/SDK/Logs/LoggerProviderBuilderTest.php
+++ b/tests/Unit/SDK/Logs/LoggerProviderBuilderTest.php
@@ -20,7 +20,7 @@ class LoggerProviderBuilderTest extends TestCase
         $processor = $this->createMock(LogRecordProcessorInterface::class);
         $resource = $this->createMock(ResourceInfo::class);
         $provider = LoggerProvider::builder()
-            ->setLogRecordProcessor($processor)
+            ->addLogRecordProcessor($processor)
             ->setResource($resource)
             ->build();
         $this->assertInstanceOf(LoggerProviderInterface::class, $provider);
diff --git a/tests/Unit/SDK/Logs/LoggerSharedStateTest.php b/tests/Unit/SDK/Logs/LoggerSharedStateTest.php
index 509746bd..73192bd5 100644
--- a/tests/Unit/SDK/Logs/LoggerSharedStateTest.php
+++ b/tests/Unit/SDK/Logs/LoggerSharedStateTest.php
@@ -53,7 +53,14 @@ class LoggerSharedStateTest extends TestCase
         $this->processor->expects($this->once())->method('shutdown')->willReturn(true);
 
         $this->assertFalse($this->loggerSharedState->hasShutdown());
-        $this->loggerSharedState->shutdown();
+        $this->assertTrue($this->loggerSharedState->shutdown());
         $this->assertTrue($this->loggerSharedState->hasShutdown());
     }
+
+    public function test_force_flush(): void
+    {
+        $this->processor->expects($this->once())->method('forceFlush')->willReturn(true);
+
+        $this->assertTrue($this->loggerSharedState->forceFlush());
+    }
 }