Skip to content

Commit 56fac28

Browse files
authored
Support log reporting based on PSR-3 (#127)
1 parent 2a6e39b commit 56fac28

File tree

17 files changed

+657
-31
lines changed

17 files changed

+657
-31
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ skywalking = { version = "0.8.0", features = ["management"] }
6262
skywalking-php-worker = { path = "worker" }
6363
systemstat = "0.2.3"
6464
thiserror = "1.0.44"
65+
time = { version = "0.3", features = ["formatting"] }
6566
tokio = { version = "1.29.1", features = ["full"] }
6667
tokio-stream = "0.1.14"
6768
tonic = { version = "0.8.3", features = ["tls", "tls-roots"] }
6869
tracing = { version = "0.1.37", features = ["attributes"] }
6970
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "time", "local-time"] }
7071
url = "2.4.0"
71-
time = { version = "0.3", features = ["formatting"] }
7272

7373
[dev-dependencies]
7474
axum = "0.6.19"

docs/en/configuration/context-injection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Context injection
1+
# Context Injection
22

33
If you want to fetch the SkyWalking Context in your PHP code, which is super helpful for debugging and observability,
44
You can enable the configuration item `skywalking_agent.inject_context`.

docs/en/configuration/ini-settings.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ This is the configuration list supported in `php.ini`.
2020
| skywalking_agent.heartbeat_period | Agent heartbeat report period. Unit, second. | 30 |
2121
| skywalking_agent.properties_report_period_factor | The agent sends the instance properties to the backend every heartbeat_period * properties_report_period_factor seconds. | 10 |
2222
| 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 |
23-
| skywalking_agent.reporter_type | Reporter type, optional values are `grpc`, `kafka` and `standalone`. | grpc |
23+
| skywalking_agent.reporter_type | Reporter type, optional values are `grpc`, `kafka` and `standalone`. | grpc |
2424
| skywalking_agent.kafka_bootstrap_servers | A list of host/port pairs to use for connect to the Kafka cluster. Only available when `reporter_type` is `kafka`. | |
2525
| skywalking_agent.kafka_producer_config | Configure Kafka Producer configuration in JSON format `{"key": "value}`. Only available when `reporter_type` is `kafka`. | {} |
2626
| skywalking_agent.inject_context | Whether to enable automatic injection of skywalking context variables (such as `SW_TRACE_ID`). For `php-fpm` mode, it will be injected into the `$_SERVER` variable. For `swoole` mode, it will be injected into the `$request->server` variable. | Off |
27-
| skywalking_agent.instance_name | Instance name. You can set `${HOSTNAME}`, refer to [Example #1](https://www.php.net/manual/en/install.fpm.configuration.php) | |
27+
| skywalking_agent.instance_name | Instance name. You can set `${HOSTNAME}`, refer to [Example #1](https://www.php.net/manual/en/install.fpm.configuration.php) | |
2828
| skywalking_agent.standalone_socket_path | Unix domain socket file path of standalone skywalking php worker. Only available when `reporter_type` is `standalone`. | |
29+
| skywalking_agent.psr_logging_level | The log level reported to SkyWalking, based on PSR-3, one of `Off`, `Debug`, `Info`, Notice`, Warning`, Error`, Critical`, Alert`, Emergency`. | Off |
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Report Log
2+
3+
## Overview
4+
5+
In `skywalking-php`, the log level configuration is managed using the `skywalking_agent.psr_logging_level` directive in your `php.ini` file. This configuration defines the minimum log level that will be reported to SkyWalking. The log levels are based on PSR-3 standards and allow you to control the verbosity of the logs sent to SkyWalking.
6+
7+
## Configuration
8+
9+
You can set the `skywalking_agent.psr_logging_level` in your `php.ini` file:
10+
11+
```ini
12+
skywalking_agent.psr_logging_level = Info
13+
```
14+
15+
The possible values for this configuration are:
16+
17+
- `Off`: No logs will be reported to SkyWalking.
18+
- `Debug`: Logs with the level "Debug" or higher will be reported.
19+
- `Info`: Logs with the level "Info" or higher will be reported.
20+
- `Notice`: Logs with the level "Notice" or higher will be reported.
21+
- `Warning`: Logs with the level "Warning" or higher will be reported.
22+
- `Error`: Logs with the level "Error" or higher will be reported.
23+
- `Critical`: Logs with the level "Critical" or higher will be reported.
24+
- `Alert`: Logs with the level "Alert" or higher will be reported.
25+
- `Emergency`: Logs with the level "Emergency" or higher will be reported.
26+
27+
### Default Value
28+
29+
The default value for `skywalking_agent.psr_logging_level` is set to `Off`, which means no log will be reported to SkyWalking unless specified otherwise.
30+
31+
## How It Works
32+
33+
The `skywalking_agent.psr_logging_level` setting works by hooking into any PHP `LoggerInterface` implementation that follows the PSR-3 standard. The agent listens for log events and compares the log level with the configured value.
34+
35+
- If the log level is **greater than or equal to** the specified `skywalking_agent.psr_logging_level`, the log is reported to SkyWalking.
36+
- Logs with a level **lower than** the configured value will be ignored and not sent to SkyWalking.
37+
38+
This approach ensures that only relevant logs (those that meet or exceed the configured severity level) are sent to SkyWalking, minimizing noise and focusing on more critical events.
39+
40+
## Example Usage
41+
42+
To report logs of level `Warning` and higher to SkyWalking, you would set the configuration as follows:
43+
44+
```ini
45+
skywalking_agent.psr_logging_level = Warning
46+
```
47+
48+
With this setting, logs at the levels `Warning`, `Error`, `Critical`, `Alert`, and `Emergency` will be sent to SkyWalking, while logs at the `Debug`, `Info`, and `Notice` levels will be ignored.
49+
50+
## Conclusion
51+
52+
The `skywalking_agent.psr_logging_level` configuration gives you fine-grained control over the logging behavior of your SkyWalking PHP agent. Adjusting the log level allows you to ensure that only the most important logs are captured, optimizing your monitoring and debugging workflows.

docs/menu.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ catalog:
2828
path: "/en/configuration/ini-settings"
2929
- name: "Zend Observer"
3030
path: "/en/configuration/zend-observer"
31-
- name: "Context injection"
31+
- name: "Context Injection"
3232
path: "/en/configuration/context-injection"
33+
- name: "Report Log"
34+
path: "/en/configuration/report-log"
3335
- name: "Reporter"
3436
catalog:
3537
- name: "Kafka Reporter"

src/errors.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,9 @@ impl From<String> for Error {
4444
Self::Anyhow(anyhow!("{}", e))
4545
}
4646
}
47+
48+
impl From<&str> for Error {
49+
fn from(e: &str) -> Self {
50+
Self::Anyhow(anyhow!("{}", e))
51+
}
52+
}

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod component;
2222
mod context;
2323
mod errors;
2424
mod execute;
25+
mod log;
2526
mod module;
2627
mod plugin;
2728
mod request;
@@ -113,6 +114,10 @@ const SKYWALKING_AGENT_INJECT_CONTEXT: &str = "skywalking_agent.inject_context";
113114
/// available when `reporter_type` is `standalone`.
114115
const SKYWALKING_AGENT_STANDALONE_SOCKET_PATH: &str = "skywalking_agent.standalone_socket_path";
115116

117+
/// The log level reported to SkyWalking, based on PSR-3, one of `Off`, `Debug`,
118+
/// `Info`, Notice`, Warning`, Error`, Critical`, Alert`, Emergency`.
119+
const SKYWALKING_AGENT_PSR_LOGGING_LEVEL: &str = "skywalking_agent.psr_logging_level";
120+
116121
#[php_get_module]
117122
pub fn get_module() -> Module {
118123
let mut module = Module::new(
@@ -204,6 +209,11 @@ pub fn get_module() -> Module {
204209
"".to_string(),
205210
Policy::System,
206211
);
212+
module.add_ini(
213+
SKYWALKING_AGENT_PSR_LOGGING_LEVEL,
214+
"".to_string(),
215+
Policy::System,
216+
);
207217

208218
// Hooks.
209219
module.on_module_init(module::init);

src/log.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
use std::fmt::Display;
17+
18+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19+
pub enum PsrLogLevel {
20+
Off,
21+
Debug,
22+
Info,
23+
Notice,
24+
Warning,
25+
Error,
26+
Critical,
27+
Alert,
28+
Emergency,
29+
}
30+
31+
impl From<&str> for PsrLogLevel {
32+
fn from(s: &str) -> Self {
33+
match &*s.to_uppercase() {
34+
"DEBUG" => PsrLogLevel::Debug,
35+
"INFO" => PsrLogLevel::Info,
36+
"NOTICE" => PsrLogLevel::Notice,
37+
"WARNING" => PsrLogLevel::Warning,
38+
"ERROR" => PsrLogLevel::Error,
39+
"CRITICAL" => PsrLogLevel::Critical,
40+
"ALERT" => PsrLogLevel::Alert,
41+
"EMERGENCY" => PsrLogLevel::Emergency,
42+
_ => PsrLogLevel::Off,
43+
}
44+
}
45+
}
46+
47+
impl Display for PsrLogLevel {
48+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49+
match self {
50+
PsrLogLevel::Off => "OFF".fmt(f),
51+
PsrLogLevel::Debug => "DEBUG".fmt(f),
52+
PsrLogLevel::Info => "INFO".fmt(f),
53+
PsrLogLevel::Notice => "NOTICE".fmt(f),
54+
PsrLogLevel::Warning => "WARNING".fmt(f),
55+
PsrLogLevel::Error => "ERROR".fmt(f),
56+
PsrLogLevel::Critical => "CRITICAL".fmt(f),
57+
PsrLogLevel::Alert => "ALERT".fmt(f),
58+
PsrLogLevel::Emergency => "EMERGENCY".fmt(f),
59+
}
60+
}
61+
}

src/module.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use crate::{
1717
channel::Reporter,
1818
execute::{register_execute_functions, register_observer_handlers},
19+
log::PsrLogLevel,
1920
util::{get_sapi_module_name, get_str_ini_with_default, IPS},
2021
worker::init_worker,
2122
*,
@@ -25,6 +26,7 @@ use once_cell::sync::Lazy;
2526
use phper::{arrays::ZArr, ini::ini_get, sys};
2627
use skywalking::{
2728
common::random_generator::RandomGenerator,
29+
logging::logger::{self, Logger},
2830
trace::tracer::{self, Tracer},
2931
};
3032
use std::{
@@ -33,6 +35,7 @@ use std::{
3335
os::unix::prelude::OsStrExt,
3436
path::{Path, PathBuf},
3537
str::FromStr,
38+
sync::Arc,
3639
time::SystemTime,
3740
};
3841
use time::format_description::well_known::Rfc3339;
@@ -158,6 +161,12 @@ pub static INJECT_CONTEXT: Lazy<bool> =
158161
pub const IS_ZEND_OBSERVER_CALLED_FOR_INTERNAL: bool =
159162
sys::PHP_MAJOR_VERSION > 8 || (sys::PHP_MAJOR_VERSION == 8 && sys::PHP_MINOR_VERSION >= 2);
160163

164+
pub static PSR_LOGGING_LEVEL: Lazy<PsrLogLevel> = Lazy::new(|| {
165+
get_str_ini_with_default(SKYWALKING_AGENT_PSR_LOGGING_LEVEL)
166+
.as_str()
167+
.into()
168+
});
169+
161170
pub fn init() {
162171
if !is_enable() {
163172
return;
@@ -183,6 +192,7 @@ pub fn init() {
183192
Lazy::force(&KAFKA_BOOTSTRAP_SERVERS);
184193
Lazy::force(&KAFKA_PRODUCER_CONFIG);
185194
Lazy::force(&INJECT_CONTEXT);
195+
Lazy::force(&PSR_LOGGING_LEVEL);
186196

187197
if let Err(err) = try_init_logger() {
188198
eprintln!("skywalking_agent: initialize logger failed: {}", err);
@@ -221,12 +231,16 @@ pub fn init() {
221231
// Initialize Agent worker.
222232
init_worker();
223233

234+
let reporter = Arc::new(Reporter::new(&*SOCKET_FILE_PATH));
235+
224236
tracer::set_global_tracer(Tracer::new(
225237
&*SERVICE_NAME,
226238
&*SERVICE_INSTANCE,
227-
Reporter::new(&*SOCKET_FILE_PATH),
239+
reporter.clone(),
228240
));
229241

242+
logger::set_global_logger(Logger::new(&*SERVICE_NAME, &*SERVICE_INSTANCE, reporter));
243+
230244
// Hook functions.
231245
register_execute_functions();
232246
register_observer_handlers();

src/plugin/mod.rs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,25 @@ mod plugin_mongodb;
2121
mod plugin_mysqli;
2222
mod plugin_pdo;
2323
mod plugin_predis;
24+
mod plugin_psr3;
2425
mod plugin_redis;
2526
mod plugin_swoole;
2627
mod style;
2728

28-
use crate::execute::{AfterExecuteHook, BeforeExecuteHook};
29+
use crate::{
30+
execute::{AfterExecuteHook, BeforeExecuteHook},
31+
log::PsrLogLevel,
32+
module::PSR_LOGGING_LEVEL,
33+
};
2934
use once_cell::sync::Lazy;
30-
use phper::{eg, objects::ZObj};
35+
use phper::{classes::ClassEntry, eg, objects::ZObj};
3136
use skywalking::trace::span::HandleSpanObject;
3237
use std::{collections::HashMap, ops::Deref, sync::Mutex};
3338
use tracing::error;
3439

3540
// Register plugins here.
3641
static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
37-
vec![
42+
let mut plugins: Vec<Box<DynPlugin>> = vec![
3843
Box::<plugin_curl::CurlPlugin>::default(),
3944
Box::<plugin_pdo::PdoPlugin>::default(),
4045
Box::<plugin_mysqli::MySQLImprovedPlugin>::default(),
@@ -46,7 +51,11 @@ static PLUGINS: Lazy<Vec<Box<DynPlugin>>> = Lazy::new(|| {
4651
Box::<plugin_amqplib::AmqplibPlugin>::default(),
4752
Box::<plugin_mongodb::MongodbPlugin>::default(),
4853
Box::<plugin_memcache::MemcachePlugin>::default(),
49-
]
54+
];
55+
if *PSR_LOGGING_LEVEL > PsrLogLevel::Off {
56+
plugins.push(Box::<plugin_psr3::Psr3Plugin>::default());
57+
}
58+
plugins
5059
});
5160

5261
pub type DynPlugin = dyn Plugin + Send + Sync + 'static;
@@ -56,6 +65,10 @@ pub trait Plugin {
5665

5766
fn function_name_prefix(&self) -> Option<&'static str>;
5867

68+
fn parent_classes(&self) -> Option<Vec<Option<&'static ClassEntry>>> {
69+
None
70+
}
71+
5972
fn hook(
6073
&self, class_name: Option<&str>, function_name: &str,
6174
) -> Option<(Box<BeforeExecuteHook>, Box<AfterExecuteHook>)>;
@@ -92,19 +105,30 @@ pub fn select_plugin_hook(
92105
fn select_plugin(class_name: Option<&str>, function_name: &str) -> Option<&'static DynPlugin> {
93106
let mut selected_plugin = None;
94107

95-
for plugin in &*PLUGINS {
108+
'plugin: for plugin in &*PLUGINS {
96109
if let Some(class_name) = class_name {
97110
if let Some(plugin_class_names) = plugin.class_names() {
98111
if plugin_class_names.contains(&class_name) {
99112
selected_plugin = Some(plugin);
100-
break;
113+
break 'plugin;
114+
}
115+
}
116+
if let Some(parent_classes) = plugin.parent_classes() {
117+
if let Ok(class) = ClassEntry::from_globals(class_name) {
118+
// Iterate parent_classes and skip None.
119+
for parent_class in parent_classes.into_iter().flatten() {
120+
if class.is_instance_of(parent_class) {
121+
selected_plugin = Some(plugin);
122+
break 'plugin;
123+
}
124+
}
101125
}
102126
}
103127
}
104128
if let Some(function_name_prefix) = plugin.function_name_prefix() {
105129
if function_name.starts_with(function_name_prefix) {
106130
selected_plugin = Some(plugin);
107-
break;
131+
break 'plugin;
108132
}
109133
}
110134
}

0 commit comments

Comments
 (0)