/* * This file is part of the Monero P2Pool * Copyright (c) 2021-2023 SChernykh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once namespace p2pool { class Wallet; namespace log { extern int GLOBAL_LOG_LEVEL; extern bool CONSOLE_COLORS; constexpr int MAX_GLOBAL_LOG_LEVEL = 6; enum class Severity { Info, Warning, Error, }; struct Stream { enum params : int { BUF_SIZE = 1024 - 1 }; template explicit FORCEINLINE Stream(char (&buf)[N]) : m_pos(0), m_numberWidth(1), m_buf(buf), m_bufSize(N - 1) {} FORCEINLINE Stream(void* buf, size_t size) : m_pos(0), m_numberWidth(1), m_buf(reinterpret_cast(buf)), m_bufSize(static_cast(size) - 1) {} template struct Entry { static constexpr void no() { static_assert(not_implemented::value, "Logging for this type is not implemented"); } static constexpr void put(const T&, Stream*) { no(); } static constexpr void put(T&&, Stream*) { no(); } }; template FORCEINLINE Stream& operator<<(T& data) { Entry::type>::put(data, this); return *this; } template FORCEINLINE Stream& operator<<(T&& data) { Entry::put(std::move(data), this); return *this; } template NOINLINE void writeInt(T data) { static_assert(1 < base && base <= 64, "Invalid base"); const T data_with_sign = data; data = abs(data); const bool negative = (data != data_with_sign); char buf[32]; size_t k = sizeof(buf); int w = m_numberWidth; do { buf[--k] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"[data % base]; data /= base; --w; } while ((data > 0) || (w > 0)); if (negative) { buf[--k] = '-'; } writeBuf(buf + k, sizeof(buf) - k); } FORCEINLINE void writeBuf(const char* buf, size_t n0) { const int n = static_cast(n0); const int pos = m_pos; if (pos + n > m_bufSize) { return; } memcpy(m_buf + pos, buf, n); m_pos = pos + n; } FORCEINLINE int getNumberWidth() const { return m_numberWidth; } FORCEINLINE void setNumberWidth(int width) { m_numberWidth = width; } NOINLINE void writeCurrentTime(); int m_pos; int m_numberWidth; char* m_buf; int m_bufSize; }; struct Writer : public Stream { explicit NOINLINE Writer(Severity severity); NOINLINE ~Writer(); char m_stackBuf[BUF_SIZE + 1]; }; #define COLOR_ENTRY(x, s) \ struct x{}; \ template<> struct Stream::Entry { static FORCEINLINE void put(x&&, Stream* wrapper) { wrapper->writeBuf(s, sizeof(s) - 1); } }; COLOR_ENTRY(NoColor, "\x1b[0m") COLOR_ENTRY(Black, "\x1b[0;30m") COLOR_ENTRY(Red, "\x1b[0;31m") COLOR_ENTRY(Green, "\x1b[0;32m") COLOR_ENTRY(Yellow, "\x1b[0;33m") COLOR_ENTRY(Blue, "\x1b[0;34m") COLOR_ENTRY(Magenta, "\x1b[0;35m") COLOR_ENTRY(Cyan, "\x1b[0;36m") COLOR_ENTRY(White, "\x1b[0;37m") COLOR_ENTRY(Gray, "\x1b[0;90m") COLOR_ENTRY(LightRed, "\x1b[0;91m") COLOR_ENTRY(LightGreen, "\x1b[0;92m") COLOR_ENTRY(LightYellow, "\x1b[0;93m") COLOR_ENTRY(LightBlue, "\x1b[0;94m") COLOR_ENTRY(LightMagenta, "\x1b[0;95m") COLOR_ENTRY(LightCyan, "\x1b[0;96m") #undef COLOR_ENTRY template struct Stream::Entry { static FORCEINLINE void put(const char (&data)[N], Stream* wrapper) { wrapper->writeBuf(data, N - 1); } }; template<> struct Stream::Entry { static FORCEINLINE void put(const char* data, Stream* wrapper) { wrapper->writeBuf(data, strlen(data)); } }; template<> struct Stream::Entry { static FORCEINLINE void put(char* data, Stream* wrapper) { wrapper->writeBuf(data, strlen(data)); } }; template<> struct Stream::Entry { static FORCEINLINE void put(char c, Stream* wrapper) { wrapper->writeBuf(&c, 1); } }; #define INT_ENTRY(x) \ template<> struct Stream::Entry { static FORCEINLINE void put(x data, Stream* wrapper) { wrapper->writeInt(data); } }; INT_ENTRY(int8_t) INT_ENTRY(int16_t) INT_ENTRY(int32_t) INT_ENTRY(int64_t) INT_ENTRY(uint8_t) INT_ENTRY(uint16_t) INT_ENTRY(uint32_t) INT_ENTRY(uint64_t) #ifdef __APPLE__ INT_ENTRY(long) INT_ENTRY(unsigned long) #endif #undef INT_ENTRY template struct BasedValue { explicit FORCEINLINE BasedValue(T value) : m_value(value) { static_assert(std::is_integral::value, "Must be an integer type here"); } T m_value; }; template struct Stream::Entry> { static FORCEINLINE void put(BasedValue data, Stream* wrapper) { wrapper->writeInt(data.m_value); } }; template FORCEINLINE BasedValue Hex(T value) { return BasedValue(value); } template<> struct Stream::Entry { static NOINLINE void put(double x, Stream* wrapper) { char buf[16]; int n = snprintf(buf, sizeof(buf), "%.3f", x); if (n > 0) { if (n > static_cast(sizeof(buf)) - 1) { n = static_cast(sizeof(buf)) - 1; } wrapper->writeBuf(buf, n); } } }; template<> struct Stream::Entry { static FORCEINLINE void put(float x, Stream* wrapper) { Stream::Entry::put(x, wrapper); } }; template<> struct Stream::Entry { static NOINLINE void put(const hash& data, Stream* wrapper) { char buf[sizeof(data) * 2]; for (size_t i = 0; i < sizeof(data.h); ++i) { buf[i * 2 + 0] = "0123456789abcdef"[data.h[i] >> 4]; buf[i * 2 + 1] = "0123456789abcdef"[data.h[i] & 15]; } wrapper->writeBuf(buf, sizeof(buf)); } }; template<> struct Stream::Entry { static NOINLINE void put(const difficulty_type& data, Stream* wrapper) { char buf[40]; size_t k = sizeof(buf); int w = wrapper->m_numberWidth; uint64_t a = data.lo; uint64_t b = data.hi; do { // 2^64 % 10 = 6, so (b % 10) is multiplied by 6 static constexpr uint64_t mul6[10] = { 0, 6, 2, 8, 4, 0, 6, 2, 8, 4 }; buf[--k] = "01234567890123456789"[a % 10 + mul6[b % 10]]; uint64_t r; a = udiv128(b % 10, a, 10, &r); b /= 10; --w; } while ((a > 0) || (b > 0) || (w > 0)); wrapper->writeBuf(buf + k, sizeof(buf) - k); } }; struct const_buf { FORCEINLINE const_buf(const char* data, size_t size) : m_data(data), m_size(size) {} const char* m_data; size_t m_size; }; template<> struct log::Stream::Entry { static FORCEINLINE void put(const_buf&& buf, Stream* wrapper) { wrapper->writeBuf(buf.m_data, buf.m_size); } }; struct hex_buf { FORCEINLINE hex_buf(const uint8_t* data, size_t size) : m_data(data), m_size(size) {} const uint8_t* m_data; size_t m_size; }; template<> struct log::Stream::Entry { static FORCEINLINE void put(const hex_buf& value, Stream* wrapper) { for (size_t i = 0; i < value.m_size; ++i) { char buf[2]; buf[0] = "0123456789abcdef"[value.m_data[i] >> 4]; buf[1] = "0123456789abcdef"[value.m_data[i] & 15]; wrapper->writeBuf(buf, sizeof(buf)); } } }; template<> struct log::Stream::Entry { static FORCEINLINE void put(const std::string& value, Stream* wrapper) { wrapper->writeBuf(value.c_str(), value.length()); } }; struct Hashrate { FORCEINLINE Hashrate() : m_data(0), m_valid(false) {} explicit FORCEINLINE Hashrate(uint64_t data) : m_data(data), m_valid(true) {} FORCEINLINE Hashrate(uint64_t data, bool valid) : m_data(data), m_valid(valid) {} uint64_t m_data; bool m_valid; }; template<> struct log::Stream::Entry { static NOINLINE void put(const Hashrate& value, Stream* wrapper) { if (!value.m_valid) { return; } const double x = static_cast(value.m_data); static constexpr const char* units[] = { "H/s", "KH/s", "MH/s", "GH/s", "TH/s", "PH/s", "EH/s" }; int n; char buf[32]; if (value.m_data < 1000) { n = snprintf(buf, sizeof(buf), "%u %s", static_cast(value.m_data), units[0]); } else { size_t k = 0; double magnitude = 1.0; while ((x >= magnitude * 1e3) && (k < array_size(units) - 1)) { magnitude *= 1e3; ++k; } n = snprintf(buf, sizeof(buf), "%.3f %s", x / magnitude, units[k]); } if (n > 0) { if (n > static_cast(sizeof(buf)) - 1) { n = static_cast(sizeof(buf)) - 1; } wrapper->writeBuf(buf, n); } } }; struct XMRAmount { explicit FORCEINLINE XMRAmount(uint64_t data) : m_data(data) {} uint64_t m_data; }; template<> struct log::Stream::Entry { static NOINLINE void put(XMRAmount value, Stream* wrapper) { constexpr uint64_t denomination = 1000000000000ULL; const int w = wrapper->getNumberWidth(); wrapper->setNumberWidth(1); *wrapper << value.m_data / denomination << '.'; wrapper->setNumberWidth(12); *wrapper << value.m_data % denomination << " XMR"; wrapper->setNumberWidth(w); } }; template<> struct log::Stream::Entry { static NOINLINE void put(NetworkType value, Stream* wrapper) { switch (value) { case NetworkType::Invalid: *wrapper << "invalid"; break; case NetworkType::Mainnet: *wrapper << "mainnet"; break; case NetworkType::Testnet: *wrapper << "testnet"; break; case NetworkType::Stagenet: *wrapper << "stagenet"; break; } } }; struct Duration { explicit FORCEINLINE Duration(uint64_t data) : m_data(data) {} uint64_t m_data; }; template<> struct log::Stream::Entry { static NOINLINE void put(Duration value, Stream* wrapper) { const uint64_t uptime = value.m_data; const int64_t s = uptime % 60; const int64_t m = (uptime / 60) % 60; const int64_t h = (uptime / 3600) % 24; const int64_t d = uptime / 86400; if (d > 0) { *wrapper << d << "d "; } *wrapper << h << "h " << m << "m " << s << 's'; } }; template struct PadRight { FORCEINLINE PadRight(const T& value, int len) : m_value(value), m_len(len) {} const T& m_value; int m_len; // Declare it to make compiler happy PadRight(const PadRight&); private: PadRight& operator=(const PadRight&) = delete; PadRight& operator=(PadRight&&) = delete; }; template FORCEINLINE PadRight pad_right(const T& value, int len) { return PadRight(value, len); } template struct log::Stream::Entry> { static NOINLINE void put(PadRight&& data, Stream* wrapper) { char buf[log::Stream::BUF_SIZE + 1]; log::Stream s(buf); s << data.m_value; const int len = std::min(data.m_len, log::Stream::BUF_SIZE); if (s.m_pos < len) { memset(buf + s.m_pos, ' ', static_cast(len) - s.m_pos); s.m_pos = len; } wrapper->writeBuf(buf, s.m_pos); } }; template<> struct log::Stream::Entry { static NOINLINE void put(const raw_ip& value, Stream* wrapper); }; template<> struct log::Stream::Entry { static NOINLINE void put(const Wallet& w, Stream* wrapper); }; namespace { template void apply_severity(log::Stream&); template<> FORCEINLINE void apply_severity(log::Stream& s) { s << log::NoColor(); } template<> FORCEINLINE void apply_severity(log::Stream& s) { s << log::Yellow(); } template<> FORCEINLINE void apply_severity(log::Stream& s) { s << log::Red(); } } #define CONCAT(a, b) CONCAT2(a, b) #define CONCAT2(a, b) a##b // This is to check that LOG() call doesn't modify variables in scope, making program behavior dependent on the log level: // // int some_func(int& n) { return ++n; } // ... // LOGINFO(1, "Some important value: " << some_func(n)); // // will not compile because the dummy lambda capture uses const-qualified copies of all variables. // // The check is "free": compiler will remove it entirely in release builds. struct DummyStream { template FORCEINLINE DummyStream& operator<<(const T&) { return *this; } }; #define SIDE_EFFECT_CHECK(level, ...) \ do { \ if (0) { \ MSVC_PRAGMA(warning(suppress:26444)) \ [=]() { \ log::DummyStream x; \ x << (level) << __VA_ARGS__; \ }; \ } \ } while (0) #ifdef P2POOL_LOG_DISABLE #define LOGINFO(level, ...) SIDE_EFFECT_CHECK(level, __VA_ARGS__) #define LOGWARN(level, ...) SIDE_EFFECT_CHECK(level, __VA_ARGS__) #define LOGERR(level, ...) SIDE_EFFECT_CHECK(level, __VA_ARGS__) #else #define LOG(level, severity, ...) \ do { \ SIDE_EFFECT_CHECK(level, __VA_ARGS__); \ if ((level) <= log::GLOBAL_LOG_LEVEL) { \ log::Writer CONCAT(log_wrapper_, __LINE__)(severity); \ CONCAT(log_wrapper_, __LINE__) << log::Gray() << log_category_prefix; \ log::apply_severity(CONCAT(log_wrapper_, __LINE__)); \ CONCAT(log_wrapper_, __LINE__) << __VA_ARGS__ << log::NoColor(); \ } \ } while (0) #define LOGINFO(level, ...) LOG(level, log::Severity::Info, __VA_ARGS__) #define LOGWARN(level, ...) LOG(level, log::Severity::Warning, __VA_ARGS__) #define LOGERR(level, ...) LOG(level, log::Severity::Error, __VA_ARGS__) #endif void reopen(); void stop(); } // namespace log } // namespace p2pool