Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Logging overhaul #4375

Merged

Conversation

pwojcikdev
Copy link
Contributor

@pwojcikdev pwojcikdev commented Jan 19, 2024

This pull request improves the logging functionality of the codebase. The new logger class aims to simplify the logging process and make it more consistent. The new logger class is using spdlog library under the hood and fmt library for formatting, with format string syntax compatible with C++20 std::format.

Out of the box, the interface presented to node administrators should be clearer and more intuitive. Each log line is categorized by node component with color coding for different log levels. The output can be configured to be written to a file, with automatic rotation and size limit. The default verbosity when running node in daemon mode is info.

Screenshot 2024-01-19 at 14 13 55

Configuration

There are two ways to configure the logger. The first is to use config-log.toml file, which is located in the data directory alongside other configuration files. The second is to use NANO_LOG and NANO_LOG_LEVELS environment variables. The environment variables take precedence over the configuration file.

Configuration File config-log.toml

During normal node operation, the configuration file is loaded from the data directory. When running testsuites, the configuration is loaded from current working directory.

[log]
#default_level = "info"

[log.console]
#colors = true
#enable = true
#to_cerr = false

[log.file]
#enable = true
#max_size = 33554432
#rotation_count = 4

[log.levels]
#active_transactions = "info"
#all = "info"
#blockprocessor = "info"
#bootstrap = "info"
#bootstrap_lazy = "info"
#...

Environment Variables

Envionment variables override the configuration file. This is useful when running testsuites.

# Set default log level
NANO_LOG = [trace|debug|info|warn|error|critical|off]

# Set log level for individual loggers
NANO_LOG_LEVELS = logger_1=level_1[,logger_2=level_2,...]

export NANO_LOG=warn
export NANO_LOG_LEVELS=active_transactions=debug,bootstrap=debug

Unit tests

By default, the logger is disabled when running unit tests to keep the output clean. To enable it, set NANO_LOG environment variable to a desired log level.
In testsuite mode, each logline additionally contains the identifier of the node that produced the logline (first 10 characters of its node ID). This makes it easier to follow the flow of events and will become much more useful once full tracing is implemented.

Screenshot 2024-01-19 at 14 15 00

Future Work

Originaly, the plan was to include both logging and tracing functionality in this pull request. However, since the amount of changes is already quite large, I decided to split the work into two pull requests. There might be some previous logging calls that were not converted to the new logger class and those should be converted into tracing calls.

I also plan to allow log calls to be divided into different categories, so that the node administrators can configure the verbosity of each category that can span multiple components.

@qwahzi qwahzi mentioned this pull request Jan 21, 2024
std::cout << initialization_text << std::endl;
logger.always_log (initialization_text);
// This avoids a blank prompt during any node initialization delays
logger.info (nano::log::type::daemon, "Starting up Nano node...");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep these startup messages as std::cout to make sure the initialization process is shown in the console even when configured for file logging

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe if someone explicitly wants to disable console output for whatever reason then we should not print anything

@qwahzi qwahzi added this to the V27 milestone Jan 21, 2024
clemahieu
clemahieu previously approved these changes Jan 21, 2024
Copy link
Contributor

@clemahieu clemahieu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are mostly minor or subtle behaviour changes I noticed that don't need changing.

I was wondering about the performance since previously we short-circuited log message construction with a boolean check before constructing the actual message. I see that the spdlog library implementation forwards all of the log message arguments and checks if that particular logger is enabled before constructing the log message so I don't see this as a problem.

Very nice overhaul.

};

// First try to load config from the current working directory, then from the node data directory
if (auto toml = try_load_toml (config_filename); toml)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "; toml" isn't needed as the assignment expression will yield the assigned value

nano/lib/logging.cpp Show resolved Hide resolved
nano/nano_node/daemon.cpp Show resolved Hide resolved
nano/node/bootstrap/bootstrap_bulk_pull.cpp Show resolved Hide resolved
nano/node/transport/socket.cpp Show resolved Hide resolved
nano/node/node.cpp Show resolved Hide resolved
nano/lib/logging.cpp Show resolved Hide resolved
@pwojcikdev pwojcikdev added documentation This item indicates the need for or supplies updated or expanded documentation configuration default change Change to default configuration values labels Jan 22, 2024
@pwojcikdev pwojcikdev merged commit b225de0 into nanocurrency:develop Jan 24, 2024
16 of 19 checks passed
@@ -404,4 +408,28 @@ bool is_sanitizer_build ();

/** Set the active network to the dev network */
void force_nano_dev_network ();

/**
* Attempt to read a configuration file from specified directory. Returns empty tomlconfig if nothing is found.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it returns a file containing the overrides, if overrides are specified.


enum class type
{
all = 0, // reserved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment to explain what happens when someone sets the "all" log type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It currently functions as every other logger type. The all has a special meaning for details where it allows to specify verbosity of type::detail logger separately from that for the whole type. It's currently used for tracing to limit the amount of unnecessary noise.

bootstrap_legacy,
};

enum class detail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need an example of how a detail would be used.

else
{
auto logger_type = logger_name.substr (0, pos);
auto logger_detail = logger_name.substr (pos + 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUG: it should be: pos + 2

}
catch (std::runtime_error const & ex)
{
std::cerr << "Unable to load log config. Using defaults. Error: " << ex.what () << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors with config-log.toml are generally ignored and the program simply prints a message on seder and continues. In order to achieve that, we have extra try/catch blocks than would be necessary if we simply said that any log config errors will cause the program terminate. Why not follow the simplest approach of terminating the program if the log config has a problem?

Copy link
Contributor Author

@pwojcikdev pwojcikdev Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log types and details change often during development and experimentation phase. Terminating when any error is encountered is just a bad user experience. And logging is not critical to proper node function.

@@ -171,6 +171,19 @@ class tomlconfig : public nano::configbase
return *this;
}

template <typename T>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a comment explaining what this functions does.
For example, what happens to non-string entries in the log file?

nano/lib/utility.hpp Show resolved Hide resolved
nano/lib/utility.hpp Show resolved Hide resolved
nano/node/blockprocessor.cpp Show resolved Hide resolved
@@ -60,15 +63,6 @@ void nano::remove_temporary_directories ()
{
std::cerr << "Could not remove temporary directory: " << ec.message () << std::endl;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a serious change of behaviour and not a logging change.
I personally agree with the change but it should not had been part of the logging PR because this affects node operators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain how this affects node operators? I only saw this function used in unit testing. Generally there was badly implemented code, fixing badly implemented code that culminated in some strange bugs when I needed to ensure orderly node initialization.

@@ -25,13 +26,18 @@

namespace
{
nano::logger logger{ "wallet_daemon" };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in #4679 this runs before main and breaks the assertion

nano::logger::logger (std::string identifier) :
  identifier{ std::move (identifier) }
{
  release_assert (global_initialized, "logging should be initialized before creating a logger");
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'm surprised this works on develop. I will look into it, thanks!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
configuration default change Change to default configuration values documentation This item indicates the need for or supplies updated or expanded documentation
Projects
Status: Merged / V27.0
Development

Successfully merging this pull request may close these issues.

6 participants