This is an automated email from the ASF dual-hosted git repository.

jmjoy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-php.git


The following commit(s) were added to refs/heads/master by this push:
     new c9c8af2  Optionally enable zend observer api for auto instrumentation. 
(#81)
c9c8af2 is described below

commit c9c8af2e007e47090a095504d5574112626aed10
Author: jmjoy <[email protected]>
AuthorDate: Tue Jul 4 19:06:43 2023 +0800

    Optionally enable zend observer api for auto instrumentation. (#81)
---
 .github/workflows/rust.yml             |  19 +++
 Cargo.lock                             |   5 +-
 Cargo.toml                             |   3 +
 README.md                              |   2 +-
 build.rs                               |   6 +-
 dist-material/LICENSE                  |   2 +-
 docs/en/configuration/ini-settings.md  |   1 +
 docs/en/configuration/zend-observer.md |  32 +++++
 docs/menu.yml                          |   2 +
 src/execute.rs                         | 213 +++++++++++++++++++++++++++------
 src/lib.rs                             |   6 +
 src/module.rs                          |  22 ++--
 src/plugin/mod.rs                      |  39 +++++-
 src/plugin/plugin_pdo.rs               |   6 +-
 src/plugin/plugin_predis.rs            |  40 +++++--
 src/tag.rs                             |   4 +-
 tests/common/mod.rs                    |  21 ++++
 tests/data/expected_context.yaml       |   6 +-
 tests/e2e.rs                           |   2 +-
 19 files changed, 354 insertions(+), 77 deletions(-)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index bf88d86..3136a71 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -56,16 +56,31 @@ jobs:
           # Many composer dependencies need PHP 7.2+
           - php: "7.2"
             swoole: "4.6.7"
+            enable_zend_observer: "Off"
           - php: "7.3"
             swoole: "4.7.1"
+            enable_zend_observer: "Off"
           - php: "7.4"
             swoole: "4.8.10"
+            enable_zend_observer: "Off"
           - php: "8.0"
             swoole: "5.0.0"
+            enable_zend_observer: "Off"
+          - php: "8.0"
+            swoole: "5.0.0"
+            enable_zend_observer: "On"
+          - php: "8.1"
+            swoole: "5.0.0"
+            enable_zend_observer: "Off"
           - php: "8.1"
             swoole: "5.0.0"
+            enable_zend_observer: "On"
+          - php: "8.2"
+            swoole: "5.0.0"
+            enable_zend_observer: "Off"
           - php: "8.2"
             swoole: "5.0.0"
+            enable_zend_observer: "On"
 
     runs-on: ${{ matrix.os }}
     steps:
@@ -164,6 +179,8 @@ jobs:
           toolchain: ${{ env.RUST_STABLE_TOOLCHAIN }}
           command: test
           args: --release --workspace
+        env:
+          ENABLE_ZEND_OBSERVER: ${{ matrix.version.enable_zend_observer }}
         continue-on-error: true
 
       # Rebuild the mixture when cargo test failed.
@@ -189,6 +206,8 @@ jobs:
           toolchain: ${{ env.RUST_STABLE_TOOLCHAIN }}
           command: test
           args: --release --workspace
+        env:
+          ENABLE_ZEND_OBSERVER: ${{ matrix.version.enable_zend_observer }}
 
       - name: View logs
         if: always()
diff --git a/Cargo.lock b/Cargo.lock
index e49b7f9..49394f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1528,9 +1528,9 @@ dependencies = [
 
 [[package]]
 name = "phper-sys"
-version = "0.12.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "b2538b854618f97f39c0612a9ad97c5980b8d6f32d3a01a9de605851dcaf9507"
+checksum = "fda67a4b0389183e564f08797db31b635eff8bb2b30cfe5cc2394acaa35cd6a7"
 dependencies = [
  "bindgen",
  "cc",
@@ -2099,6 +2099,7 @@ dependencies = [
  "libc",
  "once_cell",
  "phper",
+ "phper-build",
  "prost",
  "reqwest",
  "serde_json",
diff --git a/Cargo.toml b/Cargo.toml
index 7164e6e..47debd0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,3 +60,6 @@ url = "2.3.1"
 axum = "0.6.18"
 fastcgi-client = "0.8.0"
 reqwest = { version = "0.11.18", features = ["trust-dns", "json", "stream"] }
+
+[build-dependencies]
+phper-build = "0.12.0"
diff --git a/README.md b/README.md
index 0f92d0c..a3b339a 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ SkyWalking PHP Agent requires SkyWalking 8.4+ and PHP 7.2+
 
 * Swoole Ecosystem
 
-  *The components of the PHP-FPM ecosystem can also be used in Swoole 
regardless of the flag `SWOOLE_HOOK_ALL`.
+  *The components of the PHP-FPM ecosystem can also be used in Swoole 
regardless of the flag `SWOOLE_HOOK_ALL`.*
 
 ## Contact Us
 
diff --git a/build.rs b/build.rs
index dd029df..e7c34a8 100644
--- a/build.rs
+++ b/build.rs
@@ -14,9 +14,5 @@
 // limitations under the License.
 
 fn main() {
-    #[cfg(target_os = "macos")]
-    {
-        println!("cargo:rustc-link-arg=-undefined");
-        println!("cargo:rustc-link-arg=dynamic_lookup");
-    }
+    phper_build::register_all();
 }
diff --git a/dist-material/LICENSE b/dist-material/LICENSE
index 57055e1..79cc092 100644
--- a/dist-material/LICENSE
+++ b/dist-material/LICENSE
@@ -599,7 +599,7 @@ The text of each license is also included in 
licenses/LICENSE-[project].txt.
     https://crates.io/crates/phper-alloc/0.12.0 0.12.0 MulanPSL-2.0
     https://crates.io/crates/phper-build/0.12.0 0.12.0 MulanPSL-2.0
     https://crates.io/crates/phper-macros/0.12.0 0.12.0 MulanPSL-2.0
-    https://crates.io/crates/phper-sys/0.12.0 0.12.0 MulanPSL-2.0
+    https://crates.io/crates/phper-sys/0.12.1 0.12.1 MulanPSL-2.0
 
 ========================================================================
 Unlicense licenses
diff --git a/docs/en/configuration/ini-settings.md 
b/docs/en/configuration/ini-settings.md
index 9106753..bc48b4d 100644
--- a/docs/en/configuration/ini-settings.md
+++ b/docs/en/configuration/ini-settings.md
@@ -19,3 +19,4 @@ This is the configuration list supported in `php.ini`.
 | skywalking_agent.ssl_cert_chain_path             | The certificate file. 
Enable mTLS when `ssl_key_path` and `ssl_cert_chain_path` exist.                
                   |                           |
 | skywalking_agent.heartbeat_period                | Agent heartbeat report 
period. Unit, second.                                                           
                  | 30                        |
 | skywalking_agent.properties_report_period_factor | The agent sends the 
instance properties to the backend every heartbeat_period * 
properties_report_period_factor seconds. | 10                        |
+| skywalking_agent.enable_zend_observer            | Whether to use `zend 
observer` instead of `zend_execute_ex` to hook the functions, this feature is 
only available for PHP8+.  | Off                       |
diff --git a/docs/en/configuration/zend-observer.md 
b/docs/en/configuration/zend-observer.md
new file mode 100644
index 0000000..c6deeb8
--- /dev/null
+++ b/docs/en/configuration/zend-observer.md
@@ -0,0 +1,32 @@
+# Zend observer
+
+> Refer to: 
<https://www.datadoghq.com/blog/engineering/php-8-observability-baked-right-in/#the-observability-landscape-before-php-8>
+
+By default, skywalking-php hooks the `zend_execute_internal` and 
`zend_execute_ex` functions to implement auto instrumentation.
+
+But there are some drawbacks:
+
+- All PHP function calls are placed on the native C stack, which is limited by 
the value set in `ulimit -s`.
+- Not compatible with the new JIT added in PHP 8.
+
+## The observer API in PHP 8+
+
+Now, zend observer api is a new generation method, and it is also a method 
currently recommended by PHP8.
+
+This method has no stack problem and will not affect JIT.
+
+## Configuration
+
+The following configuration example enables JIT in PHP8 and zend observer 
support in skywalking-php at the same time.
+
+```ini
+[opcache]
+zend_extension = opcache
+; Enable JIT
+opcache.jit = tracing
+
+[skywalking_agent]
+extension = skywalking_agent.so
+; Switch to use zend observer api to implement auto instrumentation.
+skywalking_agent.enable_zend_observer = On
+```
diff --git a/docs/menu.yml b/docs/menu.yml
index 63878fc..7e9c135 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -26,6 +26,8 @@ catalog:
     catalog:
       - name: "INI Settings"
         path: "/en/configuration/ini-settings"
+      - name: "Zend Observer"
+        path: "/en/configuration/zend-observer"
   - name: "Contribution"
     catalog:
       - name: "Compiling Guidance"
diff --git a/src/execute.rs b/src/execute.rs
index 4bc8e68..779afbd 100644
--- a/src/execute.rs
+++ b/src/execute.rs
@@ -14,7 +14,8 @@
 // limitations under the License.
 
 use crate::{
-    plugin::select_plugin,
+    module::{ENABLE_ZEND_OBSERVER, IS_ZEND_OBSERVER_CALLED_FOR_INTERNAL},
+    plugin::select_plugin_hook,
     request::{HACK_SWOOLE_ON_REQUEST_FUNCTION_NAME, IS_SWOOLE},
     util::catch_unwind_result,
 };
@@ -28,11 +29,10 @@ use phper::{
 use std::{any::Any, panic::AssertUnwindSafe, ptr::null_mut, 
sync::atomic::Ordering};
 use tracing::{error, trace};
 
-pub type BeforeExecuteHook =
-    dyn FnOnce(Option<i64>, &mut ExecuteData) -> crate::Result<Box<dyn Any>>;
+pub type BeforeExecuteHook = dyn Fn(Option<i64>, &mut ExecuteData) -> 
crate::Result<Box<dyn Any>>;
 
 pub type AfterExecuteHook =
-    dyn FnOnce(Option<i64>, Box<dyn Any>, &mut ExecuteData, &mut ZVal) -> 
crate::Result<()>;
+    dyn Fn(Option<i64>, Box<dyn Any>, &mut ExecuteData, &mut ZVal) -> 
crate::Result<()>;
 
 pub trait Noop {
     fn noop() -> Self;
@@ -109,21 +109,9 @@ unsafe extern "C" fn execute_internal(
         return;
     }
 
-    let plugin = select_plugin(class_name.as_deref(), &function_name);
-    let plugin = match plugin {
-        Some(plugin) => plugin,
-        None => {
-            ori_execute_internal(Some(execute_data), Some(return_value));
-            return;
-        }
-    };
-
-    let (before, after) = match plugin.hook(class_name.as_deref(), 
&function_name) {
-        Some(hook) => hook,
-        None => {
-            ori_execute_internal(Some(execute_data), Some(return_value));
-            return;
-        }
+    let Some((before, after)) = select_plugin_hook(class_name.as_deref(), 
&function_name) else {
+        ori_execute_internal(Some(execute_data), Some(return_value));
+        return;
     };
 
     let request_id = infer_request_id(execute_data);
@@ -183,21 +171,9 @@ unsafe extern "C" fn execute_ex(execute_data: *mut 
sys::zend_execute_data) {
         }
     };
 
-    let plugin = select_plugin(class_name.as_deref(), &function_name);
-    let plugin = match plugin {
-        Some(plugin) => plugin,
-        None => {
-            ori_execute_ex(Some(execute_data));
-            return;
-        }
-    };
-
-    let (before, after) = match plugin.hook(class_name.as_deref(), 
&function_name) {
-        Some(hook) => hook,
-        None => {
-            ori_execute_ex(Some(execute_data));
-            return;
-        }
+    let Some((before, after)) = select_plugin_hook(class_name.as_deref(), 
&function_name) else {
+        ori_execute_ex(Some(execute_data));
+        return;
     };
 
     let request_id = infer_request_id(execute_data);
@@ -257,11 +233,15 @@ fn ori_execute_ex(execute_data: Option<&mut ExecuteData>) 
{
 
 pub fn register_execute_functions() {
     unsafe {
-        ORI_EXECUTE_INTERNAL = sys::zend_execute_internal;
-        sys::zend_execute_internal = Some(execute_internal);
+        if !*ENABLE_ZEND_OBSERVER || !IS_ZEND_OBSERVER_CALLED_FOR_INTERNAL {
+            ORI_EXECUTE_INTERNAL = sys::zend_execute_internal;
+            sys::zend_execute_internal = Some(execute_internal);
+        }
 
-        ORI_EXECUTE_EX = sys::zend_execute_ex;
-        sys::zend_execute_ex = Some(execute_ex);
+        if !*ENABLE_ZEND_OBSERVER {
+            ORI_EXECUTE_EX = sys::zend_execute_ex;
+            sys::zend_execute_ex = Some(execute_ex);
+        }
     }
 }
 
@@ -324,3 +304,160 @@ fn infer_request_id(execute_data: &mut ExecuteData) -> 
Option<i64> {
         }
     }
 }
+
+pub fn register_observer_handlers() {
+    #[cfg(phper_major_version = "8")]
+    unsafe {
+        if *ENABLE_ZEND_OBSERVER {
+            tracing::info!("register zend observer handlers");
+            
sys::zend_observer_fcall_register(Some(self::observer::observer_handler));
+        }
+    }
+}
+
+#[cfg(phper_major_version = "8")]
+pub mod observer {
+    use super::*;
+    use dashmap::DashMap;
+    use once_cell::sync::Lazy;
+    use phper::sys;
+
+    /// Key is the pointer address of execute_data, value is the result of
+    /// before hook.
+    static mut RESULT_MAP: Lazy<DashMap<*const sys::zend_execute_data, Box<dyn 
Any>>> =
+        Lazy::new(DashMap::new);
+
+    pub unsafe extern "C" fn observer_handler(
+        execute_data: *mut sys::zend_execute_data,
+    ) -> sys::zend_observer_fcall_handlers {
+        let Some(execute_data) = ExecuteData::try_from_mut_ptr(execute_data) 
else {
+            return Default::default()
+        };
+
+        let (function_name, class_name) = match 
get_function_and_class_name(execute_data) {
+            Ok(x) => x,
+            Err(err) => {
+                error!(?err, "get function and class name failed");
+                return Default::default();
+            }
+        };
+
+        trace!(
+            ?function_name,
+            ?class_name,
+            "observer_handler function and class name"
+        );
+
+        let Some(function_name) = function_name else {
+            return Default::default();
+        };
+
+        if function_name == HACK_SWOOLE_ON_REQUEST_FUNCTION_NAME {
+            return Default::default();
+        }
+
+        if select_plugin_hook(class_name.as_deref(), &function_name).is_none() 
{
+            return Default::default();
+        }
+
+        sys::zend_observer_fcall_handlers {
+            begin: Some(observer_begin),
+            end: Some(observer_end),
+        }
+    }
+
+    unsafe extern "C" fn observer_begin(execute_data: *mut 
sys::zend_execute_data) {
+        let Some(execute_data) = ExecuteData::try_from_mut_ptr(execute_data) 
else {
+            return;
+        };
+        trace!(execute_data_ptr=?execute_data.as_ptr(), "start 
observer_begin");
+
+        let Ok((function_name, class_name)) = 
get_function_and_class_name(execute_data) else {
+            return;
+        };
+
+        trace!(
+            ?function_name,
+            ?class_name,
+            "observer_begin function and class name"
+        );
+
+        let Some(function_name) = function_name else {
+            return;
+        };
+
+        let Some((before, _)) = select_plugin_hook(class_name.as_deref(), 
&function_name) else {
+            return;
+        };
+
+        let request_id = infer_request_id(execute_data);
+        trace!(
+            ?request_id,
+            ?function_name,
+            ?class_name,
+            "observer_begin infer request id"
+        );
+
+        let result =
+            match catch_unwind_result(AssertUnwindSafe(|| before(request_id, 
execute_data))) {
+                Ok(result) => result,
+                Err(err) => {
+                    error!(?err, "run observer_begin hook failed");
+                    return;
+                }
+            };
+
+        RESULT_MAP.insert(execute_data.as_ptr(), result);
+    }
+
+    unsafe extern "C" fn observer_end(
+        execute_data: *mut sys::zend_execute_data, retval: *mut sys::zval,
+    ) {
+        let Some(execute_data) = ExecuteData::try_from_mut_ptr(execute_data) 
else {
+            return;
+        };
+        trace!(execute_data_ptr=?execute_data.as_ptr(), "start observer_end");
+
+        let Some((_, result)) = RESULT_MAP.remove(&execute_data.as_ptr()) else 
{
+            return;
+        };
+
+        let mut null = ZVal::from(());
+        let ret = match ZVal::try_from_mut_ptr(retval) {
+            Some(ret) => ret,
+            None => &mut null,
+        };
+
+        let Ok((function_name, class_name)) = 
get_function_and_class_name(execute_data) else {
+            return;
+        };
+
+        trace!(
+            ?function_name,
+            ?class_name,
+            "observer_end function and class name"
+        );
+
+        let Some(function_name) = function_name else {
+            return;
+        };
+
+        let Some((_, after)) = select_plugin_hook(class_name.as_deref(), 
&function_name) else {
+            return;
+        };
+
+        let request_id = infer_request_id(execute_data);
+        trace!(
+            ?request_id,
+            ?function_name,
+            ?class_name,
+            "observer_end infer request id"
+        );
+
+        if let Err(err) = catch_unwind_result(AssertUnwindSafe(|| {
+            after(request_id, result, execute_data, ret)
+        })) {
+            error!(?err, "run observer_end hook failed");
+        };
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 75e683e..5a16850 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -83,6 +83,11 @@ const SKYWALKING_AGENT_HEARTBEAT_PERIOD: &str = 
"skywalking_agent.heartbeat_peri
 const SKYWALKING_AGENT_PROPERTIES_REPORT_PERIOD_FACTOR: &str =
     "skywalking_agent.properties_report_period_factor";
 
+/// Whether to use zend observer instead of zend_execute_ex to hook the
+/// functions. This feature is only available for PHP8+, and can work with
+/// PHP8's jit.
+const SKYWALKING_AGENT_ENABLE_ZEND_OBSERVER: &str = 
"skywalking_agent.enable_zend_observer";
+
 #[php_get_module]
 pub fn get_module() -> Module {
     let mut module = Module::new(
@@ -147,6 +152,7 @@ pub fn get_module() -> Module {
         10i64,
         Policy::System,
     );
+    module.add_ini(SKYWALKING_AGENT_ENABLE_ZEND_OBSERVER, false, 
Policy::System);
 
     // Hooks.
     module.on_module_init(module::init);
diff --git a/src/module.rs b/src/module.rs
index 37a41fe..662044e 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -15,15 +15,10 @@
 
 use crate::{
     channel::Reporter,
-    execute::register_execute_functions,
+    execute::{register_execute_functions, register_observer_handlers},
     util::{get_sapi_module_name, IPS},
     worker::init_worker,
-    SKYWALKING_AGENT_AUTHENTICATION, SKYWALKING_AGENT_ENABLE, 
SKYWALKING_AGENT_ENABLE_TLS,
-    SKYWALKING_AGENT_HEARTBEAT_PERIOD, SKYWALKING_AGENT_LOG_FILE, 
SKYWALKING_AGENT_LOG_LEVEL,
-    SKYWALKING_AGENT_PROPERTIES_REPORT_PERIOD_FACTOR, 
SKYWALKING_AGENT_RUNTIME_DIR,
-    SKYWALKING_AGENT_SERVICE_NAME, SKYWALKING_AGENT_SKYWALKING_VERSION,
-    SKYWALKING_AGENT_SSL_CERT_CHAIN_PATH, SKYWALKING_AGENT_SSL_KEY_PATH,
-    SKYWALKING_AGENT_SSL_TRUSTED_CA_PATH,
+    *,
 };
 use anyhow::bail;
 use once_cell::sync::Lazy;
@@ -117,6 +112,18 @@ pub static HEARTBEAT_PERIOD: Lazy<i64> =
 pub static PROPERTIES_REPORT_PERIOD_FACTOR: Lazy<i64> =
     Lazy::new(|| 
ini_get::<i64>(SKYWALKING_AGENT_PROPERTIES_REPORT_PERIOD_FACTOR));
 
+/// Zend observer is only support in PHP8+.
+pub static ENABLE_ZEND_OBSERVER: Lazy<bool> = Lazy::new(|| {
+    sys::PHP_MAJOR_VERSION >= 8 && 
ini_get::<bool>(SKYWALKING_AGENT_ENABLE_ZEND_OBSERVER)
+});
+
+/// For PHP 8.2+, zend observer api are now also called for internal functions.
+///
+/// Refer to this commit: 
<https://github.com/php/php-src/commit/625f1649639c2b9a9d76e4d42f88c264ddb8447d>
+#[allow(clippy::absurd_extreme_comparisons)]
+pub const IS_ZEND_OBSERVER_CALLED_FOR_INTERNAL: bool =
+    sys::PHP_MAJOR_VERSION > 8 || (sys::PHP_MAJOR_VERSION == 8 && 
sys::PHP_MINOR_VERSION >= 2);
+
 pub fn init() {
     if !is_enable() {
         return;
@@ -184,6 +191,7 @@ pub fn init() {
 
     // Hook functions.
     register_execute_functions();
+    register_observer_handlers();
 }
 
 pub fn shutdown() {
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index 53adeef..71acd06 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -26,6 +26,8 @@ use crate::execute::{AfterExecuteHook, BeforeExecuteHook};
 use once_cell::sync::Lazy;
 use phper::{eg, objects::ZObj};
 use skywalking::trace::span::AbstractSpan;
+use std::{collections::HashMap, ops::Deref, sync::Mutex};
+use tracing::error;
 
 // Register plugins here.
 static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
@@ -54,7 +56,35 @@ pub trait Plugin {
     ) -> Option<(Box<BeforeExecuteHook>, Box<AfterExecuteHook>)>;
 }
 
-pub fn select_plugin(class_name: Option<&str>, function_name: &str) -> 
Option<&'static DynPlugin> {
+pub fn select_plugin_hook(
+    class_name: Option<&str>, function_name: &str,
+) -> Option<(&'static BeforeExecuteHook, &'static AfterExecuteHook)> {
+    type HookMap =
+        HashMap<(Option<String>, String), Option<(Box<BeforeExecuteHook>, 
Box<AfterExecuteHook>)>>;
+
+    static LOCK: Lazy<Mutex<()>> = Lazy::new(Default::default);
+    static mut HOOK_MAP: Lazy<HookMap> = Lazy::new(HashMap::new);
+
+    let _guard = match LOCK.lock() {
+        Ok(guard) => guard,
+        Err(err) => {
+            error!(?err, "get lock failed");
+            return None;
+        }
+    };
+    unsafe {
+        HOOK_MAP
+            .entry((class_name.map(ToOwned::to_owned), 
function_name.to_owned()))
+            .or_insert_with(|| {
+                select_plugin(class_name, function_name)
+                    .and_then(|plugin| plugin.hook(class_name, function_name))
+            })
+            .as_ref()
+            .map(|(before, after)| (before.deref(), after.deref()))
+    }
+}
+
+fn select_plugin(class_name: Option<&str>, function_name: &str) -> 
Option<&'static DynPlugin> {
     let mut selected_plugin = None;
 
     for plugin in &*PLUGINS {
@@ -77,9 +107,9 @@ pub fn select_plugin(class_name: Option<&str>, 
function_name: &str) -> Option<&'
     selected_plugin.map(AsRef::as_ref)
 }
 
-fn log_exception(span: &mut impl AbstractSpan) {
-    let ex = unsafe { ZObj::try_from_mut_ptr(eg!(exception)) };
-    if let Some(ex) = ex {
+fn log_exception(span: &mut impl AbstractSpan) -> Option<&mut ZObj> {
+    let mut ex = unsafe { ZObj::try_from_mut_ptr(eg!(exception)) };
+    if let Some(ex) = ex.as_mut() {
         let mut span_object = span.span_object_mut();
         span_object.is_error = true;
 
@@ -101,4 +131,5 @@ fn log_exception(span: &mut impl AbstractSpan) {
             span_object.add_log(logs);
         }
     }
+    ex
 }
diff --git a/src/plugin/plugin_pdo.rs b/src/plugin/plugin_pdo.rs
index 9d33b6d..c4a32b9 100644
--- a/src/plugin/plugin_pdo.rs
+++ b/src/plugin/plugin_pdo.rs
@@ -202,6 +202,10 @@ fn after_hook(
 ) -> crate::Result<()> {
     let mut span = span.downcast::<Span>().unwrap();
 
+    if log_exception(&mut *span).is_some() {
+        return Ok(());
+    }
+
     if let Some(b) = return_value.as_bool() {
         if !b {
             return after_hook_when_false(get_this_mut(execute_data)?, &mut 
span);
@@ -217,8 +221,6 @@ fn after_hook(
         }
     }
 
-    log_exception(&mut *span);
-
     Ok(())
 }
 
diff --git a/src/plugin/plugin_predis.rs b/src/plugin/plugin_predis.rs
index bcf9e36..ca7ab7e 100644
--- a/src/plugin/plugin_predis.rs
+++ b/src/plugin/plugin_predis.rs
@@ -164,9 +164,7 @@ impl Plugin for PredisPlugin {
         Box<crate::execute::AfterExecuteHook>,
     )> {
         match (class_name, function_name) {
-            (Some(class_name @ "Predis\\Client"), function_name)
-                if 
REDIS_ALL_COMMANDS.contains(&*function_name.to_ascii_uppercase()) =>
-            {
+            (Some(class_name @ "Predis\\Client"), "__call") => {
                 Some(self.hook_predis_execute_command(class_name, 
function_name))
             }
             _ => None,
@@ -181,22 +179,32 @@ enum ConnectionType {
 
 impl PredisPlugin {
     fn hook_predis_execute_command(
-        &self, class_name: &str, function_name: &str,
+        &self, class_name: &str, _function_name: &str,
     ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) {
         let class_name = class_name.to_owned();
-        let function_name = function_name.to_owned();
         (
             Box::new(move |request_id, execute_data| {
                 validate_num_args(execute_data, 1)?;
 
+                let function_name = execute_data
+                    .get_parameter(0)
+                    .as_z_str()
+                    .and_then(|s| s.to_str().ok())
+                    .unwrap_or("")
+                    .to_string();
+
+                let cmd = function_name.to_uppercase();
+
+                if !REDIS_ALL_COMMANDS.contains(&*cmd) {
+                    return Ok(Box::new(()));
+                }
+
                 let this = get_this_mut(execute_data)?;
                 let handle = this.handle();
                 let connection = this.call("getConnection", [])?;
 
                 let peer = Self::get_peer(connection)?;
 
-                let cmd = function_name.to_ascii_uppercase();
-
                 let op = if REDIS_READ_COMMANDS.contains(&*cmd) {
                     Some("read")
                 } else if REDIS_WRITE_COMMANDS.contains(&*cmd) {
@@ -206,14 +214,20 @@ impl PredisPlugin {
                 };
 
                 let key = op
-                    .and_then(|_| execute_data.get_parameter(0).as_z_str())
+                    .and_then(|_| execute_data.get_parameter(1).as_z_arr())
+                    .and_then(|params| params.get(0))
+                    .and_then(|key| key.as_z_str())
                     .and_then(|s| s.to_str().ok());
 
                 debug!(handle, cmd, key, op, "call redis command");
 
-                let mut span = RequestContext::try_with_global_ctx(request_id, 
|ctx| {
-                    Ok(ctx.create_exit_span(&format!("{}->{}", class_name, 
function_name), &peer))
-                })?;
+                let mut span =
+                    RequestContext::try_with_global_ctx(request_id, |ctx| {
+                        Ok(ctx.create_exit_span(
+                            &format!("{}->{}", class_name, &function_name,),
+                            &peer,
+                        ))
+                    })?;
 
                 let mut span_object = span.span_object_mut();
                 span_object.set_span_layer(SpanLayer::Cache);
@@ -230,6 +244,10 @@ impl PredisPlugin {
                 Ok(Box::new(span))
             }),
             Box::new(move |_, span, _, return_value| {
+                if span.downcast_ref::<()>().is_some() {
+                    return Ok(());
+                }
+
                 let mut span = span.downcast::<Span>().unwrap();
 
                 let exception = unsafe { eg!(exception) };
diff --git a/src/tag.rs b/src/tag.rs
index c408390..16a1347 100644
--- a/src/tag.rs
+++ b/src/tag.rs
@@ -17,11 +17,11 @@
 //!
 //! Virtual Cache
 //!
-//! 
https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-cache/
+//! 
<https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-cache/>
 //!
 //! Virtual Database
 //!
-//! 
https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-database/
+//! 
<https://skywalking.apache.org/docs/main/next/en/setup/service-agent/virtual-database/>
 
 use std::fmt::Display;
 
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index 91dbee1..a691108 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -69,6 +69,9 @@ pub const EXT: &str = if cfg!(target_os = "linux") {
     ""
 };
 
+pub static ENABLE_ZEND_OBSERVER: Lazy<String> =
+    Lazy::new(|| env::var("ENABLE_ZEND_OBSERVER").unwrap_or_else(|_| 
"Off".to_owned()));
+
 pub static HTTP_CLIENT: Lazy<reqwest::Client> = 
Lazy::new(reqwest::Client::new);
 
 pub struct Fixture {
@@ -82,6 +85,13 @@ pub struct Fixture {
 
 pub async fn setup() -> Fixture {
     setup_logging();
+    info!(
+        TARGET = &*TARGET,
+        EXT = &*EXT,
+        ENABLE_ZEND_OBSERVER = &*ENABLE_ZEND_OBSERVER,
+        "setup fixture"
+    );
+
     Fixture {
         http_server_1_handle: tokio::spawn(setup_http_proxy_server(
             PROXY_SERVER_1_ADDRESS,
@@ -156,6 +166,7 @@ async fn http_proxy_fpm_handler(
         let method = &req.method().to_string();
         let path = &req.uri().path().to_string();
         let query = &req.uri().query().unwrap_or_default().to_string();
+        info!(method, uri=?req.uri(), "http proxy to fpm");
 
         let stream = TcpStream::connect(&state.fpm_addr).await?;
         let client = fastcgi_client::Client::new(stream);
@@ -293,6 +304,11 @@ fn setup_php_fpm(index: usize, fpm_addr: &str) -> Child {
         ),
         "-d",
         "skywalking_agent.worker_threads=3",
+        "-d",
+        &format!(
+            "skywalking_agent.enable_zend_observer={}",
+            *ENABLE_ZEND_OBSERVER
+        ),
     ];
     info!(cmd = args.join(" "), "start command");
     let child = Command::new(&args[0])
@@ -334,6 +350,11 @@ fn setup_php_swoole(index: usize) -> Child {
         ),
         "-d",
         "skywalking.worker_threads=3",
+        "-d",
+        &format!(
+            "skywalking_agent.enable_zend_observer={}",
+            *ENABLE_ZEND_OBSERVER
+        ),
         &format!("tests/php/swoole/main.{}.php", index),
     ];
     info!(cmd = args.join(" "), "start command");
diff --git a/tests/data/expected_context.yaml b/tests/data/expected_context.yaml
index 8e07feb..7ff9a3d 100644
--- a/tests/data/expected_context.yaml
+++ b/tests/data/expected_context.yaml
@@ -684,9 +684,9 @@ segmentItems:
               }
             logs:
               - logEvent:
-                - { key: SQLSTATE, value: 42S02 }
-                - { key: Error Code, value: '1146' }
-                - { key: Error, value: "Table 'skywalking.not_exist' doesn't 
exist" }
+                  - { key: error.kind, value: PDOException }
+                  - { key: message, value: not null }
+                  - { key: stack, value: not null }
           - operationName: GET:/pdo.php
             parentSpanId: -1
             spanId: 0
diff --git a/tests/e2e.rs b/tests/e2e.rs
index 3ae9e99..00b1f72 100644
--- a/tests/e2e.rs
+++ b/tests/e2e.rs
@@ -215,6 +215,6 @@ async fn request_common(request_builder: RequestBuilder, 
actual_content: impl In
     let response = request_builder.send().await.unwrap();
     let status = response.status();
     let content = response.text().await.unwrap();
-    info!(content, "response content");
+    info!("response content: {}", content);
     assert_eq!((status, content), (StatusCode::OK, actual_content.into()));
 }

Reply via email to