Created
December 6, 2024 13:45
-
-
Save breda/79c8f38af5fac7b1c0d3bb15bff2c314 to your computer and use it in GitHub Desktop.
Simple class to add OpenTelemetry instrumentation for a class/method
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Deps: | |
* composer require open-telemetry/sem-conv | |
* composer require open-telemetry/api | |
* composer require open-telemetry/context | |
* | |
* ext-opentelemetry | |
*/ | |
declare(strict_types=1); | |
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 OpenTelemetry\SemConv\TraceAttributes; | |
use Throwable; | |
use function array_map; | |
use function implode; | |
use function is_object; | |
use function OpenTelemetry\Instrumentation\hook; | |
use function sprintf; | |
use function strrpos; | |
use function substr; | |
/** | |
* ClassInstrumentation is a re-usable helper class to register OpenTelemetry instrumentation for a specific class and method. | |
* | |
* It will create a span for each call to the method. | |
* The span will have the following attributes (at least): | |
* - code.function: the name of the function | |
* - code.namespace: the namespace of the class | |
* - code.filepath: the file path of the class | |
* - code.lineno: the line number of the function | |
* - code.params: the parameters of the function | |
* | |
* | |
* Example usage: | |
* ```php | |
* ClassInstrumentation::register('BookRepository', 'findById'); | |
* ``` | |
*/ | |
class ClassInstrumentation | |
{ | |
/* CHANGE ME */ | |
public const string NAME = 'name'; | |
public static function register(string $className, string $methodName): void | |
{ | |
hook( | |
class: $className, | |
function: $methodName, | |
pre: static function ( | |
object $instance, | |
array $params, | |
string $class, | |
string $function, | |
?string $filename, | |
?int $lineno, | |
): void { | |
$instrumentation = new CachedInstrumentation('opentelemetry.' . self::NAME); | |
$params = array_map(static function ($param) { | |
return is_object($param) ? $param::class : '"' . $param . '"'; | |
}, $params); | |
$params = sprintf('[%s]', implode(', ', $params)); | |
$namespace = substr($class, 0, (int) strrpos($class, '\\')); | |
$className = substr($class, (int) strrpos($class, '\\') + 1); | |
$builder = $instrumentation->tracer() | |
->spanBuilder(sprintf('%s::%s', $className, $function)) | |
->setSpanKind(SpanKind::KIND_CLIENT) | |
->setAttribute(TraceAttributes::CODE_FUNCTION, $function) | |
->setAttribute(TraceAttributes::CODE_NAMESPACE, $namespace) | |
->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) | |
->setAttribute(TraceAttributes::CODE_LINENO, $lineno) | |
->setAttribute('code.params', $params); | |
$parent = Context::getCurrent(); | |
$span = $builder->startSpan(); | |
Context::storage()->attach($span->storeInContext($parent)); | |
}, | |
post: static function (object $instance, array $params, mixed $statement, ?Throwable $exception): void { | |
$scope = Context::storage()->scope(); | |
if ($scope === null) { | |
return; | |
} | |
$scope->detach(); | |
$span = Span::fromContext($scope->context()); | |
if ($exception !== null) { | |
$span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); | |
$span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); | |
} | |
$span->end(); | |
}, | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment