Skip to content

Commit a9b57b6

Browse files
authored
observe methods from attribute (#146)
Add the capability to observe/hook methods and functions via WithSpan and SpanAttribute attributes. This capability is disabled by default (it has some runtime overhead), and can be enabled via php.ini WithSpan is a signal that a function or method should be auto-instrumented. SpanAttribute enables arguments to be passed through to pre hook as attributes to be added to a span. The attributes are provided by the API, as OpenTelemetry\API\Instumentation\WithSpan and OpenTelemetry\API\Instrumentation\SpanAttribute Two extra parameters are passed to pre hooks: 1. span name + span kind (from WithSpan's arguments, if provided) 2. attributes (from WithSpan's 3rd argument, and from SpanAttribute) WithSpan and SpanAttribute can be applied to a methods, functions, or interface methods. The default pre/post hook callbacks for WithSpan are provided by the API, as OpenTelemetry\API\Instrumentation\WithSpanHandler. They can be changed via php.ini There is a restriction that WithSpan hooks can only be added to functions/methods that are not already hooked by something else (because the hooks are added at runtime: when a method is executed and has no hooks, we then check for attribute and add hooks.
1 parent f776242 commit a9b57b6

31 files changed

+1105
-38
lines changed

DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,4 @@ A more advanced example: https://github.com/open-telemetry/opentelemetry-php-con
180180

181181
# Further reading
182182

183-
* https://www.phpinternalsbook.com/php7/build_system/building_extensions.html
183+
* https://www.phpinternalsbook.com/php7/build_system/building_extensions.html

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
PHP_VERSION ?= 8.3.0
1+
PHP_VERSION ?= 8.3.10
22
DISTRO ?= debian
33

44
.DEFAULT_GOAL : help
@@ -21,4 +21,6 @@ build: ## Build extension
2121
docker compose run $(DISTRO) ./build.sh
2222
test: ## Run tests
2323
docker compose run $(DISTRO) make test
24+
remove-orphans: ## Remove orphaned containers
25+
docker compose down --remove-orphans
2426
.PHONY: clean build test git-clean

README.md

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ Issues have been disabled for this repo in order to help maintain consistency be
1313
This is a PHP extension for OpenTelemetry, to enable auto-instrumentation.
1414
It is based on [zend_observer](https://www.datadoghq.com/blog/engineering/php-8-observability-baked-right-in/) and requires php8+
1515

16-
The extension allows creating `pre` and `post` hook functions to arbitrary PHP functions and methods, which allows those methods to be wrapped with telemetry.
16+
The extension allows:
17+
18+
- creating `pre` and `post` hook functions to arbitrary PHP functions and methods, which allows those methods to be wrapped with telemetry
19+
- adding attributes to functions and methods to enable observers at runtime
1720

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

@@ -241,5 +244,90 @@ string(3) "new"
241244
string(8) "original"
242245
```
243246

247+
## Attribute-based hooking
248+
249+
By applying attributes to source code, the OpenTelemetry extension can add hooks at runtime.
250+
251+
Default pre and post hook methods are provided by the OpenTelemetry API: `OpenTelemetry\API\Instrumentation\Handler::pre`
252+
and `::post`.
253+
254+
This feature is disabled by default, but can be enabled by setting `opentelemetry.attr_hooks_enabled = On` in php.ini
255+
256+
## Restrictions
257+
258+
Attribute-based hooks can only be applied to a function/method that does not already have
259+
hooks applied.
260+
Only one hook can be applied to a function/method, including via interfaces.
261+
262+
Since the attributes are evaluated at runtime, the extension checks whether a hook already
263+
exists to decide whether it should apply a new runtime hook.
264+
265+
## Configuration
266+
267+
This feature can be configured via `.ini` by modifying the following entries:
268+
269+
- `opentelemetry.attr_hooks_enabled` - boolean, default Off
270+
- `opentelemetry.attr_pre_handler_function` - FQN of pre method/function
271+
- `opentelemetry.attr_post_handler_function` - FQN of post method/function
272+
273+
## `OpenTelemetry\API\Instrumentation\WithSpan` attribute
274+
275+
This attribute is provided by the OpenTelemetry API can be applied to a function or class method.
276+
277+
You can also provide optional parameters to the attribute, which control:
278+
- span name
279+
- span kind
280+
- attributes
281+
282+
```php
283+
use OpenTelemetry\API\Instrumentation\WithSpan
284+
285+
class MyClass
286+
{
287+
#[WithSpan]
288+
public function trace_me(): void
289+
{
290+
/* ... */
291+
}
292+
293+
#[WithSpan('custom_span_name', SpanKind::KIND_INTERNAL, ['my-attr' => 'value'])]
294+
public function trace_me_with_customization(): void
295+
{
296+
/* ... */
297+
}
298+
}
299+
300+
#[WithSpan]
301+
function my_function(): void
302+
{
303+
/* ... */
304+
}
305+
```
306+
307+
## `OpenTelemetry\API\Instrumentation\SpanAttribute` attribute
308+
309+
This attribute should be used in conjunction with `WithSpan`. It is applied to function/method
310+
parameters, and causes those parameters and values to be passed through to the `pre` hook function
311+
where they can be added as trace attributes.
312+
There is one optional parameter, which controls the attribute key. If not set, the parameter name
313+
is used.
314+
315+
```php
316+
use OpenTelemetry\API\Instrumentation\WithSpan
317+
use OpenTelemetry\API\Instrumentation\SpanAttribute
318+
319+
class MyClass
320+
{
321+
#[WithSpan]
322+
public function add_user(
323+
#[SpanAttribute] string $username,
324+
string $password,
325+
#[SpanAttribute('a_better_attribute_name')] string $foo_bar_baz,
326+
): void
327+
{
328+
/* ... */
329+
}
330+
```
331+
244332
## Contributing
245333
See [DEVELOPMENT.md](DEVELOPMENT.md) and https://github.com/open-telemetry/opentelemetry-php/blob/main/CONTRIBUTING.md

docker-compose.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
version: '3.7'
21
services:
32
debian:
43
build:
54
context: docker
65
dockerfile: Dockerfile.debian
76
args:
8-
PHP_VERSION: ${PHP_VERSION:-8.3.0}
7+
PHP_VERSION: ${PHP_VERSION:-8.3.10}
98
volumes:
109
- ./ext:/usr/src/myapp
1110
environment:
@@ -15,7 +14,7 @@ services:
1514
context: docker
1615
dockerfile: Dockerfile.alpine
1716
args:
18-
PHP_VERSION: ${PHP_VERSION:-8.3.0}
17+
PHP_VERSION: ${PHP_VERSION:-8.3.10}
1918
volumes:
2019
- ./ext:/usr/src/myapp
2120
environment:

ext/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ run-tests.php
3737
tests/**/*.diff
3838
tests/**/*.out
3939
tests/**/*.php
40+
!tests/mocks/*.php
4041
tests/**/*.exp
4142
tests/**/*.log
4243
tests/**/*.sh

ext/opentelemetry.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "otel_observer.h"
1111
#include "stdlib.h"
1212
#include "string.h"
13+
#include "zend_attributes.h"
1314
#include "zend_closures.h"
1415

1516
static int check_conflict(HashTable *registry, const char *extension_name) {
@@ -86,9 +87,21 @@ STD_PHP_INI_ENTRY_EX("opentelemetry.allow_stack_extension", "Off", PHP_INI_ALL,
8687
OnUpdateBool, allow_stack_extension,
8788
zend_opentelemetry_globals, opentelemetry_globals,
8889
zend_ini_boolean_displayer_cb)
90+
STD_PHP_INI_ENTRY_EX("opentelemetry.attr_hooks_enabled", "Off", PHP_INI_ALL,
91+
OnUpdateBool, attr_hooks_enabled,
92+
zend_opentelemetry_globals, opentelemetry_globals,
93+
zend_ini_boolean_displayer_cb)
94+
STD_PHP_INI_ENTRY("opentelemetry.attr_pre_handler_function",
95+
"Opentelemetry\\API\\Instrumentation\\WithSpanHandler::pre",
96+
PHP_INI_ALL, OnUpdateString, pre_handler_function_fqn,
97+
zend_opentelemetry_globals, opentelemetry_globals)
98+
STD_PHP_INI_ENTRY("opentelemetry.attr_post_handler_function",
99+
"Opentelemetry\\API\\Instrumentation\\WithSpanHandler::post",
100+
PHP_INI_ALL, OnUpdateString, post_handler_function_fqn,
101+
zend_opentelemetry_globals, opentelemetry_globals)
89102
PHP_INI_END()
90103

91-
PHP_FUNCTION(hook) {
104+
PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) {
92105
zend_string *class_name;
93106
zend_string *function_name;
94107
zval *pre = NULL;

ext/opentelemetry.stub.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* @param string|null $class The (optional) hooked function's class. Null for a global/built-in function.
99
* @param string $function The hooked function's name.
10-
* @param \Closure|null $pre function($class, array $params, string $class, string $function, ?string $filename, ?int $lineno): $params
10+
* @param \Closure|null $pre function($class, array $params, string $class, string $function, ?string $filename, ?int $lineno, ?array $span_args, ?array $span_attributes): $params
1111
* You may optionally return modified parameters.
1212
* @param \Closure|null $post function($class, array $params, $returnValue, ?Throwable $exception): $returnValue
1313
* You may optionally return modified return value.
@@ -20,4 +20,4 @@ function hook(
2020
string $function,
2121
?\Closure $pre = null,
2222
?\Closure $post = null,
23-
): bool {}
23+
): bool {}

ext/opentelemetry_arginfo.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 98cf39fd2bbea1a60b5978923e7d83e3954afb6e */
2+
* Stub hash: aa29142596154400c530f1194a7f29fbb9036929 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
55
arginfo_OpenTelemetry_Instrumentation_hook, 0, 2, _IS_BOOL, 0)
@@ -9,8 +9,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
99
ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, post, Closure, 1, "null")
1010
ZEND_END_ARG_INFO()
1111

12-
ZEND_FUNCTION(hook);
12+
ZEND_FUNCTION(OpenTelemetry_Instrumentation_hook);
1313

14-
static const zend_function_entry ext_functions[] = {
15-
ZEND_NS_FE("OpenTelemetry\\Instrumentation", hook,
16-
arginfo_OpenTelemetry_Instrumentation_hook) ZEND_FE_END};
14+
static const zend_function_entry ext_functions[] = {ZEND_NS_FALIAS(
15+
"OpenTelemetry\\Instrumentation", hook, OpenTelemetry_Instrumentation_hook,
16+
arginfo_OpenTelemetry_Instrumentation_hook) ZEND_FE_END};

0 commit comments

Comments
 (0)