blob: 9e4a2c7dde2784e0bd81ac93769b2fefd64dc496 [file] [log] [blame]
// 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},
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| {
let message = Self::handle_message(execute_data.get_mut_parameter(0))?;
let context = Self::handle_context(execute_data.get_mut_parameter(1))?;
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| {
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 = Self::handle_context(execute_data.get_mut_parameter(2))?;
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)
}
}
}