Files
rippled/src/libxrpl/basics/Log.cpp
Pratik Mankawde 09908f7a5f fix(telemetry): guard <cstddef> in Log.cpp to match its use
std::size_t names the trace-id and span-id hex widths, which are compiled only
when telemetry is enabled, so the include is unused otherwise and clang-tidy
fails the build on warnings-as-errors.
2026-08-26 18:07:20 +01:00

466 lines
12 KiB
C++

#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/algorithm/string/predicate.hpp>
#ifdef XRPL_ENABLE_TELEMETRY
#include <opentelemetry/context/runtime_context.h>
#include <opentelemetry/nostd/shared_ptr.h>
#include <opentelemetry/nostd/span.h>
#include <opentelemetry/nostd/variant.h>
#include <opentelemetry/trace/span.h>
#include <opentelemetry/trace/span_metadata.h>
#endif // XRPL_ENABLE_TELEMETRY
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#ifdef XRPL_ENABLE_TELEMETRY
// std::size_t names the hex widths used when formatting a trace context.
#include <cstddef>
#endif // XRPL_ENABLE_TELEMETRY
namespace xrpl {
Logs::Sink::Sink(std::string partition, beast::Severity thresh, Logs& logs)
: beast::Journal::Sink(thresh, false), logs_(logs), partition_(std::move(partition))
{
}
void
Logs::Sink::write(beast::Severity level, std::string const& text)
{
if (level < threshold())
return;
logs_.write(level, partition_, text, console());
}
void
Logs::Sink::writeAlways(beast::Severity level, std::string const& text)
{
logs_.write(level, partition_, text, console());
}
//------------------------------------------------------------------------------
Logs::File::File() : stream_(nullptr)
{
}
bool
Logs::File::isOpen() const noexcept
{
return stream_ != nullptr;
}
bool
Logs::File::open(std::filesystem::path const& path)
{
close();
bool wasOpened = false;
// VFALCO TODO Make this work with Unicode file paths
std::unique_ptr<std::ofstream> stream =
std::make_unique<std::ofstream>(path.c_str(), std::fstream::app);
if (stream->good())
{
path_ = path;
stream_ = std::move(stream);
wasOpened = true;
}
return wasOpened;
}
bool
Logs::File::closeAndReopen()
{
close();
return open(path_);
}
void
Logs::File::close()
{
stream_ = nullptr;
}
void
Logs::File::write(char const* text)
{
if (stream_ != nullptr)
(*stream_) << text;
}
void
Logs::File::writeln(char const* text)
{
if (stream_ != nullptr)
{
(*stream_) << text;
(*stream_) << std::endl;
}
}
//------------------------------------------------------------------------------
Logs::Logs(beast::Severity thresh) : thresh_(thresh) // default severity
{
}
bool
Logs::open(std::filesystem::path const& pathToLogFile)
{
return file_.open(pathToLogFile);
}
beast::Journal::Sink&
Logs::get(std::string const& name)
{
std::scoped_lock const lock(mutex_);
auto const result = sinks_.emplace(name, makeSink(name, thresh_));
return *result.first->second;
}
beast::Journal::Sink&
Logs::operator[](std::string const& name)
{
return get(name);
}
beast::Journal
Logs::journal(std::string const& name)
{
return beast::Journal(get(name));
}
beast::Severity
Logs::threshold() const
{
return thresh_;
}
void
Logs::threshold(beast::Severity thresh)
{
std::scoped_lock const lock(mutex_);
thresh_ = thresh;
for (auto& sink : sinks_)
sink.second->threshold(thresh);
}
std::vector<std::pair<std::string, std::string>>
Logs::partitionSeverities() const
{
std::vector<std::pair<std::string, std::string>> list;
std::scoped_lock const lock(mutex_);
list.reserve(sinks_.size());
for (auto const& [name, sink] : sinks_)
list.emplace_back(name, toString(sink->threshold()));
return list;
}
void
Logs::write(
beast::Severity level,
std::string const& partition,
std::string const& text,
bool console)
{
std::string s;
format(s, text, level, partition);
std::scoped_lock const lock(mutex_);
file_.writeln(s);
if (!silent_)
std::cerr << s << '\n';
// VFALCO TODO Fix console output
// if (console)
// out_.write_console(s);
}
std::string
Logs::rotate()
{
std::scoped_lock const lock(mutex_);
bool const wasOpened = file_.closeAndReopen();
if (wasOpened)
return "The log file was closed and reopened.";
return "The log file could not be closed and reopened.";
}
std::unique_ptr<beast::Journal::Sink>
Logs::makeSink(std::string const& name, beast::Severity threshold)
{
return std::make_unique<Sink>(name, threshold, *this);
}
std::string
Logs::toString(beast::Severity s)
{
switch (s)
{
case beast::Severity::Trace:
return "Trace";
case beast::Severity::Debug:
return "Debug";
case beast::Severity::Info:
return "Info";
case beast::Severity::Warning:
return "Warning";
case beast::Severity::Error:
return "Error";
case beast::Severity::Fatal:
return "Fatal";
// LCOV_EXCL_START
default:
UNREACHABLE("xrpl::Logs::toString : invalid severity");
return "Unknown";
// LCOV_EXCL_STOP
}
}
std::optional<beast::Severity>
Logs::fromString(std::string const& s)
{
if (boost::iequals(s, "trace"))
return beast::Severity::Trace;
if (boost::iequals(s, "debug"))
return beast::Severity::Debug;
if (boost::iequals(s, "info") || boost::iequals(s, "information"))
return beast::Severity::Info;
if (boost::iequals(s, "warn") || boost::iequals(s, "warning") || boost::iequals(s, "warnings"))
return beast::Severity::Warning;
if (boost::iequals(s, "error") || boost::iequals(s, "errors"))
return beast::Severity::Error;
if (boost::iequals(s, "fatal") || boost::iequals(s, "fatals"))
return beast::Severity::Fatal;
return std::nullopt;
}
void
Logs::format(
std::string& output,
std::string const& message,
beast::Severity severity,
std::string const& partition)
{
output.reserve(message.size() + partition.size() + 100);
output = xrpl::to_string(std::chrono::system_clock::now());
output += " ";
if (!partition.empty())
output += partition + ":";
using beast::Severity;
switch (severity)
{
case Severity::Trace:
output += "TRC ";
break;
case Severity::Debug:
output += "DBG ";
break;
case Severity::Info:
output += "NFO ";
break;
case Severity::Warning:
output += "WRN ";
break;
case Severity::Error:
output += "ERR ";
break;
// LCOV_EXCL_START
default:
UNREACHABLE("xrpl::Logs::format : invalid severity");
[[fallthrough]];
// LCOV_EXCL_STOP
case Severity::Fatal:
output += "FTL ";
break;
}
#ifdef XRPL_ENABLE_TELEMETRY
// Inject OTel trace context when an active, sampled span exists on this
// thread. Checks the thread-local context value directly to avoid the
// heap allocation that GetSpan() performs on the no-span path.
{
auto context = opentelemetry::context::RuntimeContext::GetCurrent();
auto spanValue = context.GetValue(opentelemetry::trace::kSpanKey);
if (opentelemetry::nostd::holds_alternative<
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>>(spanValue))
{
auto span = opentelemetry::nostd::get<
opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span>>(spanValue);
auto spanCtx = span->GetContext();
// Require the sampled flag as well as a valid context. A dropped
// span still carries its parent's ids, so a valid context does
// not imply the span reaches the backend. An unsampled remote
// parent arrives either because an upstream node propagated
// sampled=0, or because a peer omitted trace_flags entirely and
// it defaults to 0 (TraceContextPropagator, TxTracing,
// ConsensusReceiveTracing). Either way the ParentBasedSampler
// drops the local span, while the tracer still returns a no-op
// span with a valid context.
// Logging those ids would advertise a trace that was never
// exported, leaving the log-to-trace link resolving to nothing.
if (spanCtx.IsValid() && spanCtx.IsSampled())
{
// Hex widths of a W3C trace context: 16-byte trace_id and
// 8-byte span_id render to 32 and 16 lowercase hex chars.
constexpr std::size_t kTraceIdHexLen = 32;
constexpr std::size_t kSpanIdHexLen = 16;
constexpr auto kTraceIdPrefix = "trace_id=";
constexpr auto kSpanIdPrefix = " span_id=";
char traceId[kTraceIdHexLen], spanId[kSpanIdHexLen];
spanCtx.trace_id().ToLowerBase16(
opentelemetry::nostd::span<char, kTraceIdHexLen>{traceId});
spanCtx.span_id().ToLowerBase16(
opentelemetry::nostd::span<char, kSpanIdHexLen>{spanId});
output += kTraceIdPrefix;
output.append(traceId, kTraceIdHexLen);
output += kSpanIdPrefix;
output.append(spanId, kSpanIdHexLen);
output += ' ';
}
}
}
#endif // XRPL_ENABLE_TELEMETRY
output += message;
// Limit the maximum length of the output
if (output.size() > kMaximumMessageCharacters)
{
output.resize(kMaximumMessageCharacters - 3);
output += "...";
}
// Attempt to prevent sensitive information from appearing in log files by
// redacting it with asterisks.
auto scrubber = [&output](char const* token) {
auto first = output.find(token);
// If we have found the specified token, then attempt to isolate the
// sensitive data (it's enclosed by double quotes) and mask it off:
if (first != std::string::npos)
{
first = output.find('\"', first + std::strlen(token));
if (first != std::string::npos)
{
auto last = output.find('\"', ++first);
if (last == std::string::npos)
last = output.size();
output.replace(first, last - first, last - first, '*');
}
}
};
scrubber("\"seed\"");
scrubber("\"seed_hex\"");
scrubber("\"secret\"");
scrubber("\"master_key\"");
scrubber("\"master_seed\"");
scrubber("\"master_seed_hex\"");
scrubber("\"passphrase\"");
}
//------------------------------------------------------------------------------
class DebugSink
{
private:
std::reference_wrapper<beast::Journal::Sink> sink_;
std::unique_ptr<beast::Journal::Sink> holder_;
std::mutex mtx_;
public:
DebugSink() : sink_(beast::Journal::getNullSink())
{
}
DebugSink(DebugSink const&) = delete;
DebugSink&
operator=(DebugSink const&) = delete;
DebugSink(DebugSink&&) = delete;
DebugSink&
operator=(DebugSink&&) = delete;
std::unique_ptr<beast::Journal::Sink>
set(std::unique_ptr<beast::Journal::Sink> sink)
{
std::scoped_lock const _(mtx_);
using std::swap;
swap(holder_, sink);
if (holder_)
{
sink_ = *holder_;
}
else
{
sink_ = beast::Journal::getNullSink();
}
return sink;
}
beast::Journal::Sink&
get()
{
std::scoped_lock const _(mtx_);
return sink_.get();
}
};
static DebugSink&
debugSink()
{
static DebugSink kInst;
return kInst;
}
std::unique_ptr<beast::Journal::Sink>
setDebugLogSink(std::unique_ptr<beast::Journal::Sink> sink)
{
return debugSink().set(std::move(sink));
}
beast::Journal
debugLog()
{
return beast::Journal(debugSink().get());
}
} // namespace xrpl