cln-plugin: add support for structured logging dependencies
What changed, and why it matters
This commit changes how log messages are collected from Rust tracing events in the cln-plugin library. Previously, only the main message text was captured. Now, extra structured fields attached by logging dependencies are appended to the message, while internal metadata fields like file and line number are filtered out. This is a feature enhancement to improve log readability and avoid duplicate metadata, not a security fix.
No security action required. Review as normal code-quality/feature change.
Security signals we found
No security-relevant code paths modified
No input parsing or deserialization changes
No cryptographic, authentication, or authorization changes
No memory safety changes
No changelog security note
Evidence from the diff
The patch modifies plugins/src/logging.rs in Core Lightning’s cln-plugin crate. The LogExtract visitor now records all non-metadata tracing fields into a Vec<(String, String)> and formats them as key=”value” pairs appended to the log message. A new is_log_metadata_field helper excludes log.target, log.module_path, log.file, and log.line. The change is additive and does not alter authentication, authorization, cryptography, network parsing, or memory-unsafe code paths.
Changed components
plugins/src/logging.rscln-plugin tracing-to-log bridgeInspect captured patch +27 / −4
diff --git a/plugins/src/logging.rs b/plugins/src/logging.rs
index 02e5df44..db784e29 100644
--- a/plugins/src/logging.rs
+++ b/plugins/src/logging.rs
@@ -116,10 +116,21 @@ mod trace {
) {
let mut extractor = LogExtract::default();
event.record(&mut extractor);
- let message = match extractor.msg {
+ let mut message = match extractor.msg {
Some(m) => m,
None => return,
};
+
+ // Append any additional fields to the message
+ if !extractor.fields.is_empty() {
+ let fields_str: Vec<String> = extractor
+ .fields
+ .iter()
+ .map(|(k, v)| format!("{}=\"{}\"", k, v))
+ .collect();
+ message = format!("{} [{}]", message, fields_str.join(", "));
+ }
+
let level = event.metadata().level().into();
self.sender.send(LogEntry { level, message }).unwrap();
}
@@ -141,14 +152,26 @@ mod trace {
#[derive(Default)]
struct LogExtract {
msg: Option<String>,
+ fields: Vec<(String, String)>,
}
impl tracing::field::Visit for LogExtract {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
- if field.name() != "message" {
- return;
+ let field_name = field.name();
+ let field_value = format!("{value:?}");
+
+ if field_name == "message" {
+ self.msg = Some(field_value);
+ } else if !is_log_metadata_field(field_name) {
+ self.fields.push((field_name.to_owned(), field_value));
}
- self.msg = Some(format!("{:?}", value));
}
}
+
+ fn is_log_metadata_field(name: &str) -> bool {
+ matches!(
+ name,
+ "log.target" | "log.module_path" | "log.file" | "log.line"
+ )
+ }
}
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.