// // Log.hpp // Clock Signal // // Created by Thomas Harte on 18/06/2018. // Copyright © 2018 Thomas Harte. All rights reserved. // #pragma once #include #include #include #include #ifdef TARGET_QT #include namespace { [[maybe_unused]] QDebug stream(const bool is_info) { return (is_info ? qInfo() : qWarning()).noquote().nospace(); } static constexpr char EndLine = 0; } #else #include namespace { [[maybe_unused]] std::ostream &stream(const bool is_info) { return is_info ? std::cout : std::cerr; } static constexpr char EndLine = '\n'; } #endif namespace Log { // TODO: if adopting C++20, std::format would be a better model to apply below. // But I prefer C files to C++ streams, so here it is for now. // Also noting: Apple's std::format support seems to require macOS 13.3, so // that'd be a bitter pill to swallow. enum class Source { ADBDevice, ADBGLU, Amiga, AmigaDisk, AmigaCopper, AmigaChipset, AmigaBlitter, AppleIISCSICard, Archimedes, ARMIOC, ARMMEMC, ARMVIDC, AtariST, AtariSTDMAController, BBCMicro, CommodoreStaticAnalyser, CMOSRTC, CRT, DirectAccessDevice, Enterprise, Floppy, i8272, I2C, IDE, IntelligentKeyboard, // Could probably be subsumed into 'Keyboard'? IWM, Keyboard, M50740, Macintosh, MasterSystem, MultiMachine, MFP68901, MOS6526, MSX, NCR5380, OpenGL, PCCompatible, PCPOST, PIC, PIT, Plus4, PCMTrack, SCC, SCSI, SZX, TapeUEF, TMS9918, TZX, Vic20, WDFDC, }; enum class EnabledLevel { None, // No logged statements are presented. Errors, // The error stream is presented, but not the info stream. ErrorsAndInfo, // All streams are presented. }; constexpr EnabledLevel enabled_level(const Source source) { #ifdef NDEBUG return EnabledLevel::None; #endif // Allow for compile-time source-level enabling and disabling of different sources. switch(source) { default: return EnabledLevel::ErrorsAndInfo; // The following are all things I'm not actively working on. case Source::AmigaBlitter: case Source::AmigaChipset: case Source::AmigaCopper: case Source::AmigaDisk: case Source::DirectAccessDevice: case Source::IWM: case Source::MFP68901: case Source::NCR5380: case Source::SCC: case Source::SCSI: case Source::I2C: // case Source::PCPOST: return EnabledLevel::None; case Source::Floppy: // case Source::Keyboard: return EnabledLevel::Errors; } } constexpr const char *prefix(const Source source) { switch(source) { case Source::ADBDevice: return "ADB device"; case Source::ADBGLU: return "ADB GLU"; case Source::Amiga: return "Amiga"; case Source::AmigaBlitter: return "Blitter"; case Source::AmigaChipset: return "Chipset"; case Source::AmigaCopper: return "Copper"; case Source::AmigaDisk: return "Disk"; case Source::AppleIISCSICard: return "SCSI card"; case Source::Archimedes: return "Archimedes"; case Source::ARMIOC: return "IOC"; case Source::ARMMEMC: return "MEMC"; case Source::ARMVIDC: return "VIDC"; case Source::AtariST: return "AtariST"; case Source::AtariSTDMAController: return "DMA"; case Source::BBCMicro: return "BBC"; case Source::CommodoreStaticAnalyser: return "Commodore Static Analyser"; case Source::CMOSRTC: return "CMOSRTC"; case Source::CRT: return "CRT"; case Source::DirectAccessDevice: return "Direct Access Device"; case Source::Enterprise: return "Enterprise"; case Source::Floppy: return "Floppy"; case Source::i8272: return "i8272"; case Source::I2C: return "I2C"; case Source::IDE: return "IDE"; case Source::IntelligentKeyboard: return "IKYB"; case Source::IWM: return "IWM"; case Source::Keyboard: return "Keyboard"; case Source::M50740: return "M50740"; case Source::Macintosh: return "Macintosh"; case Source::MasterSystem: return "SMS"; case Source::MOS6526: return "MOS6526"; case Source::MFP68901: return "MFP68901"; case Source::MultiMachine: return "Multi-machine"; case Source::MSX: return "MSX"; case Source::NCR5380: return "5380"; case Source::OpenGL: return "OpenGL"; case Source::Plus4: return "Plus4"; case Source::PCCompatible: return "PC"; case Source::PCPOST: return "POST"; case Source::PIC: return "PIC"; case Source::PIT: return "PIT"; case Source::PCMTrack: return "PCM Track"; case Source::SCSI: return "SCSI"; case Source::SCC: return "SCC"; case Source::SZX: return "SZX"; case Source::TapeUEF: return "UEF"; case Source::TMS9918: return "TMS9918"; case Source::TZX: return "TZX"; case Source::Vic20: return "Vic20"; case Source::WDFDC: return "WD FDC"; } return nullptr; } template struct LogLine; struct RepeatAccumulator { std::string last; Source source; size_t count = 0; bool is_info; }; struct AccumulatingLog { inline static thread_local RepeatAccumulator accumulator_; }; template struct LogLine: private AccumulatingLog { public: explicit LogLine(const bool is_info) noexcept : is_info_(is_info) {} ~LogLine() { if(output_ == accumulator_.last && source == accumulator_.source && is_info_ == accumulator_.is_info) { ++accumulator_.count; return; } if(!accumulator_.last.empty()) { const char *const unadorned_prefix = prefix(accumulator_.source); std::string prefix; if(unadorned_prefix) { prefix = "["; prefix += unadorned_prefix; prefix += "] "; } auto &&out = stream(accumulator_.is_info); out << prefix.c_str() << accumulator_.last.c_str(); // Use of .c_str() avoids an ambiguity affecting older // versions of Qt. if(accumulator_.count > 1) { out << " [* " << accumulator_.count << "]"; } if(EndLine) out << EndLine; } accumulator_.count = 1; accumulator_.last = output_; accumulator_.source = source; accumulator_.is_info = is_info_; } template auto &append(const char (&format)[size], Args... args) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat" #pragma GCC diagnostic ignored "-Wformat-security" const auto append_size = std::snprintf(nullptr, 0, format, args...); const auto end = output_.size(); output_.resize(output_.size() + size_t(append_size) + 1); std::snprintf(output_.data() + end, size_t(append_size) + 1, format, args...); output_.pop_back(); #pragma GCC diagnostic pop return *this; } template auto &append_if(const bool condition, const char (&format)[size], Args... args) { if(!condition) return *this; return append(format, args...); } private: bool is_info_; std::string output_; }; template struct LogLine { explicit LogLine(bool) noexcept {} template auto &append(const char (&)[size], Args...) { return *this; } template auto &append_if(bool, const char (&)[size], Args...) { return *this; } }; template class Logger { public: Logger() = delete; static constexpr bool InfoEnabled = enabled_level(source) == EnabledLevel::ErrorsAndInfo; static constexpr bool ErrorsEnabled = enabled_level(source) >= EnabledLevel::Errors; static auto info() { return LogLine(true); } static auto error() { return LogLine(false); } }; }