Skip to content

Commit

Permalink
observe methods from attribute (#146)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brettmc authored Aug 29, 2024
1 parent f776242 commit a9b57b6
Show file tree
Hide file tree
Showing 31 changed files with 1,105 additions and 38 deletions.
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,4 @@ A more advanced example: https://github.com/open-telemetry/opentelemetry-php-con

# Further reading

* https://www.phpinternalsbook.com/php7/build_system/building_extensions.html
* 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
90 changes: 89 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,90 @@ string(3) "new"
string(8) "original"
```

## Attribute-based hooking

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

Default pre and post hook methods are provided by the OpenTelemetry API: `OpenTelemetry\API\Instrumentation\Handler::pre`
and `::post`.

This feature is disabled by default, but can be enabled by setting `opentelemetry.attr_hooks_enabled = On` in php.ini

## 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 Off
- `opentelemetry.attr_pre_handler_function` - FQN of pre method/function
- `opentelemetry.attr_post_handler_function` - FQN of post method/function

## `OpenTelemetry\API\Instrumentation\WithSpan` attribute

This attribute is provided by the OpenTelemetry API 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\API\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
{
/* ... */
}
```

## `OpenTelemetry\API\Instrumentation\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\API\Instrumentation\WithSpan
use OpenTelemetry\API\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
1 change: 1 addition & 0 deletions ext/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ run-tests.php
tests/**/*.diff
tests/**/*.out
tests/**/*.php
!tests/mocks/*.php
tests/**/*.exp
tests/**/*.log
tests/**/*.sh
Expand Down
15 changes: 14 additions & 1 deletion 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,9 +87,21 @@ 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) {
PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) {
zend_string *class_name;
zend_string *function_name;
zval *pre = NULL;
Expand Down
4 changes: 2 additions & 2 deletions 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 @@ -20,4 +20,4 @@ function hook(
string $function,
?\Closure $pre = null,
?\Closure $post = null,
): bool {}
): bool {}
10 changes: 5 additions & 5 deletions 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: aa29142596154400c530f1194a7f29fbb9036929 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
arginfo_OpenTelemetry_Instrumentation_hook, 0, 2, _IS_BOOL, 0)
Expand All @@ -9,8 +9,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, post, Closure, 1, "null")
ZEND_END_ARG_INFO()

ZEND_FUNCTION(hook);
ZEND_FUNCTION(OpenTelemetry_Instrumentation_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 ext_functions[] = {ZEND_NS_FALIAS(
"OpenTelemetry\\Instrumentation", hook, OpenTelemetry_Instrumentation_hook,
arginfo_OpenTelemetry_Instrumentation_hook) ZEND_FE_END};
Loading

0 comments on commit a9b57b6

Please sign in to comment.