Skip to content

Commit

Permalink
log discarded span+log attributes (open-telemetry#1336)
Browse files Browse the repository at this point in the history
The SDK should log a warning once if there are any dropped span attributes, links, link attributes, events or event attributes.
The SDK should log a warning once if there are any dropped LogRecord attributes.
  • Loading branch information
brettmc authored Jun 26, 2024
1 parent a90f18c commit b6f9afb
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 2 deletions.
1 change: 0 additions & 1 deletion .phan/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@
'vendor/guzzlehttp',
'vendor/psr',
'vendor/php-http',
'vendor/phan/phan/src/Phan',
'vendor/phpunit/phpunit/src',
'vendor/google/protobuf/src',
'vendor/ramsey/uuid/src',
Expand Down
2 changes: 1 addition & 1 deletion src/SDK/Logs/LogRecordLimits.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
class LogRecordLimits
{
/**
* @internal Use {@see SpanLimitsBuilder} to create {@see SpanLimits} instance.
* @internal Use {@see LogRecordLimitsBuilder} to create {@see LogRecordLimits} instance.
*/
public function __construct(private readonly AttributesFactoryInterface $attributesFactory)
{
Expand Down
8 changes: 8 additions & 0 deletions src/SDK/Logs/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OpenTelemetry\SDK\Logs;

use OpenTelemetry\API\Behavior\LogsMessagesTrait;
use OpenTelemetry\API\Logs\LoggerInterface;
use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
Expand All @@ -16,6 +17,8 @@
*/
class Logger implements LoggerInterface
{
use LogsMessagesTrait;

/**
* @internal
*/
Expand All @@ -33,5 +36,10 @@ public function emit(LogRecord $logRecord): void
$readWriteLogRecord,
$readWriteLogRecord->getContext(),
);
if ($readWriteLogRecord->getAttributes()->getDroppedAttributesCount()) {
self::logWarning('Dropped log attributes', [
'attributes' => $readWriteLogRecord->getAttributes()->getDroppedAttributesCount(),
]);
}
}
}
34 changes: 34 additions & 0 deletions src/SDK/Trace/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OpenTelemetry\SDK\Trace;

use OpenTelemetry\API\Behavior\LogsMessagesTrait;
use OpenTelemetry\API\Common\Time\Clock;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\API\Trace\SpanContextInterface;
Expand All @@ -17,6 +18,7 @@

final class Span extends API\Span implements ReadWriteSpanInterface
{
use LogsMessagesTrait;

/** @var list<EventInterface> */
private array $events = [];
Expand Down Expand Up @@ -252,6 +254,8 @@ public function end(int $endEpochNanos = null): void
$this->endEpochNanos = $endEpochNanos ?? Clock::getDefault()->now();
$this->hasEnded = true;

$this->checkForDroppedElements();

$this->spanProcessor->onEnd($this);
}

Expand Down Expand Up @@ -329,4 +333,34 @@ public function getResource(): ResourceInfo
{
return $this->resource;
}

private function checkForDroppedElements(): void
{
$spanData = $this->toSpanData(); //@todo could be optimized to reduce overhead of multiple calls
$droppedLinkAttributes = 0;
$droppedEventAttributes = 0;
array_map(function (EventInterface $event) use (&$droppedEventAttributes) {
$droppedEventAttributes += $event->getAttributes()->getDroppedAttributesCount();
}, $spanData->getEvents());
array_map(function (LinkInterface $link) use (&$droppedLinkAttributes) {
$droppedLinkAttributes += $link->getAttributes()->getDroppedAttributesCount();
}, $spanData->getLinks());
if (
$spanData->getTotalDroppedLinks() ||
$spanData->getTotalDroppedEvents() ||
$spanData->getAttributes()->getDroppedAttributesCount() ||
$droppedEventAttributes ||
$droppedLinkAttributes
) {
self::logWarning('Dropped span attributes, links or events', [
'trace_id' => $spanData->getTraceId(),
'span_id' => $spanData->getSpanId(),
'attributes' => $spanData->getAttributes()->getDroppedAttributesCount(),
'links' => $spanData->getTotalDroppedLinks(),
'link_attributes' => $droppedLinkAttributes,
'events' => $spanData->getTotalDroppedEvents(),
'event_attributes' => $droppedEventAttributes,
]);
}
}
}
2 changes: 2 additions & 0 deletions tests/Integration/SDK/SpanBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Mockery;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\MockInterface;
use OpenTelemetry\API\Behavior\Internal\Logging;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\API\Trace\SpanContext;
use OpenTelemetry\API\Trace\SpanContextValidator;
Expand Down Expand Up @@ -96,6 +97,7 @@ public function test_add_link_invalid(): void

public function test_add_link_dropping_links(): void
{
Logging::disable();
$maxNumberOfLinks = 8;
$tracerProvider = new TracerProvider([], null, null, (new SpanLimitsBuilder())->setLinkCountLimit($maxNumberOfLinks)->build());
$spanBuilder = $tracerProvider
Expand Down
2 changes: 2 additions & 0 deletions tests/Integration/SDK/TracerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OpenTelemetry\Tests\Integration\SDK;

use OpenTelemetry\API\Behavior\Internal\Logging;
use OpenTelemetry\API\Trace as API;
use OpenTelemetry\API\Trace\NonRecordingSpan;
use OpenTelemetry\API\Trace\SpanContext;
Expand Down Expand Up @@ -113,6 +114,7 @@ public function test_factory_returns_noop_tracer_when_sdk_disabled(): void

public function test_general_identity_attributes_are_dropped_by_default(): void
{
Logging::disable();
$exporter = new InMemoryExporter();
$tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter));
$tracer = $tracerProvider->getTracer('test');
Expand Down
43 changes: 43 additions & 0 deletions tests/Unit/SDK/Logs/LoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

namespace OpenTelemetry\Tests\Unit\SDK\Logs;

use OpenTelemetry\API\Behavior\Internal\Logging;
use OpenTelemetry\API\Behavior\Internal\LogWriter\LogWriterInterface;
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\LogRecordLimitsBuilder;
use OpenTelemetry\SDK\Logs\LogRecordProcessorInterface;
use OpenTelemetry\SDK\Logs\ReadWriteLogRecord;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -21,16 +25,28 @@
#[CoversClass(Logger::class)]
class LoggerTest extends TestCase
{
/** @var LogWriterInterface&MockObject $logWriter */
private LogWriterInterface $logWriter;
private LoggerSharedState $sharedState;
private LogRecordProcessorInterface $processor;
private InstrumentationScope $scope;

public function setUp(): void
{
$limits = (new LogRecordLimitsBuilder())->setAttributeCountLimit(1)->build();
$this->sharedState = $this->createMock(LoggerSharedState::class);
$this->sharedState->method('getLogRecordLimits')->willReturn($limits);
$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

$this->logWriter = $this->createMock(LogWriterInterface::class);
Logging::setLogWriter($this->logWriter);
}

public function tearDown(): void
{
Logging::reset();
}

public function test_log_record(): void
Expand Down Expand Up @@ -66,4 +82,31 @@ public function test_sets_observed_timestamp_on_emit(): void

$logger->emit($record);
}

public function test_logs_dropped_attributes(): void
{
$this->logWriter
->expects($this->once())
->method('write')
->with(
$this->equalTo('warning'),
$this->stringContains('Dropped'),
$this->callback(function (array $context): bool {
$this->assertArrayHasKey('attributes', $context);
$this->assertSame(2, $context['attributes']);

return true;
}),
);
$logger = new Logger($this->sharedState, $this->scope);
$record = new LogRecord();
//limit is 1
$record->setAttributes([
'one' => 'attr_one',
'two' => 'attr_two',
'three' => 'attr_three',
]);

$logger->emit($record);
}
}
42 changes: 42 additions & 0 deletions tests/Unit/SDK/Trace/SpanTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
#[CoversClass(Span::class)]
class SpanTest extends MockeryTestCase
{
private const TRACE_ID = 'e4a8d4e0d75c0702200af2882cb16c6b';
private const SPAN_ID = '46701247e52c2d1b';
private const SPAN_NAME = 'test_span';
private const NEW_SPAN_NAME = 'new_test_span';
private const START_EPOCH = 1000123789654;
Expand Down Expand Up @@ -655,6 +657,7 @@ public function test_attribute_length(): void
public function test_dropping_attributes(): void
{
$maxNumberOfAttributes = 8;
$this->expectDropped(8, 0, 0);
$span = $this->createTestSpan(API\SpanKind::KIND_INTERNAL, (new SpanLimitsBuilder())->setAttributeCountLimit($maxNumberOfAttributes)->build());

foreach (range(1, $maxNumberOfAttributes * 2) as $idx) {
Expand All @@ -676,6 +679,7 @@ public function test_dropping_attributes(): void
public function test_dropping_attributes_provided_via_span_builder(): void
{
$maxNumberOfAttributes = 8;
$this->expectDropped(8, 0, 0);

$attributesBuilder = Attributes::factory()->builder();

Expand Down Expand Up @@ -704,6 +708,7 @@ public function test_dropping_attributes_provided_via_span_builder(): void

public function test_dropping_events(): void
{
$this->expectDropped(0, 8, 0);
$maxNumberOfEvents = 8;
$span = $this->createTestSpan(API\SpanKind::KIND_INTERNAL, (new SpanLimitsBuilder())->setEventCountLimit($maxNumberOfEvents)->build());

Expand All @@ -719,6 +724,26 @@ public function test_dropping_events(): void
$span->end();
}

public function test_dropping_links(): void
{
$maxNumberOfLinks = 8;
$expectedDroppedLinks = 9; //test span contains one link by default
$this->expectDropped(0, 0, $expectedDroppedLinks);
$span = $this->createTestSpan(API\SpanKind::KIND_INTERNAL, (new SpanLimitsBuilder())->setLinkCountLimit($maxNumberOfLinks)->build());
$ctx = SpanContext::create(self::TRACE_ID, self::SPAN_ID);

foreach (range(1, $maxNumberOfLinks * 2) as $_idx) {
$span->addLink($ctx);
$this->testClock->advanceSeconds();
}

$spanData = $span->toSpanData();
$this->assertCount($maxNumberOfLinks, $spanData->getLinks());
$this->assertSame($expectedDroppedLinks, $spanData->getTotalDroppedLinks());

$span->end();
}

// endregion SDK
#[Group('trace-compliance')]
public function test_set_attributes_merges_attributes(): void
Expand Down Expand Up @@ -915,4 +940,21 @@ private function assertSpanData(
$this->assertSame($hasEnded, $spanData->hasEnded());
$this->assertEquals($attributes, $spanData->getAttributes());
}

private function expectDropped(int $attributes, int $events, int $links): void
{
$this->logWriter->expects($this->atLeastOnce())->method('write')->with(
$this->anything(),
$this->stringContains('Dropped span attributes'),
$this->callback(function (array $context) use ($attributes, $events, $links) {
$this->assertSame($context['attributes'], $attributes);
$this->assertSame($context['events'], $events);
$this->assertSame($context['links'], $links);
$this->assertNotNull($context['trace_id']);
$this->assertNotNull($context['span_id']);

return true;
}),
);
}
}

0 comments on commit b6f9afb

Please sign in to comment.