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

observe methods from attribute #146

Merged
merged 31 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3b5765c
observe methods from attribute
brettmc Aug 8, 2024
769a341
use method available in 8.0
brettmc Aug 8, 2024
22747ac
add SpanAttribute attribute
brettmc Aug 9, 2024
dc67507
configurable attribute pre/post function
brettmc Aug 10, 2024
1f7cb58
disable attribute-based hooking from ini
brettmc Aug 10, 2024
6e1eadb
generate attribute classes, docs, ide stubs
brettmc Aug 10, 2024
efda093
move attribute find into function
brettmc Aug 10, 2024
6877f79
check interfaces for WithSpan
brettmc Aug 10, 2024
f0f12a6
use a const for attribute fqns
brettmc Aug 10, 2024
b407ce3
implement WithSpan on interfaces
brettmc Aug 11, 2024
60c94f4
implement SpanAttribute on interface
brettmc Aug 11, 2024
7724912
combine WithSpan and SpanAttribute attributes into one array
brettmc Aug 12, 2024
d5ad17d
update comments, readme
brettmc Aug 12, 2024
3c1bef3
add test for invalid callback
brettmc Aug 13, 2024
e2f4cc2
rename default handler to WithSpanHandler
brettmc Aug 21, 2024
6ab15d6
only fetch attribute if WithSpan, warn if disabled and used
brettmc Aug 21, 2024
50e8be9
test whether non-simple types can be passed via SpanAttribute
brettmc Aug 21, 2024
08058c8
disable by default
brettmc Aug 21, 2024
f40839f
remove null type
brettmc Aug 21, 2024
e6afdc4
Merge branch 'main' into attribute_observer
brettmc Aug 28, 2024
00416fe
removing attributes
brettmc Aug 29, 2024
22c37c3
fix wonky arginfo generation
brettmc Aug 29, 2024
9ca3c9e
format
brettmc Aug 29, 2024
5f18d95
Merge branch 'attribute_observer' of github.com:brettmc/opentelemetry…
brettmc Aug 29, 2024
2abf899
force add mocks
brettmc Aug 29, 2024
093613a
don't gitignore mocks
brettmc Aug 29, 2024
11bcb5f
skip attribute tests for 8.0
brettmc Aug 29, 2024
77f2bc7
update readme
brettmc Aug 29, 2024
610380a
un-bork
brettmc Aug 29, 2024
ccb719a
tidy attribute tests
brettmc Aug 29, 2024
0d0a4d6
remove unimplemented function from header
brettmc Aug 29, 2024
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
26 changes: 26 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,32 @@ Basic usage is in the `tests/` directory.

A more advanced example: https://github.com/open-telemetry/opentelemetry-php-contrib/pull/78/files

# `opentelemetry.stub.php` and `opentelemetry_argoinfo.h`

The build system will generate `opentelemetry_arginfo.h` from `opentelemetry.stub.php`, but it seems
to generate an invalid output. Until that starts working as expected:

Manually fix:

```diff
-ZEND_FUNCTION(OpenTelemetry_Instrumentation_hook);
+ZEND_FUNCTION(hook);
```

```diff
-ZEND_NS_FALIAS("OpenTelemetry\\Instrumentation", hook, OpenTelemetry_Instrumentation_hook, arginfo_OpenTelemetry_Instrumentation_hook)
+ZEND_NS_FE("OpenTelemetry\\Instrumentation", hook, arginfo_OpenTelemetry_Instrumentation_hook)
```

Also note that the `#[Attribute]` attribute is not added to the classes in the stub file, as that could
not be handled by `gen_stub.php`. Constructors are also not added due to not building cleanly, but might
be fixable (TODO).

You can regenerate the output by `php build/gen_stub.php`, or `make` (if the stub file has been changed).

`opentelemetry.ide.stub.php` represents what the stub wile (probably) should be, and this file can
be used in IDEs for type-hinting.

# Further reading

* https://www.phpinternalsbook.com/php7/build_system/building_extensions.html
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PHP_VERSION ?= 8.3.0
PHP_VERSION ?= 8.3.10
DISTRO ?= debian

.DEFAULT_GOAL : help
Expand All @@ -21,4 +21,6 @@ build: ## Build extension
docker compose run $(DISTRO) ./build.sh
test: ## Run tests
docker compose run $(DISTRO) make test
remove-orphans: ## Remove orphaned containers
docker compose down --remove-orphans
.PHONY: clean build test git-clean
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ Issues have been disabled for this repo in order to help maintain consistency be
This is a PHP extension for OpenTelemetry, to enable auto-instrumentation.
It is based on [zend_observer](https://www.datadoghq.com/blog/engineering/php-8-observability-baked-right-in/) and requires php8+

The extension allows creating `pre` and `post` hook functions to arbitrary PHP functions and methods, which allows those methods to be wrapped with telemetry.
The extension allows:

- creating `pre` and `post` hook functions to arbitrary PHP functions and methods, which allows those methods to be wrapped with telemetry
- adding attributes to functions and methods to enable observers at runtime

In PHP 8.2+, internal/built-in PHP functions can also be observed.

Expand Down Expand Up @@ -241,5 +244,86 @@ string(3) "new"
string(8) "original"
```

## Attribute-based hooking

By applying attributes to source code, the OpenTelemetry extension can add hooks at runtime.

By default, the following pre/post hooks will be invoked: `OpenTelemetry\API\Instrumentation\Handler::pre` and `::post`.

## Restrictions

Attribute-based hooks can only be applied to a function/method that does not already have
hooks applied.
Only one hook can be applied to a function/method, including via interfaces.

Since the attributes are evaluated at runtime, the extension checks whether a hook already
exists to decide whether it should apply a new runtime hook.

### Configuration

This feature can be configured via `.ini` by modifying the following entries:

- `opentelemetry.attr_hooks_enabled` - boolean, default On
- `opentelemetry.attr_pre_handler_function` - FQN of pre method/function
- `opentelemetry.attr_post_handler_function` - FQN of post method/function

## `WithSpan` attribute

This attribute can be applied to a function or class method.
You can also provide optional parameters to the attribute, which control:
- span name
- span kind
- attributes

```php
use OpenTelemetry\Instrumentation\WithSpan

class MyClass
{
#[WithSpan]
public function trace_me(): void
{
/* ... */
}

#[WithSpan('custom_span_name', SpanKind::KIND_INTERNAL, ['my-attr' => 'value'])]
public function trace_me_with_customization(): void
{
/* ... */
}
}

#[WithSpan]
function my_function(): void
{
/* ... */
}
```

## `SpanAttribute` attribute

This attribute should be used in conjunction with `WithSpan`. It is applied to function/method
parameters, and causes those parameters and values to be passed through to the `pre` hook function
where they can be added as trace attributes.
There is one optional parameter, which controls the attribute key. If not set, the parameter name
is used.

```php
use OpenTelemetry\Instrumentation\WithSpan
use OpenTelemetry\Instrumentation\SpanAttribute

class MyClass
{
#[WithSpan]
public function add_user(
#[SpanAttribute] string $username,
string $password,
#[SpanAttribute('a_better_attribute_name')] string $foo_bar_baz,
): void
{
/* ... */
}
```

## Contributing
See [DEVELOPMENT.md](DEVELOPMENT.md) and https://github.com/open-telemetry/opentelemetry-php/blob/main/CONTRIBUTING.md
5 changes: 2 additions & 3 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
version: '3.7'
services:
debian:
build:
context: docker
dockerfile: Dockerfile.debian
args:
PHP_VERSION: ${PHP_VERSION:-8.3.0}
PHP_VERSION: ${PHP_VERSION:-8.3.10}
volumes:
- ./ext:/usr/src/myapp
environment:
Expand All @@ -15,7 +14,7 @@ services:
context: docker
dockerfile: Dockerfile.alpine
args:
PHP_VERSION: ${PHP_VERSION:-8.3.0}
PHP_VERSION: ${PHP_VERSION:-8.3.10}
volumes:
- ./ext:/usr/src/myapp
environment:
Expand Down
23 changes: 23 additions & 0 deletions ext/opentelemetry.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "otel_observer.h"
#include "stdlib.h"
#include "string.h"
#include "zend_attributes.h"
#include "zend_closures.h"

static int check_conflict(HashTable *registry, const char *extension_name) {
Expand Down Expand Up @@ -86,6 +87,18 @@ STD_PHP_INI_ENTRY_EX("opentelemetry.allow_stack_extension", "Off", PHP_INI_ALL,
OnUpdateBool, allow_stack_extension,
zend_opentelemetry_globals, opentelemetry_globals,
zend_ini_boolean_displayer_cb)
STD_PHP_INI_ENTRY_EX("opentelemetry.attr_hooks_enabled", "Off", PHP_INI_ALL,
OnUpdateBool, attr_hooks_enabled,
zend_opentelemetry_globals, opentelemetry_globals,
zend_ini_boolean_displayer_cb)
STD_PHP_INI_ENTRY("opentelemetry.attr_pre_handler_function",
"Opentelemetry\\API\\Instrumentation\\WithSpanHandler::pre",
PHP_INI_ALL, OnUpdateString, pre_handler_function_fqn,
zend_opentelemetry_globals, opentelemetry_globals)
STD_PHP_INI_ENTRY("opentelemetry.attr_post_handler_function",
"Opentelemetry\\API\\Instrumentation\\WithSpanHandler::post",
PHP_INI_ALL, OnUpdateString, post_handler_function_fqn,
zend_opentelemetry_globals, opentelemetry_globals)
PHP_INI_END()

PHP_FUNCTION(hook) {
Expand Down Expand Up @@ -128,6 +141,16 @@ PHP_MINIT_FUNCTION(opentelemetry) {

if (!OTEL_G(disabled)) {
opentelemetry_observer_init(INIT_FUNC_ARGS_PASSTHRU);
zend_class_entry *ce_with_span =
register_class_OpenTelemetry_Instrumentation_WithSpan();
zend_class_entry *ce_span_attribute =
register_class_OpenTelemetry_Instrumentation_SpanAttribute();
// todo: gen_stubs.php should do this
zend_internal_attribute_register(ce_with_span,
ZEND_ATTRIBUTE_TARGET_METHOD |
ZEND_ATTRIBUTE_TARGET_FUNCTION);
zend_internal_attribute_register(ce_span_attribute,
ZEND_ATTRIBUTE_TARGET_PARAMETER);
}

return SUCCESS;
Expand Down
39 changes: 39 additions & 0 deletions ext/opentelemetry.ide.stub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace OpenTelemetry\Instrumentation;

/**
* @param string|null $class The (optional) hooked function's class. Null for a global/built-in function.
* @param string $function The hooked function's name.
* @param \Closure|null $pre function($class, array $params, string $class, string $function, ?string $filename, ?int $lineno, ?array $span_args, ?array $span_attributes): $params
* You may optionally return modified parameters.
* @param \Closure|null $post function($class, array $params, $returnValue, ?Throwable $exception): $returnValue
* You may optionally return modified return value.
* @return bool Whether the observer was successfully added
*
* @see https://github.com/open-telemetry/opentelemetry-php-instrumentation
*/
function hook(
string|null $class,
string $function,
?\Closure $pre = null,
?\Closure $post = null,
): bool {}

#[\Attribute(\Attribute::TARGET_FUNCTION|\Attribute::TARGET_METHOD)]
final class WithSpan
{
public function __construct(
?string $span_name = null,
?int $span_type = null,
array $attributes = [],
){}
}

#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class SpanAttribute
{
public function __construct(
public ?string $name = null,
){}
}
10 changes: 9 additions & 1 deletion ext/opentelemetry.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* @param string|null $class The (optional) hooked function's class. Null for a global/built-in function.
* @param string $function The hooked function's name.
* @param \Closure|null $pre function($class, array $params, string $class, string $function, ?string $filename, ?int $lineno): $params
* @param \Closure|null $pre function($class, array $params, string $class, string $function, ?string $filename, ?int $lineno, ?array $span_args, ?array $span_attributes): $params
* You may optionally return modified parameters.
* @param \Closure|null $post function($class, array $params, $returnValue, ?Throwable $exception): $returnValue
* You may optionally return modified return value.
Expand All @@ -21,3 +21,11 @@ function hook(
?\Closure $pre = null,
?\Closure $post = null,
): bool {}

final class WithSpan
{
}

final class SpanAttribute
{
}
33 changes: 32 additions & 1 deletion ext/opentelemetry_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 98cf39fd2bbea1a60b5978923e7d83e3954afb6e */
* Stub hash: f4707b118ba43575214908e608c9c465bdc1edc2 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
arginfo_OpenTelemetry_Instrumentation_hook, 0, 2, _IS_BOOL, 0)
Expand All @@ -14,3 +14,34 @@ ZEND_FUNCTION(hook);
static const zend_function_entry ext_functions[] = {
ZEND_NS_FE("OpenTelemetry\\Instrumentation", hook,
arginfo_OpenTelemetry_Instrumentation_hook) ZEND_FE_END};

static const zend_function_entry
class_OpenTelemetry_Instrumentation_WithSpan_methods[] = {ZEND_FE_END};

static const zend_function_entry
class_OpenTelemetry_Instrumentation_SpanAttribute_methods[] = {ZEND_FE_END};

static zend_class_entry *
register_class_OpenTelemetry_Instrumentation_WithSpan(void) {
zend_class_entry ce, *class_entry;

INIT_NS_CLASS_ENTRY(ce, "OpenTelemetry\\Instrumentation", "WithSpan",
class_OpenTelemetry_Instrumentation_WithSpan_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_FINAL;

return class_entry;
}

static zend_class_entry *
register_class_OpenTelemetry_Instrumentation_SpanAttribute(void) {
zend_class_entry ce, *class_entry;

INIT_NS_CLASS_ENTRY(
ce, "OpenTelemetry\\Instrumentation", "SpanAttribute",
class_OpenTelemetry_Instrumentation_SpanAttribute_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
class_entry->ce_flags |= ZEND_ACC_FINAL;

return class_entry;
}
Loading