feat(logging): add flexible log formatting and positioning options

- Add LOG_LOCATION_POSITION env var to control file:line placement (prefix/suffix/none)
- Add LOG_DATE_FORMAT env var for custom timestamp formats (supports date::format syntax)
- Add LOG_DATE_LOCAL env var to use local timezone instead of UTC
- Add LOG_LOCATION_ESCAPE env var for custom colors (red/green/cyan/etc)
- Default to suffix position for better readability
- Move implementation to .cpp files to reduce recompilation
- Update zlib to 1.3.1 to fix macOS build issues
- Enable date-tz library for timezone support
This commit is contained in:
Nicholas Dudfield
2025-07-26 16:00:25 +07:00
parent 797a5d4c5b
commit a3ef83d970
5 changed files with 153 additions and 51 deletions

View File

@@ -9,6 +9,9 @@
find_package (date QUIET)
if (NOT TARGET date::date)
# Tell date to build the timezone library
set(BUILD_TZ_LIB ON CACHE BOOL "Build date-tz library")
FetchContent_Declare(
hh_date_src
GIT_REPOSITORY https://github.com/HowardHinnant/date.git

View File

@@ -35,7 +35,7 @@ class Xrpl(ConanFile):
'snappy/1.1.10',
'soci/4.0.3',
'sqlite3/3.42.0',
'zlib/1.2.13',
'zlib/1.3.1',
'wasmedge/0.11.2',
]
@@ -52,7 +52,7 @@ class Xrpl(ConanFile):
'unity': False,
'cassandra-cpp-driver:shared': False,
'date:header_only': True,
'date:header_only': False,
'grpc:shared': False,
'grpc:secure': True,
'libarchive:shared': False,

View File

@@ -17,11 +17,16 @@
*/
//==============================================================================
#include <date/date.h>
#include <date/tz.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/contract.h>
#include <boost/algorithm/string.hpp>
#include <cassert>
#include <cstring>
#include <ctime>
#include <fstream>
#include <functional>
#include <iostream>
@@ -316,9 +321,35 @@ Logs::format(
{
output.reserve(message.size() + partition.size() + 100);
output = to_string(std::chrono::system_clock::now());
#ifdef LOG_LINE_NUMBERS
static const char* fmt = []() {
const char* env = std::getenv("LOG_DATE_FORMAT");
return env ? env : "%Y-%b-%d %T %Z"; // Default format
}();
// Check if we should use local time
static const bool useLocalTime = []() {
const char* env = std::getenv("LOG_DATE_LOCAL");
return env && std::strcmp(env, "1") == 0;
}();
if (useLocalTime)
{
auto now = std::chrono::system_clock::now();
auto local = date::make_zoned(date::current_zone(), now);
output = date::format(fmt, local);
}
else
{
output = date::format(fmt, std::chrono::system_clock::now());
}
#else
output = to_string(std::chrono::system_clock::now());
#endif
if (!output.empty()) // Allow setting date format to an empty string
output += " ";
output += " ";
if (!partition.empty())
output += partition + ":";

View File

@@ -146,6 +146,10 @@ private:
ScopedStream(Sink& sink, Severity level);
#ifdef LOG_LINE_NUMBERS
ScopedStream(Sink& sink, Severity level, const char* file, int line);
#endif
template <typename T>
ScopedStream(Stream const& stream, T const& t);
@@ -173,6 +177,10 @@ private:
Sink& m_sink;
Severity const m_level;
std::ostringstream mutable m_ostream;
#ifdef LOG_LINE_NUMBERS
const char* file_ = nullptr;
int line_ = 0;
#endif
};
#ifndef __INTELLISENSE__
@@ -212,11 +220,6 @@ public:
operator<<(std::ostream& manip(std::ostream&)) const;
private:
// Helper to write the location prefix (implemented after detail
// namespace)
void
writeLocationPrefix(ScopedStream& s) const;
const char* file_;
int line_;
const Stream& stream_;
@@ -398,6 +401,8 @@ 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)
{
@@ -474,9 +479,8 @@ template <typename T>
Journal::ScopedStream
Journal::StreamWithLocation::operator<<(T const& t) const
{
// Create a ScopedStream and inject the location info first
ScopedStream scoped(stream_.sink(), stream_.level());
writeLocationPrefix(scoped);
// Create a ScopedStream with location info
ScopedStream scoped(stream_.sink(), stream_.level(), file_, line_);
scoped.ostream() << t;
return scoped;
}

View File

@@ -136,37 +136,51 @@ Journal::ScopedStream::ScopedStream(
m_ostream << manip;
}
Journal::ScopedStream::~ScopedStream()
{
std::string const& s(m_ostream.str());
if (!s.empty())
{
if (s == "\n")
m_sink.write(m_level, "");
else
m_sink.write(m_level, s);
}
}
std::ostream&
Journal::ScopedStream::operator<<(std::ostream& manip(std::ostream&)) const
{
return m_ostream << manip;
}
//------------------------------------------------------------------------------
Journal::ScopedStream
Journal::Stream::operator<<(std::ostream& manip(std::ostream&)) const
{
return ScopedStream(*this, 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()
@@ -225,30 +239,80 @@ getLocationEscape()
} // namespace detail
// Implementation of writeLocationPrefix helper
void
Journal::StreamWithLocation::writeLocationPrefix(ScopedStream& s) const
#endif
#ifdef LOG_LINE_NUMBERS
Journal::ScopedStream::ScopedStream(
Sink& sink,
Severity level,
const char* file,
int line)
: m_sink(sink), m_level(level), file_(file), line_(line)
{
if (detail::shouldUseColors())
// Modifiers applied from all ctors
m_ostream << std::boolalpha << std::showbase;
// Write prefix if configured
if (file_ &&
detail::getLocationPosition() == detail::LocationPosition::PREFIX)
{
s.ostream() << detail::getLocationEscape() << "["
<< detail::stripSourceRoot(file_) << ":" << line_
<< "]\033[0m ";
detail::writeLocationString(m_ostream, file_, line_);
m_ostream << " ";
}
else
}
#endif
Journal::ScopedStream::~ScopedStream()
{
std::string s(m_ostream.str());
#ifdef LOG_LINE_NUMBERS
// Add suffix if configured
if (file_ &&
detail::getLocationPosition() == detail::LocationPosition::SUFFIX &&
!s.empty() && s != "\n")
{
s.ostream() << "[" << detail::stripSourceRoot(file_) << ":" << line_
<< "] ";
std::ostringstream combined;
combined << s;
if (!s.empty() && s.back() != ' ')
combined << " ";
detail::writeLocationString(combined, file_, line_);
s = combined.str();
}
#endif
if (!s.empty())
{
if (s == "\n")
m_sink.write(m_level, "");
else
m_sink.write(m_level, s);
}
}
std::ostream&
Journal::ScopedStream::operator<<(std::ostream& manip(std::ostream&)) const
{
return m_ostream << manip;
}
//------------------------------------------------------------------------------
Journal::ScopedStream
Journal::Stream::operator<<(std::ostream& manip(std::ostream&)) const
{
return ScopedStream(*this, manip);
}
#ifdef LOG_LINE_NUMBERS
// Implementation moved to use new constructor
Journal::ScopedStream
Journal::StreamWithLocation::operator<<(
std::ostream& manip(std::ostream&)) const
{
// Create a ScopedStream and inject the location info first
ScopedStream scoped(stream_.sink(), stream_.level());
writeLocationPrefix(scoped);
// Create a ScopedStream with location info
ScopedStream scoped(stream_.sink(), stream_.level(), file_, line_);
scoped.ostream() << manip;
return scoped;
}