feat(logging): add enhanced logging with colors and flexible formatting

- Add BEAST_ENHANCED_LOGGING CMake option for build-time control
- Implement file:line logging with customizable position (prefix/suffix/none)
  via LOG_LOCATION_POSITION env var (defaults to suffix for readability)
- Add flexible timestamp formatting via LOG_DATE_FORMAT (date::format syntax)
- Add local timezone support via LOG_DATE_LOCAL env var
- Support customizable highlight colors via LOG_HIGHLIGHT_ESCAPE env var
  (red, green, yellow, blue, magenta, cyan, white, gray, orange, none)
- Apply consistent highlighting to both file:line and partition:severity
- Centralize all enhanced logging features in beast_EnhancedLogging module
- Smart TTY detection with NO_COLOR/FORCE_COLOR support
- Constexpr path stripping using SOURCE_ROOT_PATH
- Move implementations to .cpp files to minimize recompilation
- Update zlib to 1.3.1 to fix macOS 15.5 build issues
- Enable date-tz library for timezone support

Example output with defaults:
  2025-Jul-26 15:20:07.124745 NetworkOPs:NFO STATE->connected [ripple/app/misc/NetworkOPs.cpp:2264]

Environment variables:
  LOG_LOCATION_POSITION=suffix  # or prefix, none
  LOG_HIGHLIGHT_ESCAPE=cyan     # or red, green, yellow, etc
  LOG_DATE_FORMAT=%Y-%b-%d %r.%f %Z  # custom timestamp format
  LOG_DATE_LOCAL=1              # use local timezone

Zero runtime cost when disabled - all features are build-time conditional.
Backward compatible - no changes when BEAST_ENHANCED_LOGGING is not defined.
This commit is contained in:
Nicholas Dudfield
2025-07-26 16:49:24 +07:00
parent a3ef83d970
commit 1e698ed194
9 changed files with 257 additions and 180 deletions

View File

@@ -50,6 +50,12 @@ target_sources (xrpl_core PRIVATE
src/ripple/beast/utility/src/beast_Journal.cpp
src/ripple/beast/utility/src/beast_PropertyStream.cpp)
# Conditionally add enhanced logging source when BEAST_ENHANCED_LOGGING is enabled
if(DEFINED BEAST_ENHANCED_LOGGING AND BEAST_ENHANCED_LOGGING)
target_sources(xrpl_core PRIVATE
src/ripple/beast/utility/src/beast_EnhancedLogging.cpp)
endif()
#[===============================[
core sources
#]===============================]

View File

@@ -37,15 +37,15 @@ endif() #git
set(SOURCE_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/")
add_definitions(-DSOURCE_ROOT_PATH="${SOURCE_ROOT_PATH}")
# LOG_LINE_NUMBERS option - default to ON for Debug builds, OFF for Release
# BEAST_ENHANCED_LOGGING option - default to ON for Debug builds, OFF for Release
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
option(LOG_LINE_NUMBERS "Include file and line numbers in log messages" ON)
option(BEAST_ENHANCED_LOGGING "Include file and line numbers in log messages" ON)
else()
option(LOG_LINE_NUMBERS "Include file and line numbers in log messages" OFF)
option(BEAST_ENHANCED_LOGGING "Include file and line numbers in log messages" OFF)
endif()
if(LOG_LINE_NUMBERS)
add_definitions(-DLOG_LINE_NUMBERS=1)
if(BEAST_ENHANCED_LOGGING)
add_definitions(-DBEAST_ENHANCED_LOGGING=1)
message(STATUS "Log line numbers enabled")
else()
message(STATUS "Log line numbers disabled")

View File

@@ -249,7 +249,7 @@ private:
// Wraps a Journal::Stream to skip evaluation of
// expensive argument lists if the stream is not active.
#ifndef JLOG
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
#define JLOG(x) \
if (!(x)) \
{ \

View File

@@ -23,6 +23,7 @@
#include <ripple/basics/Log.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/contract.h>
#include <ripple/beast/utility/EnhancedLogging.h>
#include <boost/algorithm/string.hpp>
#include <cassert>
#include <cstring>
@@ -321,7 +322,7 @@ Logs::format(
{
output.reserve(message.size() + partition.size() + 100);
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
static const char* fmt = []() {
const char* env = std::getenv("LOG_DATE_FORMAT");
return env ? env : "%Y-%b-%d %T %Z"; // Default format
@@ -351,7 +352,12 @@ Logs::format(
output += " ";
if (!partition.empty())
{
#ifdef BEAST_ENHANCED_LOGGING
output += beast::detail::get_log_highlight_escape();
#endif
output += partition + ":";
}
using namespace beast::severities;
switch (severity)
@@ -379,6 +385,10 @@ Logs::format(
break;
}
#ifdef BEAST_ENHANCED_LOGGING
output += "\033[0m";
#endif
output += message;
// Limit the maximum length of the output

View File

@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef BEAST_UTILITY_ENHANCEDLOGGING_H_INCLUDED
#define BEAST_UTILITY_ENHANCEDLOGGING_H_INCLUDED
#include <cstddef> // for size_t
#include <iosfwd> // for std::ostream
namespace beast {
namespace detail {
// Check if we should use colors - cached at startup
bool
should_log_use_colors();
// Get the log location escape sequence - can be overridden via
// LOG_LOCATION_ESCAPE
const char*
get_log_highlight_escape();
// Strip source root path from __FILE__ at compile time
// IMPORTANT: This MUST stay in the header as constexpr for compile-time
// evaluation!
constexpr const char*
stripSourceRoot(const char* file)
{
#ifdef SOURCE_ROOT_PATH
constexpr const char* sourceRoot = SOURCE_ROOT_PATH;
constexpr auto strlen_constexpr = [](const char* s) constexpr
{
const char* p = s;
while (*p)
++p;
return p - s;
};
constexpr auto strncmp_constexpr =
[](const char* a, const char* b, size_t n) constexpr
{
for (size_t i = 0; i < n; ++i)
{
if (a[i] != b[i])
return a[i] - b[i];
if (a[i] == '\0')
break;
}
return 0;
};
constexpr size_t sourceRootLen = strlen_constexpr(sourceRoot);
return (strncmp_constexpr(file, sourceRoot, sourceRootLen) == 0)
? file + sourceRootLen
: file;
#else
return file;
#endif
}
// Location position enum
enum class LocationPosition { PREFIX, SUFFIX, NONE };
// Get configured position - cached at startup
LocationPosition
get_log_location_position();
// Helper to write location string (no leading/trailing space)
void
log_write_location_string(std::ostream& os, const char* file, int line);
} // namespace detail
} // namespace beast
#endif

View File

@@ -146,7 +146,7 @@ private:
ScopedStream(Sink& sink, Severity level);
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
ScopedStream(Sink& sink, Severity level, const char* file, int line);
#endif
@@ -177,7 +177,7 @@ private:
Sink& m_sink;
Severity const m_level;
std::ostringstream mutable m_ostream;
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
const char* file_ = nullptr;
int line_ = 0;
#endif
@@ -200,7 +200,7 @@ private:
public:
/** Provide a light-weight way to check active() before string formatting */
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
/** Stream with location information that prepends file:line to the first
* message */
class StreamWithLocation
@@ -290,7 +290,7 @@ public:
operator<<(T const& t) const;
/** @} */
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
/** Create a StreamWithLocation that prepends file:line info */
StreamWithLocation
withLocation(const char* file, int line) const
@@ -398,54 +398,6 @@ static_assert(std::is_nothrow_destructible<Journal>::value == true, "");
//------------------------------------------------------------------------------
#ifdef LOG_LINE_NUMBERS
namespace detail {
// Helper to strip source root path from __FILE__ at compile time
// IMPORTANT: This MUST stay in the header as constexpr for compile-time
// evaluation!
constexpr const char*
stripSourceRoot(const char* file)
{
#ifdef SOURCE_ROOT_PATH
constexpr const char* sourceRoot = SOURCE_ROOT_PATH;
constexpr auto strlen_constexpr = [](const char* s) constexpr
{
const char* p = s;
while (*p)
++p;
return p - s;
};
constexpr auto strncmp_constexpr =
[](const char* a, const char* b, size_t n) constexpr
{
for (size_t i = 0; i < n; ++i)
{
if (a[i] != b[i])
return a[i] - b[i];
if (a[i] == '\0')
break;
}
return 0;
};
constexpr size_t sourceRootLen = strlen_constexpr(sourceRoot);
return (strncmp_constexpr(file, sourceRoot, sourceRootLen) == 0)
? file + sourceRootLen
: file;
#else
return file;
#endif
}
// Check if we should use colors - cached at startup
bool
shouldUseColors();
// Get the location escape sequence - can be overridden via LOG_LOCATION_ESCAPE
const char*
getLocationEscape();
} // namespace detail
#endif
//------------------------------------------------------------------------------
template <typename T>
@@ -472,7 +424,7 @@ Journal::Stream::operator<<(T const& t) const
return ScopedStream(*this, t);
}
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
//------------------------------------------------------------------------------
template <typename T>

View File

@@ -0,0 +1,124 @@
//------------------------------------------------------------------------------
/*
This file is part of Beast: https://github.com/vinniefalco/Beast
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/beast/utility/EnhancedLogging.h>
#include <cstdlib>
#include <cstring>
#include <ostream>
#include <unistd.h>
namespace beast {
namespace detail {
// Check if we should use colors - cached at startup
bool
should_log_use_colors()
{
static const bool use_colors = []() {
// Honor NO_COLOR environment variable (standard)
if (std::getenv("NO_COLOR"))
return false;
// Honor FORCE_COLOR to override terminal detection
if (std::getenv("FORCE_COLOR"))
return true;
// Check if stderr is a terminal
return isatty(STDERR_FILENO) != 0;
}();
return use_colors;
}
// Get the log location escape sequence - can be overridden via
// LOG_LOCATION_ESCAPE
const char*
get_log_highlight_escape()
{
static const char* escape = []() {
const char* env = std::getenv("LOG_HIGHLIGHT_ESCAPE");
if (!env)
return "\033[36m"; // Default: cyan
// Simple map of color names to escape sequences
if (std::strcmp(env, "red") == 0)
return "\033[31m";
if (std::strcmp(env, "green") == 0)
return "\033[32m";
if (std::strcmp(env, "yellow") == 0)
return "\033[33m";
if (std::strcmp(env, "blue") == 0)
return "\033[34m";
if (std::strcmp(env, "magenta") == 0)
return "\033[35m";
if (std::strcmp(env, "cyan") == 0)
return "\033[36m";
if (std::strcmp(env, "white") == 0)
return "\033[37m";
if (std::strcmp(env, "gray") == 0 || std::strcmp(env, "grey") == 0)
return "\033[90m"; // Bright black (gray)
if (std::strcmp(env, "orange") == 0)
return "\033[93m"; // Bright yellow (appears orange-ish)
if (std::strcmp(env, "none") == 0)
return "";
// Default to cyan if unknown color name
return "\033[36m";
}();
return escape;
}
// Get configured position - cached at startup
LocationPosition
get_log_location_position()
{
static const LocationPosition position = []() {
const char* env = std::getenv("LOG_LOCATION_POSITION");
if (!env)
return LocationPosition::SUFFIX; // Default to suffix for better
// readability
if (std::strcmp(env, "suffix") == 0 || std::strcmp(env, "end") == 0)
return LocationPosition::SUFFIX;
if (std::strcmp(env, "prefix") == 0 || std::strcmp(env, "start") == 0)
return LocationPosition::PREFIX;
if (std::strcmp(env, "none") == 0)
return LocationPosition::NONE;
return LocationPosition::PREFIX;
}();
return position;
}
// Helper to write location string (no leading/trailing space)
void
log_write_location_string(std::ostream& os, const char* file, int line)
{
if (detail::should_log_use_colors())
{
os << detail::get_log_highlight_escape() << "["
<< detail::stripSourceRoot(file) << ":" << line << "]\033[0m";
}
else
{
os << "[" << detail::stripSourceRoot(file) << ":" << line << "]";
}
}
} // namespace detail
} // namespace beast

View File

@@ -19,10 +19,10 @@
#include <ripple/beast/utility/Journal.h>
#include <cassert>
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
#include <ripple/beast/utility/EnhancedLogging.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#endif
namespace beast {
@@ -136,112 +136,7 @@ Journal::ScopedStream::ScopedStream(
m_ostream << manip;
}
#ifdef LOG_LINE_NUMBERS
//------------------------------------------------------------------------------
namespace detail {
// Location position enum
enum class LocationPosition { PREFIX, SUFFIX, NONE };
// Get configured position - cached at startup
LocationPosition
getLocationPosition()
{
static const LocationPosition position = []() {
const char* env = std::getenv("LOG_LOCATION_POSITION");
if (!env)
return LocationPosition::SUFFIX; // Default to suffix for better
// readability
if (std::strcmp(env, "suffix") == 0 || std::strcmp(env, "end") == 0)
return LocationPosition::SUFFIX;
if (std::strcmp(env, "prefix") == 0 || std::strcmp(env, "start") == 0)
return LocationPosition::PREFIX;
if (std::strcmp(env, "none") == 0)
return LocationPosition::NONE;
return LocationPosition::PREFIX;
}();
return position;
}
// Helper to write location string (no leading/trailing space)
void
writeLocationString(std::ostream& os, const char* file, int line)
{
if (detail::shouldUseColors())
{
os << detail::getLocationEscape() << "["
<< detail::stripSourceRoot(file) << ":" << line << "]\033[0m";
}
else
{
os << "[" << detail::stripSourceRoot(file) << ":" << line << "]";
}
}
// Check if we should use colors - cached at startup
bool
shouldUseColors()
{
static const bool useColors = []() {
// Honor NO_COLOR environment variable (standard)
if (std::getenv("NO_COLOR"))
return false;
// Honor FORCE_COLOR to override terminal detection
if (std::getenv("FORCE_COLOR"))
return true;
// Check if stderr is a terminal
return isatty(STDERR_FILENO) != 0;
}();
return useColors;
}
// Get the location escape sequence - can be overridden via LOG_LOCATION_ESCAPE
const char*
getLocationEscape()
{
static const char* escape = []() {
const char* env = std::getenv("LOG_LOCATION_ESCAPE");
if (!env)
return "\033[36m"; // Default: cyan
// Simple map of color names to escape sequences
if (std::strcmp(env, "red") == 0)
return "\033[31m";
if (std::strcmp(env, "green") == 0)
return "\033[32m";
if (std::strcmp(env, "yellow") == 0)
return "\033[33m";
if (std::strcmp(env, "blue") == 0)
return "\033[34m";
if (std::strcmp(env, "magenta") == 0)
return "\033[35m";
if (std::strcmp(env, "cyan") == 0)
return "\033[36m";
if (std::strcmp(env, "white") == 0)
return "\033[37m";
if (std::strcmp(env, "gray") == 0 || std::strcmp(env, "grey") == 0)
return "\033[90m"; // Bright black (gray)
if (std::strcmp(env, "orange") == 0)
return "\033[93m"; // Bright yellow (appears orange-ish)
if (std::strcmp(env, "none") == 0)
return "";
// Default to cyan if unknown color name
return "\033[36m";
}();
return escape;
}
} // namespace detail
#endif
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
Journal::ScopedStream::ScopedStream(
Sink& sink,
Severity level,
@@ -254,9 +149,9 @@ Journal::ScopedStream::ScopedStream(
// Write prefix if configured
if (file_ &&
detail::getLocationPosition() == detail::LocationPosition::PREFIX)
detail::get_log_location_position() == detail::LocationPosition::PREFIX)
{
detail::writeLocationString(m_ostream, file_, line_);
detail::log_write_location_string(m_ostream, file_, line_);
m_ostream << " ";
}
}
@@ -266,17 +161,18 @@ Journal::ScopedStream::~ScopedStream()
{
std::string s(m_ostream.str());
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
// Add suffix if configured
if (file_ &&
detail::getLocationPosition() == detail::LocationPosition::SUFFIX &&
detail::get_log_location_position() ==
detail::LocationPosition::SUFFIX &&
!s.empty() && s != "\n")
{
std::ostringstream combined;
combined << s;
if (!s.empty() && s.back() != ' ')
combined << " ";
detail::writeLocationString(combined, file_, line_);
detail::log_write_location_string(combined, file_, line_);
s = combined.str();
}
#endif
@@ -304,7 +200,7 @@ Journal::Stream::operator<<(std::ostream& manip(std::ostream&)) const
return ScopedStream(*this, manip);
}
#ifdef LOG_LINE_NUMBERS
#ifdef BEAST_ENHANCED_LOGGING
// Implementation moved to use new constructor
Journal::ScopedStream

View File

@@ -91,20 +91,21 @@ class Invariants_test : public beast::unit_test::suite
{
terActual = ac.checkInvariants(terActual, fee);
BEAST_EXPECT(terExpect == terActual);
// Handle both with and without LOG_LINE_NUMBERS
// Handle both with and without BEAST_ENHANCED_LOGGING
auto const msg = sink.messages().str();
bool hasExpectedPrefix = false;
#ifdef LOG_LINE_NUMBERS
// When LOG_LINE_NUMBERS is enabled, messages may include ANSI color
// codes and start with [file:line]. Just search for the message
// content.
#ifdef BEAST_ENHANCED_LOGGING
// When BEAST_ENHANCED_LOGGING is enabled, messages may include ANSI
// color codes and start with [file:line]. Just search for the
// message content.
hasExpectedPrefix =
msg.find("Invariant failed:") != std::string::npos ||
msg.find("Transaction caused an exception") !=
std::string::npos;
#else
// Without LOG_LINE_NUMBERS, messages start directly with the text
// Without BEAST_ENHANCED_LOGGING, messages start directly with the
// text
hasExpectedPrefix = msg.starts_with("Invariant failed:") ||
msg.starts_with("Transaction caused an exception");
#endif