Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement mutable ReadWriteLogRecord #1482

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/SDK/Logs/Exporter/InMemoryExporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OpenTelemetry\SDK\Common\Future\CompletedFuture;
use OpenTelemetry\SDK\Common\Future\FutureInterface;
use OpenTelemetry\SDK\Logs\LogRecordExporterInterface;
use OpenTelemetry\SDK\Logs\ReadableLogRecord;

class InMemoryExporter implements LogRecordExporterInterface
{
Expand All @@ -22,7 +23,7 @@ public function __construct(private readonly ArrayObject $storage = new ArrayObj
public function export(iterable $batch, ?CancellationInterface $cancellation = null): FutureInterface
{
foreach ($batch as $record) {
$this->storage->append($record);
$this->storage->append($this->convert($record));
}

return new CompletedFuture(true);
Expand All @@ -42,4 +43,19 @@ public function getStorage(): ArrayObject
{
return $this->storage;
}

private function convert(ReadableLogRecord $record): array
{
return [
'timestamp' => $record->getTimestamp(),
'observed_timestamp' => $record->getObservedTimestamp(),
'severity_number' => $record->getSeverityNumber(),
'severity_text' => $record->getSeverityText(),
'body' => $record->getBody(),
'attributes' => $record->getAttributes()->toArray(),
'trace_id' => $record->getSpanContext()?->getTraceId(),
'span_id' => $record->getSpanContext()?->getSpanId(),
'trace_flags' => $record->getSpanContext()?->getTraceFlags(),
];
}
}
2 changes: 1 addition & 1 deletion src/SDK/Logs/LogRecordProcessorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

interface LogRecordProcessorInterface
{
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void;
public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void;
public function shutdown(?CancellationInterface $cancellation = null): bool;
public function forceFlush(?CancellationInterface $cancellation = null): bool;
}
2 changes: 1 addition & 1 deletion src/SDK/Logs/Processor/BatchLogRecordProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public function __construct(
});
}

public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void
{
if ($this->closed) {
return;
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Logs/Processor/MultiLogRecordProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct(array $processors)
}
}

public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void
{
foreach ($this->processors as $processor) {
$processor->onEmit($record, $context);
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Logs/Processor/NoopLogRecordProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static function getInstance(): self
/**
* @codeCoverageIgnore
*/
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Logs/Processor/SimpleLogRecordProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function __construct(private readonly LogRecordExporterInterface $exporte
/**
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/sdk.md#onemit
*/
public function onEmit(ReadWriteLogRecord $record, ?ContextInterface $context = null): void
public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void
{
$this->exporter->export([$record]);
}
Expand Down
13 changes: 13 additions & 0 deletions src/SDK/Logs/ReadWriteLogRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,17 @@

class ReadWriteLogRecord extends ReadableLogRecord
{
public function setAttribute(string $name, mixed $value): self
{
$this->attributesBuilder->offsetSet($name, $value);

return $this;
}

public function removeAttribute(string $key): self
{
$this->attributesBuilder->offsetUnset($key);

return $this;
}
}
11 changes: 5 additions & 6 deletions src/SDK/Logs/ReadableLogRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OpenTelemetry\API\Trace\SpanContextInterface;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\SDK\Common\Attribute\AttributesBuilderInterface;
use OpenTelemetry\SDK\Common\Attribute\AttributesInterface;
use OpenTelemetry\SDK\Common\Attribute\LogRecordAttributeValidator;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
Expand All @@ -20,7 +21,7 @@
*/
class ReadableLogRecord extends LogRecord
{
protected AttributesInterface $convertedAttributes;
protected AttributesBuilderInterface $attributesBuilder;
protected SpanContextInterface $spanContext;

public function __construct(
Expand All @@ -38,12 +39,10 @@ public function __construct(
$this->severityNumber = $logRecord->severityNumber;
$this->severityText = $logRecord->severityText;

//convert attributes now so that excess data is not sent to processors
$this->convertedAttributes = $this->loggerSharedState
$this->attributesBuilder = $this->loggerSharedState
->getLogRecordLimits()
->getAttributeFactory()
->builder($logRecord->attributes, new LogRecordAttributeValidator())
->build();
->builder($logRecord->attributes, new LogRecordAttributeValidator());
}

public function getInstrumentationScope(): InstrumentationScopeInterface
Expand Down Expand Up @@ -96,6 +95,6 @@ public function getBody()

public function getAttributes(): AttributesInterface
{
return $this->convertedAttributes;
return $this->attributesBuilder->build();
}
}
74 changes: 74 additions & 0 deletions tests/Integration/SDK/Logs/LoggerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Integration\SDK\Logs;

use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\Context\ContextInterface;
use OpenTelemetry\SDK\Common\Future\CancellationInterface;
use OpenTelemetry\SDK\Logs\Exporter\InMemoryExporter;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
use OpenTelemetry\SDK\Logs\Processor\MultiLogRecordProcessor;
use OpenTelemetry\SDK\Logs\Processor\SimpleLogRecordProcessor;
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;

#[CoversNothing]
class LoggerTest extends TestCase
{
/**
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.40.0/specification/logs/sdk.md#onemit
*/
#[Group('logs-compliance')]
public function test_log_record_mutations_visible_to_later_processors(): void
{
$logRecord = (new LogRecord())
->setAttributes(['foo' => 'bar']);
$storage = new \ArrayObject();
$exporter = new InMemoryExporter($storage);
$mutator = new class($exporter) implements LogRecordProcessorInterface {
public function __construct(private readonly InMemoryExporter $exporter)
{
}

public function onEmit(ReadWriteLogRecord &$record, ?ContextInterface $context = null): void
{
$record->setAttributes(['baz' => 'bat']);
$this->exporter->export([$record]);
}

public function shutdown(?CancellationInterface $cancellation = null): bool
{
return true;
}

public function forceFlush(?CancellationInterface $cancellation = null): bool
{
return true;
}
};
$multi = new MultiLogRecordProcessor([
new SimpleLogRecordProcessor($exporter),
$mutator,
new SimpleLogRecordProcessor($exporter),
]);
$logger = LoggerProvider::builder()->addLogRecordProcessor($multi)->build()->getLogger('test');

$this->assertCount(0, $storage);
$logger->emit($logRecord);
$this->assertCount(3, $storage);

$first = $storage[0]; //@var array $first
$this->assertSame(['foo' => 'bar'], $first['attributes'], 'original attributes'); //@phpstan-ignore-line

$second = $storage[1]; //@var array $second
$this->assertSame(['foo' => 'bar', 'baz' => 'bat'], $second['attributes'], 'mutated attributes'); //@phpstan-ignore-line

$third = $storage[2]; //@var array $third
$this->assertSame(['foo' => 'bar', 'baz' => 'bat'], $third['attributes'], 'attributes after mutation by second processor'); //@phpstan-ignore-line
}
}
92 changes: 92 additions & 0 deletions tests/Unit/SDK/Logs/ReadWriteLogRecordTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace Unit\SDK\Logs;

use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Logs\LoggerSharedState;
use OpenTelemetry\SDK\Logs\LogRecordLimitsBuilder;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(ReadWriteLogRecord::class)]
class ReadWriteLogRecordTest extends TestCase
{
private ReadWriteLogRecord $record;

public function setUp(): void
{
$limits = (new LogRecordLimitsBuilder())->setAttributeCountLimit(10)->build();
$loggerSharedState = new LoggerSharedState(
ResourceInfoFactory::emptyResource(),
$limits,
$this->createMock(LogRecordProcessorInterface::class)
);
$record = (new LogRecord())
->setTimestamp(1)
->setObservedTimestamp(2)
->setSeverityText('severity')
->setSeverityNumber(3)
->setBody('body')
->setAttributes(['key' => 'value']);

$this->record = new ReadWriteLogRecord(
$this->createMock(InstrumentationScopeInterface::class),
$loggerSharedState,
$record
);
}

public function test_modify_timestamp(): void
{
$this->record->setTimestamp(4);
$this->assertEquals(4, $this->record->getTimestamp());
}

public function test_set_observed_timestamp(): void
{
$this->record->setObservedTimestamp(5);
$this->assertEquals(5, $this->record->getObservedTimestamp());
}

public function test_set_severity_text(): void
{
$this->record->setSeverityText('severity2');
$this->assertEquals('severity2', $this->record->getSeverityText());
}

public function test_set_severity_number(): void
{
$this->record->setSeverityNumber(6);
$this->assertEquals(6, $this->record->getSeverityNumber());
}

public function test_set_body(): void
{
$this->record->setBody('body2');
$this->assertEquals('body2', $this->record->getBody());
}

public function test_add_attribute(): void
{
$this->record->setAttribute('key2', 'value2');
$this->assertEquals(['key' => 'value', 'key2' => 'value2'], $this->record->getAttributes()->toArray());
}

public function test_remove_attribute(): void
{
$this->record->removeAttribute('key');
$this->assertEquals([], $this->record->getAttributes()->toArray());
}

public function test_modify_attribute(): void
{
$this->record->setAttribute('key', 'updated');
$this->assertEquals(['key' => 'updated'], $this->record->getAttributes()->toArray());
}
}
Loading