| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use super::Plugin; |
| use crate::{ |
| context::RequestContext, |
| execute::{AfterExecuteHook, BeforeExecuteHook, Noop, validate_num_args}, |
| log::PsrLogLevel, |
| module::PSR_LOGGING_LEVEL, |
| }; |
| use phper::{ |
| alloc::ToRefOwned, arrays::IterKey, classes::ClassEntry, functions::call, objects::ZObj, |
| values::ZVal, |
| }; |
| use skywalking::{ |
| logging::{ |
| logger, |
| record::{LogRecord, RecordType}, |
| }, |
| trace::span::HandleSpanObject, |
| }; |
| use tracing::{debug, instrument}; |
| |
| #[derive(Default, Clone)] |
| pub struct Psr3Plugin; |
| |
| impl Plugin for Psr3Plugin { |
| fn class_names(&self) -> Option<&'static [&'static str]> { |
| None |
| } |
| |
| fn function_name_prefix(&self) -> Option<&'static str> { |
| None |
| } |
| |
| fn parent_classes(&self) -> Option<Vec<Option<&'static phper::classes::ClassEntry>>> { |
| Some(vec![ |
| ClassEntry::from_globals(r"Psr\Log\LoggerInterface").ok(), |
| ]) |
| } |
| |
| fn hook( |
| &self, class_name: Option<&str>, function_name: &str, |
| ) -> Option<( |
| Box<crate::execute::BeforeExecuteHook>, |
| Box<crate::execute::AfterExecuteHook>, |
| )> { |
| let class_name = class_name?; |
| match &*function_name.to_uppercase() { |
| "EMERGENCY" | "ALERT" | "CRITICAL" | "ERROR" | "WARNING" | "NOTICE" | "INFO" |
| | "DEBUG" => { |
| let log_level = function_name.into(); |
| if log_level >= *PSR_LOGGING_LEVEL { |
| Some(self.hook_log_methods( |
| class_name.to_owned(), |
| function_name.to_owned(), |
| log_level, |
| )) |
| } else { |
| None |
| } |
| } |
| "LOG" => Some(self.hook_log(class_name.to_owned(), function_name.to_owned())), |
| _ => None, |
| } |
| } |
| } |
| |
| impl Psr3Plugin { |
| #[instrument(skip_all)] |
| fn hook_log_methods( |
| &self, class_name: String, function_name: String, log_level: PsrLogLevel, |
| ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) { |
| ( |
| Box::new(move |request_id, execute_data| { |
| validate_num_args(execute_data, 1)?; |
| |
| let message = Self::handle_message(execute_data.get_mut_parameter(0))?; |
| let context = if execute_data.num_args() >= 2 { |
| Self::handle_context(execute_data.get_mut_parameter(1))? |
| } else { |
| vec![] |
| }; |
| Self::handle_log( |
| class_name.clone(), |
| function_name.clone(), |
| log_level.clone(), |
| request_id, |
| message, |
| context, |
| )?; |
| Ok(Box::new(())) |
| }), |
| Noop::noop(), |
| ) |
| } |
| |
| #[instrument(skip_all)] |
| fn hook_log( |
| &self, class_name: String, function_name: String, |
| ) -> (Box<BeforeExecuteHook>, Box<AfterExecuteHook>) { |
| ( |
| Box::new(move |request_id, execute_data| { |
| validate_num_args(execute_data, 2)?; |
| |
| let log_level = execute_data.get_parameter(0).expect_z_str()?.to_str()?; |
| let log_level: PsrLogLevel = log_level.into(); |
| if log_level < *PSR_LOGGING_LEVEL { |
| return Ok(Box::new(())); |
| } |
| let message = Self::handle_message(execute_data.get_mut_parameter(1))?; |
| let context = if execute_data.num_args() >= 3 { |
| Self::handle_context(execute_data.get_mut_parameter(2))? |
| } else { |
| vec![] |
| }; |
| Self::handle_log( |
| class_name.clone(), |
| function_name.clone(), |
| log_level, |
| request_id, |
| message, |
| context, |
| )?; |
| Ok(Box::new(())) |
| }), |
| Noop::noop(), |
| ) |
| } |
| |
| fn handle_log( |
| class_name: String, function_name: String, log_level: PsrLogLevel, request_id: Option<i64>, |
| message: String, context: Vec<(String, String)>, |
| ) -> anyhow::Result<()> { |
| debug!(?class_name, ?function_name, "call psr-3 log method"); |
| |
| RequestContext::try_with_global(request_id, |ctx| { |
| logger::log( |
| LogRecord::new() |
| .record_type(RecordType::Text) |
| .content(message) |
| .with_tracing_context(&ctx.tracing_context) |
| .endpoint(&ctx.entry_span.span_object().operation_name) |
| .with_span(&ctx.entry_span) |
| .add_tag("level", log_level.to_string()) |
| .add_tag("logger", &class_name) |
| .add_tags(context), |
| ); |
| Ok(()) |
| })?; |
| |
| Ok(()) |
| } |
| |
| fn handle_message(message: &mut ZVal) -> crate::Result<String> { |
| if let Some(message) = message.as_z_str() { |
| Ok(message.to_str()?.to_string()) |
| } else if let Some(message) = message.as_mut_z_obj() { |
| match Self::cast_object_to_string(message)? { |
| Some(message) => Ok(message), |
| _ => Err("message hasn't __toString method".into()), |
| } |
| } else { |
| Err("unknown message type".into()) |
| } |
| } |
| |
| fn handle_context(context: &mut ZVal) -> crate::Result<Vec<(String, String)>> { |
| let Some(context) = context.as_mut_z_arr() else { |
| return Ok(vec![]); |
| }; |
| let mut tags = Vec::with_capacity(context.len()); |
| for (key, value) in context.iter_mut() { |
| match key { |
| IterKey::Index(_) => continue, |
| IterKey::ZStr(key) => { |
| let key = key.to_str()?.to_string(); |
| |
| let value = if value.as_null().is_some() { |
| "null".to_string() |
| } else if let Some(value) = value.as_bool() { |
| value.to_string() |
| } else if let Some(value) = value.as_long() { |
| value.to_string() |
| } else if let Some(value) = value.as_double() { |
| value.to_string() |
| } else if let Some(value) = value.as_z_str() { |
| value.to_str()?.to_string() |
| } else if value.as_z_arr().is_some() { |
| "Array".to_string() |
| } else if let Some(value) = value.as_mut_z_obj() { |
| let Some(value) = Self::cast_object_to_string(value)? else { |
| continue; |
| }; |
| value |
| } else { |
| continue; |
| }; |
| |
| tags.push((key, value)); |
| } |
| } |
| } |
| Ok(tags) |
| } |
| |
| fn cast_object_to_string(obj: &mut ZObj) -> crate::Result<Option<String>> { |
| if call( |
| "method_exists", |
| [obj.to_ref_owned().into(), "__toString".into()], |
| )? |
| .as_bool() |
| == Some(true) |
| { |
| let s = obj.call("__toString", [])?; |
| Ok(Some(s.expect_z_str()?.to_str()?.to_string())) |
| } else { |
| Ok(None) |
| } |
| } |
| } |