-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add configurable logging system #407
base: main
Are you sure you want to change the base?
Add configurable logging system #407
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, thank you for looking into this! This already looks pretty good, but I have some comments after the first round of review.
@@ -28,13 +28,11 @@ class AndroidLibTest { | |||
config1.storageDirPath = tmpDir1 | |||
config1.listeningAddresses = listOf(listenAddress1) | |||
config1.network = Network.REGTEST | |||
config1.logLevel = LogLevel.TRACE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we still set TRACE level, just by the new means?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we should. I created a new CustomLogWriter
class via which we can set the log level and also test LogWriter
. Ran into some issues with NoPointer
/Pointer.NULL
when testing this (due to unfamiliarity with Kotlin) and would appreciate any pointers to resolve.
src/logger.rs
Outdated
pub(crate) enum Writer { | ||
/// Writes logs to the file system. | ||
FileWriter(FilesystemLogger), | ||
/// Forwards logs to the `log` facade. | ||
LogFacadeWriter(LogFacadeLogger), | ||
/// Forwards logs to custom writer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/custom writer/a custom writer/
tests/common/mod.rs
Outdated
} | ||
|
||
/// Simple in-memory mock `log` logger for tests. | ||
#[derive(Debug)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this Debug
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NodeBuilder
is Debug
, and it fields an optional LogWriterConfig
. The config object has to be Debug
and custom log writer variant needs to implement it for this reason.
tests/common/mod.rs
Outdated
@@ -271,9 +274,18 @@ impl MockLogger { | |||
} | |||
} | |||
|
|||
/// [`MockLogger`] as `log` logger - destination for [`Writer::LogFacadeWriter`] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to belong in a different commit? Also, let's not add documents on trait implementations (internal ones in particular).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted. I thought the documentation added clarity given that the same MockLogger object was being used as a custom logger and a log logger.
I think the MockLogger belongs to the right commit, as I have been careful about what goes into a commit. However, I may have missed this and will be doubly-careful going forward.
tests/integration_tests_rust.rs
Outdated
@@ -828,13 +831,16 @@ fn generate_bip21_uri() { | |||
}, | |||
Err(e) => panic!("Failed to generate URI: {:?}", e), | |||
} | |||
|
|||
assert!(mock_logger.retrieve_logs().last().unwrap().contains("Invoice created: lnbcrt")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above: please avoid such potentially-flaky assert
s. Also, we should really be testing the log facade separately instead of sprinkling in these assert
s to unrelated testcases.
tests/integration_tests_rust.rs
Outdated
} | ||
|
||
#[test] | ||
fn unified_qr_send_receive() { | ||
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); | ||
let chain_source = TestChainSource::Esplora(&electrsd); | ||
|
||
// Setup `log` facade logger. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same goes for arbitrary comments: please only add comments where they add to the context. Comments like this don't provide additional information over just reading the next line, they just increase the (visual) noise when reading code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted.
@enigbe Is there any update on this? Please let me know if you're hitting any blockers. This seems to need a minor rebase by now. |
No blockers on this. I'll be pushing updates later today. |
* Add flexible log writer interface for multiple destinations * Implement filesystem writing capability via FilesystemLogger * Prefix LDK-based objects with 'Ldk' for consistency * Add configuration options for log file path and log level
* Add support for user-provided custom logger to write logs to, allowing users to provide any logger that implements LogWriter * Add test to cover this use case, implementing Log- Writer for the mock, in-memory MockLogger.
1d57cab
to
d8eb0e1
Compare
This commit includes several improvements aimed at simplifying code, improving readability, and optimizing performance: - Inline enum struct fields to reduce indirection and clutter. - Rename structs to improve clarity and better reflect their purpose. - Enable specific feature flags in dependencies for a more streamlined build. - Eliminate unnecessary data allocations to optimize memory usage. - Correct minor grammatical error in documentation.
- allocates Strings only when necessary with `uniffi` feature, otherwise, keeps LogRecord fileds as lifetime references.
26aff81
to
7fa2928
Compare
This commit adds a CustomLogWriter class to the kotlin library test, configuring the writer via the exposed node builder and tests the ability to log to the custom writer destination.
…ehavior - Removed trait name in description. - Expanded the description of the `LogWriter` trait to clarify its behavior when the `uniffi` feature is enabled or disabled. - Improved explanation of the trait’s responsibilities, including handling log messages and forwarding to outputs.
7fa2928
to
283fe85
Compare
- Add a custom Debug implementation for LogWriterConfig to work around NodeBuilder's Debug constraints. - Remove the Debug trait from LogWriter, as it is no longer needed due to the custom Debug impl on LogWriterConfig. - Remove the Debug implementation from Writer for consistency.
- Revert MockLogger configuration and assertions - Remove clutter from setting up LogWriter across all tests
@enigbe Please let me know if/when this is ready for the next round of review! |
dcf757d
to
0660dfa
Compare
0660dfa
to
6766c60
Compare
- Remove remnants of TestLogWriter and MockLogger that will be reintroduced in a later PR to add a more structured TestConfig object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tnull I believe this is ready for another review. I have addressed the majority of the concerns you raised in the first pass.
Regarding your concerns about testing the logging to custom loggers, I agree that the necessary refactor would be extensive and could detract from the purpose of this PR. As such, I plan to address these test-related changes in a follow-up PR.
Additionally, I encountered some challenges testing a custom logger in Kotlin and would greatly appreciate your guidance or suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did another round of review, already looks pretty good.
I took a quick look at the Kotlin failures, but also couldn't immediately spot what's up, let me know if you want me to have a closer look though.
Btw, you could consider rebasing on #426 (or on main after it lands) which generally fixes pre-existing CI failures.
@@ -563,6 +569,18 @@ dictionary NodeAnnouncementInfo { | |||
sequence<SocketAddress> addresses; | |||
}; | |||
|
|||
dictionary LogRecord { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Since we're (AFAIU?) moving LogLevel
up to group with FilesystemLoggerConfig
, we should probably do the same with these logging related items.
} | ||
} | ||
|
||
pub(crate) fn default_log_file_path() -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just use const
s for these static default values, no need to add methods on top of it.
self | ||
} | ||
|
||
/// Configures the [`Node`] instance to write logs to the `log` facade. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you point the user to the log
crate for more information here?
@@ -391,7 +428,8 @@ impl NodeBuilder { | |||
) -> Result<Node, BuildError> { | |||
use bitcoin::key::Secp256k1; | |||
|
|||
let logger = setup_logger(&self.config)?; | |||
let log_writer_config = self.log_writer_config.clone().unwrap_or_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mhh, can we avoid these clone
s here? Possibly by having setup_logger
taking the whole &Option<LogWriterConfig>
and implementing the defaulting behavior once there, which probably would be preferable over doing it in several places anyhow?
let log_file_path = if let Some(fp) = &fs_logger_config.log_file_path { | ||
fp | ||
} else { | ||
&default_log_file_path() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, let's just use the const
s directly here.
/// Forwards logs to the `log` facade. | ||
LogFacadeWriter { level: LogLevel }, | ||
/// Forwards logs to a custom writer. | ||
CustomWriter(Arc<dyn LogWriter + Send + Sync>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might be able to drop the explicit + Send + Sync
here if we already have LogWriter
inherit them above?
|
||
impl LogWriter for Writer { | ||
fn log(&self, record: LogRecord) { | ||
let log = format!( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so format!
allocates a string on the heap. As we try to avoid allocations (to reduce heap fragmentation where possible), can we a) only create this string when we're sure we need it to log (i.e, after passing the log level filtering), and b) can we avoid it altogether for the CustomWriter
case, and possibly even the LogFacadeWriter
case? In the latter case we should be able to give the arguments to the respective macros directly, no?
Ok(Self { writer: Writer::FileWriter { file_path: file_path.to_string(), level } }) | ||
} | ||
|
||
pub fn new_log_facade(level: LogLevel) -> Result<Self, ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems new_log_facade
and new_custom_writer
are infallible, so no reason to have them return a Result
.
if let Some(parent_dir) = Path::new(&log_file_path).parent() { | ||
pub(crate) struct Logger { | ||
/// Specifies the logger's writer. | ||
writer: Writer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Logger
only has this one field, why not have it be the enum
directly? I.e., rename Writer
to Logger
and implement what's needed directly on there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a concern about this approach. I have tried it and realized that if we use the Writer
(renamed to Logger
) directly, we will run into a problem where the compiler will not be able to decide between traits LdkLogger::log(...)
and LogWriter::log(...)
for any log_*!(...)
. An easy work-around would be to rename the LogWriter
's log
to write
but we decided against that a while back. How would you suggest we address this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't need to bother implementing the custom logger on both Android and JVM. Let's pick one, likely JVM as we already do most tests there?
Overview
This PR introduces a flexible logging system for LDK Node by implementing a
LogWriter
interface that supports writing logs to different destinations.What this PR does
LogWriter
interface, allowingWriter
variants to handle log output destinations. The supportedWriter
variants can now:LogWriter
to bindings.log
logger,LogWriter
logger.Related Issue(s)
logger
interface #309