Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions src/Instrumentation/AwsSdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor/
composer.lock
45 changes: 45 additions & 0 deletions src/Instrumentation/AwsSdk/.php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('vendor')
->exclude('tests/Unit81') //contains php8.1 syntax
->exclude('var/cache')
->exclude('tests/coverage')
->in(__DIR__);

$config = new PhpCsFixer\Config();
return $config->setRules([
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'none'],
'is_null' => true,
'modernize_types_casting' => true,
'ordered_imports' => true,
'php_unit_construct' => true,
'single_line_comment_style' => true,
'yoda_style' => false,
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => true,
'cast_spaces' => true,
'declare_strict_types' => true,
'type_declaration_spaces' => true,
'include' => true,
'lowercase_cast' => true,
'new_with_parentheses' => true,
'no_extra_blank_lines' => true,
'no_leading_import_slash' => true,
'echo_tag_syntax' => true,
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_types' => true,
'short_scalar_cast' => true,
'blank_lines_before_namespace' => true,
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
->setRiskyAllowed(true)
->setFinder($finder);

25 changes: 25 additions & 0 deletions src/Instrumentation/AwsSdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[![Releases](https://img.shields.io/badge/releases-purple)](https://github.com/opentelemetry-php/contrib-auto-aws-sdk/releases)
[![Issues](https://img.shields.io/badge/issues-pink)](https://github.com/open-telemetry/opentelemetry-php/issues)
[![Source](https://img.shields.io/badge/source-contrib-green)](https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Instrumentation/AwsSdk)
[![Mirror](https://img.shields.io/badge/mirror-opentelemetry--php--contrib-blue)](https://github.com/opentelemetry-php/contrib-auto-aws-sdk)
[![Latest Version](http://poser.pugx.org/open-telemetry/opentelemetry-auto-guzzle/v/unstable)](https://packagist.org/packages/open-telemetry/opentelemetry-auto-aws-sdk/)
[![Stable](http://poser.pugx.org/open-telemetry/opentelemetry-auto-aws-sdk/v/stable)](https://packagist.org/packages/open-telemetry/opentelemetry-auto-aws-sdk/)

This is a read-only subtree split of https://github.com/open-telemetry/opentelemetry-php-contrib.

# OpenTelemetry AWS SDK auto-instrumentation
Please read https://opentelemetry.io/docs/instrumentation/php/automatic/ for instructions on how to
install and configure the extension and SDK.

## Overview
Auto-instrumentation hooks are registered via composer.

* create spans automatically for each AWS SDK request that is sent

## Configuration

The extension can be disabled via [runtime configuration](https://opentelemetry.io/docs/instrumentation/php/sdk/#configuration):

```shell
OTEL_PHP_DISABLED_INSTRUMENTATIONS=aws-sdk
```
20 changes: 20 additions & 0 deletions src/Instrumentation/AwsSdk/_register.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);

use OpenTelemetry\Contrib\Instrumentation\AwsSdk\AwsSdkInstrumentation;
use OpenTelemetry\SDK\Sdk;

if (class_exists(Sdk::class)
&& Sdk::isInstrumentationDisabled(AwsSdkInstrumentation::NAME)) {
return;
}

if (!extension_loaded('opentelemetry')) {
trigger_error(
'The opentelemetry extension must be loaded to use the AWS SDK auto‑instrumentation',
E_USER_WARNING
);
return;
}

AwsSdkInstrumentation::register();
47 changes: 47 additions & 0 deletions src/Instrumentation/AwsSdk/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "open-telemetry/opentelemetry-auto-aws-sdk",
"description": "OpenTelemetry auto‑instrumentation for the AWS SDK",
"keywords": ["opentelemetry", "otel", "open-telemetry", "tracing", "aws-sdk", "instrumentation"],
"type": "library",
"homepage": "https://opentelemetry.io/docs/php",
"readme": "./README.md",
"license": "Apache-2.0",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": "^8.2",
"aws/aws-sdk-php": "^3",
"ext-opentelemetry": "*",
"open-telemetry/api": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3",
"guzzlehttp/promises": "^2",
"nyholm/psr7": "*",
"phan/phan": "^5.0",
"phpstan/phpstan-mockery": "^1.1.0",
"phpstan/phpstan": "^1.1",
"phpstan/phpstan-phpunit": "^1.0",
"psalm/plugin-phpunit": "^0.19.2",
"open-telemetry/sdk": "^1.0",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "6.4.0"
},
"autoload": {
"psr-4": {
"OpenTelemetry\\Contrib\\Instrumentation\\AwsSdk\\": "src/"
},
"files": ["_register.php"]
},
"autoload-dev": {
"psr-4": {
"OpenTelemetry\\Tests\\Instrumentation\\AwsSdk\\": "tests/"
}
},
"config": {
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": true
}
}
}
12 changes: 12 additions & 0 deletions src/Instrumentation/AwsSdk/phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon

parameters:
tmpDir: var/cache/phpstan
level: 5
paths:
- src
- tests
excludePaths:
analyseAndScan:
- tests/Unit
47 changes: 47 additions & 0 deletions src/Instrumentation/AwsSdk/phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
cacheResult="false"
colors="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
forceCoversAnnotation="false"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
stopOnSkipped="false"
stopOnRisky="false"
timeoutForSmallTests="1"
timeoutForMediumTests="10"
timeoutForLargeTests="60"
verbose="true">

<coverage processUncoveredFiles="true" disableCodeCoverageIgnore="false">
<include>
<directory>src</directory>
</include>
</coverage>

<php>
<ini name="date.timezone" value="UTC" />
<ini name="display_errors" value="On" />
<ini name="display_startup_errors" value="On" />
<ini name="error_reporting" value="E_ALL" />
</php>

<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>

</phpunit>
15 changes: 15 additions & 0 deletions src/Instrumentation/AwsSdk/psalm.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
cacheDirectory="var/cache/psalm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
<projectFiles>
<directory name="src"/>
<directory name="tests"/>
</projectFiles>
<plugins>
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
</plugins>
</psalm>
89 changes: 89 additions & 0 deletions src/Instrumentation/AwsSdk/src/AwsSdkInstrumentation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

// src/Instrumentation/AwsSdk/AwsSdkInstrumentation.php
declare(strict_types=1);

namespace OpenTelemetry\Contrib\Instrumentation\AwsSdk;

use Aws\AwsClient;
use Aws\Exception\AwsException;
use Aws\ResultInterface;
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\Context\Context;
use function OpenTelemetry\Instrumentation\hook;

final class AwsSdkInstrumentation
{
public const NAME = 'aws-sdk';

public static function register(): void
{
$inst = new CachedInstrumentation(
'io.opentelemetry.contrib.php.aws-sdk',
null,
'https://opentelemetry.io/schemas/1.30.0',
);

/**
* ② Intercept the low‑level `execute` call that actually
* performs the HTTP request and has the Command object.
*/
hook(
AwsClient::class,
'execute',
pre: static function (
AwsClient $c,
array $params,
string $class,
string $func,
?string $file,
?int $line
) use ($inst) {
$cmd = $params[0];
$builder = $inst->tracer()
->spanBuilder("{$c->getApi()->getServiceName()}.{$cmd->getName()}")
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute('rpc.system', 'aws-api')
->setAttribute('rpc.method', $cmd->getName())
->setAttribute('rpc.service', $c->getApi()->getServiceName())
->setAttribute('aws.region', $c->getRegion())
->setAttribute('code.function', $func)
->setAttribute('code.namespace', $class)
->setAttribute('code.filepath', $file)
->setAttribute('code.line_number', $line);

$span = $builder->startSpan();
Context::storage()->attach($span->storeInContext(Context::getCurrent()));
},
post: static function (
AwsClient $c,
array $params,
mixed $result,
?\Throwable $ex
) {
$scope = Context::storage()->scope();
if (!$scope) {
return;
}
$span = Span::fromContext($scope->context());
$scope->detach();

if ($result instanceof ResultInterface && isset($result['@metadata'])) {
$span->setAttribute('http.status_code', $result['@metadata']['statusCode']);
$span->setAttribute('aws.requestId', $result['@metadata']['headers']['x-amz-request-id']);
}
if ($ex) {
if ($ex instanceof AwsException && $ex->getAwsRequestId() !== null) {
$span->setAttribute('aws.requestId', $ex->getAwsRequestId());
}
$span->recordException($ex);
$span->setStatus(StatusCode::STATUS_ERROR, $ex->getMessage());
}
$span->end();
}
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace OpenTelemetry\Tests\Contrib\Instrumentation\AwsSdk\Integration;

use ArrayObject;
use Aws\MockHandler;
use Aws\Result;
use Aws\S3\S3Client;
use OpenTelemetry\API\Instrumentation\Configurator;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Context\ScopeInterface;
use OpenTelemetry\SDK\Trace\ImmutableSpan;
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use PHPUnit\Framework\TestCase;

/**
* @covers \OpenTelemetry\Contrib\Instrumentation\AwsSdk\AwsSdkInstrumentation
*/
class AwsSdkInstrumentationTest extends TestCase
{
private S3Client $client;
private MockHandler $mock;
private ArrayObject $spans;
private ScopeInterface $scope;

public function setUp(): void
{
$this->spans = new ArrayObject();
$tracerProvider = new TracerProvider(
new SimpleSpanProcessor(new InMemoryExporter($this->spans))
);
$this->scope = Configurator::create()
->withTracerProvider($tracerProvider)
->withPropagator(TraceContextPropagator::getInstance())
->activate();

$this->mock = new MockHandler();
$this->mock->append(new Result([
'@metadata' => [
'statusCode' => 200,
'headers' => [
'x-amz-request-id' => 'TEST-REQUEST-ID',
],
],
]));

$this->client = new S3Client([
'region' => 'us-west-2',
'version' => 'latest',
'handler' => $this->mock,
]);
}

public function tearDown(): void
{
$this->scope->detach();
}

public function test_listBuckets_generates_one_aws_span_with_expected_attributes(): void
{
$this->client->listBuckets();

$this->assertCount(1, $this->spans);

$span = $this->spans->offsetGet(0);

$this->assertInstanceOf(ImmutableSpan::class, $span);

$this->assertSame('s3.ListBuckets', $span->getName());

$attrs = $span->getAttributes();
$this->assertSame('aws-api', $attrs->get('rpc.system'));
$this->assertSame('s3', $attrs->get('rpc.service'));
$this->assertSame('ListBuckets', $attrs->get('rpc.method'));
$this->assertSame('us-west-2', $attrs->get('aws.region'));
$this->assertSame(200, $attrs->get('http.status_code'));
$this->assertSame('TEST-REQUEST-ID', $attrs->get('aws.requestId'));
}
}
Empty file.