Slide 1

Slide 1 text

PHP 7.4でもOpenTelemetry ゼロコード計装がしたい! id:arthur-1 株式会社はてな 2026-03-21 PHPerKaigi 2026 [Day 0, Track C]

Slide 2

Slide 2 text

前置き 本セッションでは、タイトル通りすでにEoLを迎 えたPHPについての話題が出てきますが、それら の使用を奨励する意図はありません 2

Slide 3

Slide 3 text

Arthurと申します 株式会社はてな Mackerel開発チーム サブディレクター・テックリード 𝕏: @Arthur1__ 好きなPHPの関数:array_column() 次はPHPカンファレンス小田原2026で お会いしましょう(コアスタッフ) 3

Slide 4

Slide 4 text

まずはアンケート 4

Slide 5

Slide 5 text

オブザーバビリティ 知ってますか? 5

Slide 6

Slide 6 text

OpenTelemetry 知ってますか? 6

Slide 7

Slide 7 text

PHPのOTel計装 やったことありますか? 7

Slide 8

Slide 8 text

おしながき ● 導入:OpenTelemetry ● PHP 8.0から利用できるOTel公式のゼロコード計装 の仕組み ● PHP 7.4でどうゼロコード計装を実現するか 8

Slide 9

Slide 9 text

持ち帰ることができる知識 ● PHPのOpenTelemetryゼロコード計装を実現してい る仕組みがわかる ● PHPの拡張モジュールの実装のコードが少しだけ読め るようになる ● PHP 7.4でもOpenTelemetryゼロコード計装を頑張 りたい人への解決策が提示される 9

Slide 10

Slide 10 text

導入:OpenTelemetry 10

Slide 11

Slide 11 text

オブザーバビリティ Observability(可観測性) システムの出力から、その内部の状態を測定できる度合い 内部構造が既知でなくても良い・未知の未知への対処 11 🔥 🔥

Slide 12

Slide 12 text

アプリケーションにおけるO11y テレメトリー:システムから出力されるデータ メトリクス・トレース・ログ・プロファイルなど アプリケーションに計装してテレメトリーを生成・送出 12 🔥 🔥 ログ トレース メトリクス 計装されたシステム オブザーバビリティ バックエンド

Slide 13

Slide 13 text

OpenTelemetry OpenTelemetryは、メトリックやログなどのテレメトリー データの生成・投稿を、ベンダーの違いを超えて標準化する オブザーバビリティフレームワーク・ツールキット 13 https://opentelemetry.io/ https://www.oreilly.co.jp/books/9784814401024/ https://www.oreilly.co.jp/books/9784814401031/

Slide 14

Slide 14 text

OpenTelemetryによる標準化 14 OpenTelemetryの エージェント OpenTelemetryの プロトコル name: system.cpu.time sum: dataPoints: - asInt: 12345678 OpenTelemetryの データ形式 収集先のバックエンドの違いによらず 同じものが使える オブザーバビリティ バックエンド

Slide 15

Slide 15 text

OTelの計装って大変? 疑問や懸念 ● 既存のコードベースを大きく変えなければならない? ● 運用だけ任されていて、コードを書ける人がいない…… ● 面倒抜きで手っ取り早く始めたい!! 15

Slide 16

Slide 16 text

計装には大きく3パターンある ● コードベース計装 公式のAPI・SDKをインポートして、マニュアルでテレメトリー の生成・エクスポートを実装する ● ライブラリ計装 既存のライブラリに計装ライブラリを外付けする ● ゼロコード計装(自動計装) ソースコードを編集しなくても計装できる 16

Slide 17

Slide 17 text

ゼロコード計装 コードを編集しなくても、OTel SDKや計装ライブラリをア プリケーションに自動で注入 アプリケーションのエッジ(リクエスト・レスポンス・デー タベースの呼び出しなど)を中心に観測可能にできる 以下のケースで向いている: ● 最初のステップ ● アプリケーションコードを修正できない 17

Slide 18

Slide 18 text

ゼロコード計装に対応している言語 ゼロコード計装は全ての言語に対して提供されているわ けではない 2026年3月現在、以下のものが公式で用意されている: 18 ● .NET ● Go ● Java ● JavaScript ● PHP ● Python

Slide 19

Slide 19 text

PHPにおけるOTelゼロコード計装 ドキュメント: https://opentelemetry.io/ja/docs/zero-code/php/ 主なステップは3つ ● OpenTelemetry拡張モジュールをインストール ● OpenTelemetry SDK・Exporter・フレームワークやライ ブラリに合わせた計装ライブラリをインストール ● エクスポートするための設定を記述(送り先など) 19

Slide 20

Slide 20 text

PHPゼロコード計装導入デモ PHP 8.5・Laravel 13のアプリケーションに、実 際にゼロコード計装を入れるデモをお見せします 20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

PHPの ゼロコード計装の仕組み 22

Slide 23

Slide 23 text

ゼロコード計装導入でやったこと ① PECL (The PHP Extension Community Library) リポジトリに存在するOpenTelemetry拡張をインス トールしていた $ pecl install opentelemetry Dockerの場合は: RUN install-php-extensions opentelemetry 23

Slide 24

Slide 24 text

PHP拡張モジュール PHP拡張モジュールを使用すると、PHPのインタ プリタであるZend Engineを拡張できる: ● 新しい関数・クラスを追加したり ● PHP単体では触れられない低レイヤに触れられたり ● 関数呼び出しの前後にフックを仕掛けられたり 24

Slide 25

Slide 25 text

フックとゼロコード計装の関係 例)HTTPリクエストにかかった時間が知りたい HTTPリクエストに相当する関数の開始時: 🕛 現在時刻を記録 HTTPリクエストに相当する関数の終了時: 🕑 すでに記録された時刻と現在時刻の差分を取る 25

Slide 26

Slide 26 text

拡張モジュールはC言語で書く Zend EngineはC言語で作られており、Zend Engine が公開するAPIもまたC言語のもの そこで、拡張モジュールもC言語で書くことになる 予告:この後C言語のコードが出てきます 26

Slide 27

Slide 27 text

PHP 7以前でフックさせる方法 拡張モジュールロード時に、zend_execute_ex関数ポ インタを差し替えることで、PHPで作られた関数の呼び 出しの前後にフックを仕込める PHPの組み込み関数やPHP拡張で追加された関数の場合 は、同様にzend_execute_internal関数ポインタを差し 替えればよい 27

Slide 28

Slide 28 text

28 static void (*original_execute_ex)(zend_execute_data *execute_data) = NULL; static void my_excute_ex(zend_execute_data *execute_data) { // ここで前処理 original_execute_ex(execute_data); // ここで後処理 } PHP_MINIT_FUNCTION(my_ext) { original_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS; }

Slide 29

Slide 29 text

29 static void (*original_execute_ex)(zend_execute_data *execute_data) = NULL; static void my_excute_ex(zend_execute_data *execute_data) { // ここで前処理 original_execute_ex(execute_data); // ここで後処理 } PHP_MINIT_FUNCTION(my_ext) { original_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS; } 拡張モジュールロード時に PHP_MINIT_FUNCTION が呼ばれるイメージ(マクロ)

Slide 30

Slide 30 text

30 static void (*original_execute_ex)(zend_execute_data *execute_data) = NULL; static void my_excute_ex(zend_execute_data *execute_data) { // ここで前処理 original_execute_ex(execute_data); // ここで後処理 } PHP_MINIT_FUNCTION(my_ext) { original_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS; } 元々のzend_execute_ex 関数ポインタを退避しておく

Slide 31

Slide 31 text

31 static void (*original_execute_ex)(zend_execute_data *execute_data) = NULL; static void my_excute_ex(zend_execute_data *execute_data) { // ここで前処理 original_execute_ex(execute_data); // ここで後処理 } PHP_MINIT_FUNCTION(my_ext) { original_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS; } 元々のzend_execute_ex をwrapした関数を定義 上で定義した関数ポインタで zend_execute_exを上書き

Slide 32

Slide 32 text

zend_execute_exの問題点 ● 各関数呼び出しがサイズ制限されたスタックに乗るの で、スタックオーバーフローによりPHPがクラッシュす ることがある ● 全ての関数呼び出しに対してフックさせることになるの で、処理時間のオーバーヘッドが大きい ● PHP 8の新しいJITの仕組みにより、このフックが動作し ないことがある 32

Slide 33

Slide 33 text

PHP 8: Observer API 先ほどの問題点を解決するため、PHP 8からZend EngineにObserver APIが導入された ● スタック制限なし ● PHP 8+のJIT下でもちゃんと動く ● 特定の関数のみにフックを仕掛ける仕組みがある など、これまでの手法の多くの問題を解決している 33

Slide 34

Slide 34 text

34 static void my_observer_begin(zend_execute_data *execute_data) { // ここで前処理 } static void my_observer_end(zend_execute_data *execute_data, zval *return_value) { // ここで後処理 } static zend_observer_fcall_handlers my_observer_init(zend_execute_data *execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; }

Slide 35

Slide 35 text

35 static void my_observer_begin(zend_execute_data *execute_data) { // ここで前処理 } static void my_observer_end(zend_execute_data *execute_data, zval *return_value) { // ここで後処理 } static zend_observer_fcall_handlers my_observer_init(zend_execute_data *execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; }

Slide 36

Slide 36 text

36 PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; }

Slide 37

Slide 37 text

37 PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; } 拡張モジュールロード時に PHP_MINIT_FUNCTION が呼ばれるイメージ(マクロ)

Slide 38

Slide 38 text

38 PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; } 関数オブザーバーを登録

Slide 39

Slide 39 text

39 static void my_observer_begin(zend_execute_data *execute_data) { // ここで前処理 } static void my_observer_end(zend_execute_data *execute_data, zval *return_value) { // ここで後処理 } static zend_observer_fcall_handlers my_observer_init(zend_execute_data *execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; }

Slide 40

Slide 40 text

40 static zend_observer_fcall_handlers my_observer_init( zend_execute_data *execute_data ) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; }

Slide 41

Slide 41 text

41 static zend_observer_fcall_handlers my_observer_init( zend_execute_data *execute_data ) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } 登録した関数オブザーバーの定義 (zend_observer_fcall_registerの引数)

Slide 42

Slide 42 text

42 static zend_observer_fcall_handlers my_observer_init( zend_execute_data *execute_data ) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } ユーザーランド関数ならフックハンドラを追加 (zend_execute_exに合わせた条件にした)

Slide 43

Slide 43 text

43 static zend_observer_fcall_handlers my_observer_init(zend_execute_data *execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } ユーザーランド関数でなければハンドラはなし

Slide 44

Slide 44 text

44 static void my_observer_begin(zend_execute_data *execute_data) { // ここで前処理 } static void my_observer_end(zend_execute_data *execute_data, zval *return_value) { // ここで後処理 } static zend_observer_fcall_handlers my_observer_init(zend_execute_data *execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; if (execute_data->func->type == ZEND_USER_FUNCTION) { handlers.begin = my_observer_begin; handlers.end = my_observer_end; } return handlers; } PHP_MINIT_FUNCTION(my_ext) { zend_observer_fcall_register(my_observer_init); return SUCCESS; }

Slide 45

Slide 45 text

45 static void my_observer_begin( zend_execute_data *execute_data ) { // ここで前処理 } static void my_observer_end( zend_execute_data *execute_data, zval *return_value ) { // ここで後処理 }

Slide 46

Slide 46 text

46 static void my_observer_begin( zend_execute_data *execute_data ) { // ここで前処理 } static void my_observer_end( zend_execute_data *execute_data, zval *return_value ) { // ここで後処理 } handlers.beginに設定した前処理のハンドラ zend_execute_exと同じシグネチャなので、 同等のことができる

Slide 47

Slide 47 text

47 static void my_observer_begin( zend_execute_data *execute_data ) { // ここで前処理 } static void my_observer_end( zend_execute_data *execute_data, zval *return_value ) { // ここで後処理 } handlers.endに設定した後処理のハンドラ zend_execute_exと同じ引数 +関数の返却値が受け取れる

Slide 48

Slide 48 text

【補足】Observer APIの進化 PHP 8でObserver APIが導入された当初は、ユーザー ランドの関数だけにしかフックできなかった PHP 8.2からは内部関数にも対応 > Registered zend_observer_fcall_init handlers are now also called for internal functions. https://github.com/php/php-src/blob/php-8.2.0/UPGRADING.INTE RNALS#L54 48

Slide 49

Slide 49 text

PHPのOTelゼロコード計装は8.0から https://opentelemetry.io/ja/d ocs/zero-code/php/ には、PHP のゼロコード計装はPHP 8.0以降 でなければいけないと書いてある その理由の1つが、ゼロコード計 装の仕組みがObserver APIに依 存していること 49

Slide 50

Slide 50 text

OpenTelemetry\Instrumentation\hook OpenTelemetry拡張は、任意の関数・メソッドにフックを仕掛 けられる関数 OpenTelemetry\Instrumentation\hook を提 供している。この関数により、Observer APIでフックさせたい 関数を拡張に登録する 50 OpenTelemetry\Instrumentation\hook( DemoClass::class, 'run', pre: static function (DemoClass $demo, array $params, string $class, string $function, ?string $filename, ?int $lineno) { … }, post: static function (DemoClass #demo, array $params, $returnValue, ?Throwable $exception) { … }, )

Slide 51

Slide 51 text

51 PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) { zend_string *class_name; zend_string *function_name; zval *pre = NULL; zval *post = NULL; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR_OR_NULL(class_name) Z_PARAM_STR(function_name) Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(add_observer(class_name, function_name, pre, post)); } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L107-L122

Slide 52

Slide 52 text

52 PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) { zend_string *class_name; zend_string *function_name; zval *pre = NULL; zval *post = NULL; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR_OR_NULL(class_name) Z_PARAM_STR(function_name) Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(add_observer(class_name, function_name, pre, post)); } OpenTelemetry_Intrumentation_hook という名前の関数を定義 https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L107-L122

Slide 53

Slide 53 text

53 PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) { zend_string *class_name; zend_string *function_name; zval *pre = NULL; zval *post = NULL; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR_OR_NULL(class_name) Z_PARAM_STR(function_name) Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(add_observer(class_name, function_name, pre, post)); } hook()の引数定義とparse https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L107-L122

Slide 54

Slide 54 text

54 PHP_FUNCTION(OpenTelemetry_Instrumentation_hook) { zend_string *class_name; zend_string *function_name; zval *pre = NULL; zval *post = NULL; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR_OR_NULL(class_name) Z_PARAM_STR(function_name) Z_PARAM_OPTIONAL Z_PARAM_OBJECT_OF_CLASS_OR_NULL(pre, zend_ce_closure) Z_PARAM_OBJECT_OF_CLASS_OR_NULL(post, zend_ce_closure) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(add_observer(class_name, function_name, pre, post)); } add_observerが、ハッシュテーブルに フックの情報を保持しておく https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L107-L122

Slide 55

Slide 55 text

55 PHP_MINIT_FUNCTION(opentelemetry) { #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) ZEND_TSRMLS_CACHE_UPDATE(); #endif REGISTER_INI_ENTRIES(); check_conflicts(); if (!OTEL_G(disabled)) { opentelemetry_observer_init(INIT_FUNC_ARGS_PASSTHRU); } return SUCCESS; } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L140-L154

Slide 56

Slide 56 text

56 PHP_MINIT_FUNCTION(opentelemetry) { #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) ZEND_TSRMLS_CACHE_UPDATE(); #endif REGISTER_INI_ENTRIES(); check_conflicts(); if (!OTEL_G(disabled)) { opentelemetry_observer_init(INIT_FUNC_ARGS_PASSTHRU); } return SUCCESS; } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L140-L154 拡張モジュールロード時に PHP_MINIT_FUNCTION が呼ばれるイメージ(マクロ)

Slide 57

Slide 57 text

57 PHP_MINIT_FUNCTION(opentelemetry) { #if defined(ZTS) && defined(COMPILE_DL_OPENTELEMETRY) ZEND_TSRMLS_CACHE_UPDATE(); #endif REGISTER_INI_ENTRIES(); check_conflicts(); if (!OTEL_G(disabled)) { opentelemetry_observer_init(INIT_FUNC_ARGS_PASSTHRU); } return SUCCESS; } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/opentelemetry.c#L140-L154 オブザーバー初期化(フックの登録) をこの中でやっている

Slide 58

Slide 58 text

58 void opentelemetry_observer_init(INIT_FUNC_ARGS) { if (type != MODULE_TEMPORARY) { zend_observer_fcall_register(observer_fcall_init); op_array_extension = zend_get_op_array_extension_handle("opentelemetry"); #if PHP_VERSION_ID >= 80400 zend_get_internal_function_extension_handle("opentelemetry"); #endif } } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/otel_observer.c#L1183-L1192

Slide 59

Slide 59 text

59 void opentelemetry_observer_init(INIT_FUNC_ARGS) { if (type != MODULE_TEMPORARY) { zend_observer_fcall_register(observer_fcall_init); op_array_extension = zend_get_op_array_extension_handle("opentelemetry"); #if PHP_VERSION_ID >= 80400 zend_get_internal_function_extension_handle("opentelemetry"); #endif } } https://github.com/open-telemetry/opentelemetry-php-instrumentation/blob/ad826b91 686dfd72fa01e2b3e360d563ab3b6343/ext/otel_observer.c#L1183-L1192 Observer APIの関数フックを登録する関数 observer_fcall_init内で、 OpenTelemetry\Intrumentation\hookにより 記録したフック情報をObserver APIの形式に変換

Slide 60

Slide 60 text

ゼロコード計装導入でやったこと ② composerを使って、OpenTelemetryのSDKや Exporterだけでなく、使用しているフレームワーク ・ライブラリに対応するゼロコード計装ライブラリ をインストールしていた $ composer install open-telemetry/opentelemetry-auto-laravel 60

Slide 61

Slide 61 text

ゼロコード計装ライブラリは何をする? ライブラリ・フレームワークに合わせて、テレメトリーを 計装する関数フックを登録する 例えば、Laravelであれば、Eloquent ORMの呼び出しに 対してトレースを生成するフックを仕掛けるよう、 OpenTelemetry\Instrumentation\hookを呼んでいる 61

Slide 62

Slide 62 text

62 hook( \Illuminate\Database\Eloquent\Builder::class, 'find', pre: function ($builder, array $params, string $class, string $function, ?string $filename, ?int $lineno) { $model = $builder->getModel(); $builder = $this->instrumentation->tracer() ->spanBuilder($model::class . '::find') ->setSpanKind(SpanKind::KIND_INTERNAL) ->setAttribute(TraceAttributes::CODE_FUNCTION_NAME, sprintf('%s::%s', $class, $function)) …; $parent = Context::getCurrent(); $span = $builder->startSpan(); Context::storage()->attach($span->storeInContext($parent)); return $params; }, post: function ($builder, array $params, $result, ?Throwable $exception) { $this->endSpan($exception); } ) https://github.com/open-telemetry/opentelemetry-php-contrib/blob/2b6a02f67d85f7f9 4ebc285d2c08c3523d26e093/src/Instrumentation/Laravel/src/Hooks/Illuminate/Databas e/Eloquent/Model.php#L36-L60

Slide 63

Slide 63 text

63 hook( \Illuminate\Database\Eloquent\Builder::class, 'find', pre: function ($builder, array $params, string $class, string $function, ?string $filename, ?int $lineno) { // トレーススパンの開始 }, post: function ($builder, array $params, $result, ?Throwable $exception) { // トレーススパンの終了 } ) https://github.com/open-telemetry/opentelemetry-php-contrib/blob/2b6a02f67d85f7f9 4ebc285d2c08c3523d26e093/src/Instrumentation/Laravel/src/Hooks/Illuminate/Databas e/Eloquent/Model.php#L36-L60

Slide 64

Slide 64 text

PHPゼロコード計装の仕組みまとめ ● OpenTelemetry拡張モジュールは、任意の関数・メソッ ドに対してフックを仕掛けられる機能を提供する ○ この仕組みの裏側には、PHP 8で導入されたZend Engineの Observer APIがあり、これまでのハックで起こる問題を解 消している ● ユーザーはフレームワーク・ライブラリごと用意された自 動計装ライブラリをインストールするだけで、テレメト リーを計装するフックをいい感じに登録してもらえる 64

Slide 65

Slide 65 text

PHP 7.4でどう ゼロコード計装を 実現するか 65

Slide 66

Slide 66 text

PHP 7系の現状 PHP 7の最後のバージョンである7.4が2022年11月で EoL。現在は、脆弱性があってもセキュリティパッチが提 供されない状態 とはいえ、W3Techsによると、2026年3月現在でPHP7 系は33.6%のシェアがある 66 https://w3techs.com/technologies/details/pl-php

Slide 67

Slide 67 text

PHP 7でもゼロコード計装したい! レガシーなPHPアプリケーションではコードベースを弄る のは相対的に大変 オブザーバビリティを獲得する手段として、ゼロコード計 装が選べるなら選びたいはず 67

Slide 68

Slide 68 text

OBIでもゼロコード計装できる! 選択肢の1つとしてOpenTelemetry eBPF計装(OBI)がある eBPFという、Linuxカーネルの挙動を動的に書き換えられる仕 組みを応用し、実行しているプロセスや通信に割り込んでゼロ コード計装する https://opentelemetry.io/ja/docs/zero-code/obi/ 68

Slide 69

Slide 69 text

OBIは銀の弾丸ではない ● PHPの通常のゼロコード計装と比較して、計装できる対象や情報 量がより限定的になる ● Linux OSでない環境(BSD、Windowsなど)では使用できない ● AWS FargateやGoogle Cloud Runなどのマネージドなコンテナ コンピューティング環境では使用できない ● トレースが繋がらず断片的になることがある 69

Slide 70

Slide 70 text

そこをなんとか そもそも、オブザーバビリティベンダーが提供する固有 のツールではPHP 7でもゼロコード計装できていること が多い なんとかOpenTelemetryでもPHP 7.4のアプリケーショ ンのゼロコード計装ができないだろうか 70

Slide 71

Slide 71 text

自作しよう 71

Slide 72

Slide 72 text

PHP 7.4でのゼロコード計装作戦 ● ゼロコード計装ライブラリはrequire php ^8.0 ○ よって、ゼロコード計装ライブラリは自分で作る必要が ある ● フック登録にあたって、PHP 8+のObserver APIは使えな い ○ よって、OpenTelemetry拡張は自分で作る必要がある ○ zend_execute_exの仕組みで同じようなフック機構は実 現できるはず 72

Slide 73

Slide 73 text

PHP 7.4でのゼロコード計装作戦 ● OpenTelemetry SDKはrequire php ^8.1 ○ ただし、過去のバージョン(v1.0.8)であればPHP 7.4 にも対応している。今回はこれを使えば良さそう ○ RectorというツールでPHPのダウングレードができるの で、最新のSDKのコードを変換するという手もある(そ れだけで済むならそうしたかった) 73

Slide 74

Slide 74 text

できたもの https://github.com/Arthur1/opentelemetry-php74-in strumentation/ 74

Slide 75

Slide 75 text

PHPゼロコード計装の導入デモ2 PHP 7.4・Laravel 8のアプリケーションに、今 回作ったゼロコード計装を導入するデモをお見 せします 完成系のコードは↓ https://github.com/Arthur1/opentelemetry-php74-instr umentation/tree/main/examples/laravel8/src 75

Slide 76

Slide 76 text

やることは3ステップ ● 今回作ったPHP拡張を有効化 ● open-telemetry/exporter-otlp(一般のもの)や Laravel向けの自動計装ライブラリ(今回作ったもの)を Composerでインストール ● Docker Composeで一緒にJaegerに対してトレースを送 るような環境変数を設定する 76

Slide 77

Slide 77 text

77

Slide 78

Slide 78 text

作ったもの ● PHP 7.4でも動くような、関数呼び出しにフッ クをつけるPHP拡張モジュール ● 上記拡張を用いた、Laravelの関数呼び出し起 因でOTelのトレースを自動計装するライブラリ Claude Opus 4.6に37ドル分仕事してもらった 78

Slide 79

Slide 79 text

PHP 7.4向けの関数フック拡張 本家のOpenTelemetry拡張モジュールと同じように関数や メソッドにフックを登録できる拡張を、Observer APIを使 用せず、zend_execute_exの仕組みで作った 可能な限り本家のhook()にシグネチャを合わせている が、名前付き引数はPHP 8〜なのでそこだけ断念 hook($className, $funcName, $preFunc, $postFunc); 79

Slide 80

Slide 80 text

Laravel向けの自動計装ライブラリ OpenTelemetry Laravel auto-instrumentationライ ブラリのコードを参考に、先ほど作った関数フック拡張 を用いる形で自動計装ライブラリを作成 https://github.com/open-telemetry/opentelemetry- php-contrib/tree/main/src/Instrumentation/Laravel 80

Slide 81

Slide 81 text

81 - use function OpenTelemetry\Instrumentation\hook; + use function OpenTelemetryPHP74\Instrumentation\hook; return hook( KernelContract::class, 'handle', - pre: function (...) { ⬛ }, + function (...) { ⬛ }, - post: function (...) { ● } + function (...) { ● } ) 書き換えは基本これだけ

Slide 82

Slide 82 text

SDK, API部分は古いものを参照 composer.jsonにこのように書 いておくと、APIはv1.0.3、 SDKはv1.0.8が勝手に入る (それぞれPHP 7.4に対応して いる中では最新のもの) 82 "require": { "php": "^7.4", "ext-opentelemetry_php74": "*", "open-telemetry/api": "^1.0", "open-telemetry/sdk": "^1.0", ... }

Slide 83

Slide 83 text

83 フックを自動でロードさせる仕組み composer.jsonのautoloadにエントリーポイントとなる ファイルを指定して登録しておく。指定したファイルは 自動でrequireされる このエントリーポイント経由で、拡張が提供する OpenTelemetryPHP74\Instrumentation\hookを呼 ぶ実装になっていれば良い

Slide 84

Slide 84 text

まとめ 84

Slide 85

Slide 85 text

PHPのゼロコード計装に必要なもの ● ゼロコード計装は、コードベースを書き換えなくてもテレ メトリーを送出可能にする手法 ● PHPのゼロコード計装に必要なものは以下の2つ: ○ 関数にフックを仕掛ける機能を持つPHP拡張 ○ フレームワークやライブラリの関数呼び出し時にトレー スを計装するようフックを仕掛けた自動計装ライブラリ 85

Slide 86

Slide 86 text

PHP 8+におけるゼロコード計装 ● PHP 8ではZend EngineにObserver APIが追加され、関 数の呼び出しに対して、より堅牢な仕組みで割り込めるよ うになった ● このおかげでPHP 8からは手軽に、そしてアプリケーショ ンの負荷やクラッシュリスクを抑えてOpenTelemetryの ゼロコード計装ができるようになった 86

Slide 87

Slide 87 text

PHP 7.4におけるゼロコード計装 ● OpenTelemetryが提供するゼロコード計装はObserver APIに依存しており、PHP 7以前では使えない ● 「必要なもの」が揃っていれば理屈上ゼロコード計装でき るので、以下の2つを自作してみた: ○ Observer APIを使わずに関数にフックを仕掛ける拡張 ○ 上記拡張を用いて、かつPHP 7.4に対応した、フレーム ワーク向け自動計装ライブラリ 87

Slide 88

Slide 88 text

最後に EoLの切れたPHPはアップデートしよう!! PHP 8系にバージョンアップできた暁には、 OpenTelemetryのゼロコード計装を利用して、手軽に オブザーバビリティを手に入れよう!! 88

Slide 89

Slide 89 text

参考文献 ● Sammy Powers and Levi Morrison, “PHP 8: Observability baked right in,” Datadog, Feb 2, 2021. [Online]. Available: https://www.datadoghq.com/blog/engineering/php-8-observability-baked -right-in/ ● Michihide Hotta, “PHP Extension 開発入門,” ネットワーク管理者(の卵)養成 講座, 2017. [Online]. Available: https://net-newbie.com/phpext/index.html ● Benjamin Eberlei, “Hooks provided by PHP,” PHP Internal Books, Jul 30, 2019. [Online]. Available: https://www.phpinternalsbook.com/php7/extensions_design/hooks.html ● OpenTelemetry Authors, “Documentation,” OpenTelemetry, 2019. [Online]. Avaibale: https://opentelemetry.io/docs/ 89

Slide 90

Slide 90 text

ご静聴いただき ありがとうございました 90