From ff342675d04a267fc588c88e0243bc796dde3405 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:43:05 -0500 Subject: [PATCH 01/19] Add simple logging utility Partially inspired by rfc5424, along with a few other idioms I picked up along the way. This should allow one to tweak the log level by setting a minimum log level in the `Log' namespace. Right now I've set the log level to debug in the main entry point. --- source/util/CMakeLists.txt | 2 + source/util/log.cpp | 42 ++++++++++++ source/util/log.h | 128 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 source/util/log.cpp create mode 100644 source/util/log.h diff --git a/source/util/CMakeLists.txt b/source/util/CMakeLists.txt index 436aa137..038bd08f 100644 --- a/source/util/CMakeLists.txt +++ b/source/util/CMakeLists.txt @@ -32,6 +32,7 @@ if ( PLATFORM_OSX ) endif () set( srcs + log.cpp settingstree.cpp version.cpp @@ -43,6 +44,7 @@ set( headers enumflags.h enumtostring.h enumtostring_fwd.h + log.h settingstree.h tostring.h toutf8.h diff --git a/source/util/log.cpp b/source/util/log.cpp new file mode 100644 index 00000000..9db0dc35 --- /dev/null +++ b/source/util/log.cpp @@ -0,0 +1,42 @@ +/* Copyright (C) 2021-2023 Simon Symeonidis + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ +#include "log.h" + +namespace Log { + +enum Level FilterLevel = Level::Debug; + +std::optional logFile; + +std::filesystem::path logPath; + +std::mutex lock; + +void init(enum Level lvl, std::filesystem::path lp) +{ + FilterLevel = lvl; + logPath = lp; +} + +std::string all() +{ + std::stringstream ss; + std::ifstream ifs(logPath); + ss << ifs.rdbuf(); + return ss.str(); +} + +} diff --git a/source/util/log.h b/source/util/log.h new file mode 100644 index 00000000..05f39716 --- /dev/null +++ b/source/util/log.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021-2023 Simon Symeonidis + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ +#ifndef APP_LOG_H +#define APP_LOG_H + +#include +#include +#include /* put_time */ +#include +#include +#include +#include + +#include + + +namespace Log { + +enum class Level { + Debug = 0, + Info = 1, + Notify = 2, + Warning = 3, + Error = 4, +}; + +/** + * toggleable log level - set this to the minimum level to filter from a la + * rfc5424. + */ +extern enum Level CurrentLevel; + +extern std::filesystem::path logPath; + +extern std::optional logFile; + +void init(enum Level lvl, std::filesystem::path logPath); + +/** + * return the full contents of the log file + */ +std::string all(); + +template +void backend(enum Level level, std::string_view fmt, Args&&... args) +{ + /* formats a timestamp in a rfc3339 fashion; I believe the system clock + * defaults to epoch, which should be UTC, but I might be wrong. */ + const auto timestamp_now_str_fn = []() -> std::string { + auto now = std::chrono::system_clock::now(); + auto now_t = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&now_t), "%Y-%m-%dT%H:%M:%SZ"); + return ss.str(); + }; + + const auto level_str_fn = [](enum Level l) noexcept { + switch (l) { + case Level::Debug: return "[debug]"; + case Level::Info: return "[info]"; + case Level::Notify: return "[notify]"; + case Level::Warning: return "[warn]"; + case Level::Error: return "[error]"; + /* unreachable */ + default: + return "unknown"; + } + }; + + std::cout + << timestamp_now_str_fn() << ": " + << level_str_fn(level) << ": " + << std::vformat(fmt, std::make_format_args(args...)) + << std::endl; + + if (!logFile) { + const auto mode = std::ios_base::out | std::ios_base::app; + logFile = std::ofstream(logPath, mode); + } + + if (logFile.value().good()) { + logFile.value() + << timestamp_now_str_fn() << ": " + << level_str_fn(level) << ": " + << std::vformat(fmt, std::make_format_args(args...)) + << std::endl; + } else { + std::cout << std::format( + "{} {} could not open file at ({})", + timestamp_now_str_fn(), + level_str_fn(Level::Warning), + logPath.generic_string() + ); + } +} + +template +void d(std::string_view fmt, Args&&... args) { backend(Level::Debug, fmt, std::forward(args)...); } + +template +void i(std::string_view fmt, Args&&... args) { backend(Level::Info, fmt, std::forward(args)...); } + +template +void n(std::string_view fmt, Args&&... args) { backend(Level::Notify, fmt, std::forward(args)...); } + +template +void w(std::string_view fmt, Args&&... args) { backend(Level::Warning, fmt, std::forward(args)...); } + +template +void e(std::string_view fmt, Args&&... args) { backend(Level::Error, fmt, std::forward(args)...); } + +} +#endif From d7d9ba2c832e0f7dd86233dae70a4a5475a3f8f9 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:48:24 -0500 Subject: [PATCH 02/19] Add logging path to Paths namespace --- source/app/paths.cpp | 5 +++++ source/app/paths.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/source/app/paths.cpp b/source/app/paths.cpp index 4d5bce2c..bbb9e0c8 100644 --- a/source/app/paths.cpp +++ b/source/app/paths.cpp @@ -39,6 +39,11 @@ path getConfigDir() #endif } +path getLogPath() +{ + return getConfigDir() / "log.txt"; +} + path getUserDataDir() { return fromQString( diff --git a/source/app/paths.h b/source/app/paths.h index 898a3ab4..e81bb6cf 100644 --- a/source/app/paths.h +++ b/source/app/paths.h @@ -26,6 +26,9 @@ namespace Paths { /// Return a path to a directory where config files should be written to. path getConfigDir(); + /// Return a path to the log file + path getLogPath(); + /// Return a path to a directory where persistent application data should /// be written to. path getUserDataDir(); From 933c6752f3e7a3913796d63f133f250a40df703b Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:53:30 -0500 Subject: [PATCH 03/19] Replace std::cerr with Log::e in score/... --- source/score/serialization.cpp | 3 +-- source/score/serialization.h | 4 ++-- source/score/utils/directionindex.cpp | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/source/score/serialization.cpp b/source/score/serialization.cpp index ff426ad1..c1980503 100644 --- a/source/score/serialization.cpp +++ b/source/score/serialization.cpp @@ -42,8 +42,7 @@ InputArchive::InputArchive(std::istream &is) } else { - std::cerr << "Warning: Reading an unknown file version - " << version - << std::endl; + Log::e("warning: reading an unknown file version - {}", version); // Reading in a newer version. Just do the best we can with the latest // file version we're aware of. diff --git a/source/score/serialization.h b/source/score/serialization.h index d36051b1..e060ca47 100644 --- a/source/score/serialization.h +++ b/source/score/serialization.h @@ -31,6 +31,7 @@ #include #include #include +#include #include namespace ScoreUtils @@ -113,8 +114,7 @@ namespace detail val = *result; else { - std::cerr << "Unknown enum value: " << text - << std::endl; + Log::e("unknown enum value: {}", text); } } } diff --git a/source/score/utils/directionindex.cpp b/source/score/utils/directionindex.cpp index 3d663221..51445669 100644 --- a/source/score/utils/directionindex.cpp +++ b/source/score/utils/directionindex.cpp @@ -19,6 +19,7 @@ #include #include +#include DirectionIndex::DirectionIndex(const Score &score) : myScore(score), myActiveSymbol(DirectionSymbol::ActiveNone) @@ -147,8 +148,7 @@ SystemLocation DirectionIndex::followDirection(DirectionSymbol::SymbolType type) else { // This should not happen if the score is properly written. - std::cerr << "Could not find the symbol " - << static_cast(nextSymbol) << std::endl; + Log::e("Could not find the symbol {}", static_cast(nextSymbol)); return SystemLocation(0, 0); } } From e436cb5f2a088b01c0a2f29176ed44b01d4b7193 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:56:02 -0500 Subject: [PATCH 04/19] Replace std::cerr with Log::e in formats/... --- source/formats/gp7/from_xml.cpp | 22 ++++++++----------- source/formats/gp7/to_pt2.cpp | 3 ++- .../powertab_old/powertabdocument/macros.cpp | 9 ++++++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/source/formats/gp7/from_xml.cpp b/source/formats/gp7/from_xml.cpp index 74800336..1de16272 100644 --- a/source/formats/gp7/from_xml.cpp +++ b/source/formats/gp7/from_xml.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -167,8 +168,7 @@ parseChordNote(const pugi::xml_node &node) note.myAccidental = *accidental; else { - std::cerr << "Unknown accidental type: " << accidental_text - << std::endl; + Log::e("unknown accidental type: {}", accidental_text); } return note; @@ -189,7 +189,7 @@ parseChordDegree(const pugi::xml_node &chord_node, const char *name) if (auto alteration = Util::toEnum(text)) degree.myAlteration = *alteration; else - std::cerr << "Unknown alteration type: " << text << std::endl; + Log::e("unknown alteration type: {}", text); return degree; } @@ -425,8 +425,7 @@ parseMasterBars(const pugi::xml_node &master_bars_node) master_bar.myDirectionTargets.push_back(*target); else { - std::cerr << "Invalid direction target type: " << target_str - << std::endl; + Log::e("invalid direction target type: {}", target_str); } } @@ -438,8 +437,7 @@ parseMasterBars(const pugi::xml_node &master_bars_node) master_bar.myDirectionJumps.push_back(*jump); else { - std::cerr << "Invalid direction jump type: " << jump_str - << std::endl; + Log::e("invalid direction jump type: {}", jump_str); } } } @@ -463,7 +461,7 @@ parseBars(const pugi::xml_node &bars_node) if (auto clef_type = Util::toEnum(clef_name)) bar.myClefType = *clef_type; else - std::cerr << "Invalid clef type: " << clef_name << std::endl; + Log::e("invalid clef type: {}", clef_name); // TODO - import the 'Ottavia' key if the clef has 8va, etc @@ -507,7 +505,7 @@ parseBeats(const pugi::xml_node &beats_node, Gp7::Version version) { beat.myOttavia = Util::toEnum(ottavia); if (!beat.myOttavia) - std::cerr << "Invalid ottavia value: " << ottavia << std::endl; + Log::e("invalid ottavia value: {}", ottavia); } beat.myFreeText = node.child_value("FreeText"); @@ -654,8 +652,7 @@ parseNotes(const pugi::xml_node ¬es_node) note.myHarmonic = Util::toEnum(harmonic_type); if (!note.myHarmonic) { - std::cerr << "Unknown harmonic type: " << harmonic_type - << std::endl; + Log::e("unknown harmonic type: {}", harmonic_type); } } else if (name == "Bended") @@ -718,8 +715,7 @@ parseNotes(const pugi::xml_node ¬es_node) note.myLeftFinger = Util::toEnum(finger_type); if (!note.myLeftFinger) { - std::cerr << "Unknown finger type: " << finger_type - << std::endl; + Log::e("unknown finger type: {}", finger_type); } } diff --git a/source/formats/gp7/to_pt2.cpp b/source/formats/gp7/to_pt2.cpp index 6f8635e0..3bc38941 100644 --- a/source/formats/gp7/to_pt2.cpp +++ b/source/formats/gp7/to_pt2.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -182,7 +183,7 @@ getHarmonicPitchOffset(double harmonic_fret) } else { - std::cerr << "Unexpected harmonic type" << std::endl; + Log::e("unexpected harmonic type"); return 12; } } diff --git a/source/formats/powertab_old/powertabdocument/macros.cpp b/source/formats/powertab_old/powertabdocument/macros.cpp index a3ba272b..a3178c51 100644 --- a/source/formats/powertab_old/powertabdocument/macros.cpp +++ b/source/formats/powertab_old/powertabdocument/macros.cpp @@ -14,14 +14,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + #include "macros.h" + +#include + #include // std::transform #include void logToDebug(const std::string& msg, const std::string& file, int line) { - std::cerr << msg << std::endl << file << std::endl << "Line: " << line << std::endl; + Log::e("{}", msg); + Log::e("{}", file); + Log::e("line: {}", line); } std::string ArabicToRoman(uint32_t number, bool upperCase) From bd117d805bb23c5c110156367578972452419e9f Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 22:00:09 -0500 Subject: [PATCH 05/19] Replace std::cerr with Log::e in midioutputdevice.cpp --- source/audio/midioutputdevice.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/audio/midioutputdevice.cpp b/source/audio/midioutputdevice.cpp index b4aed5a4..1c8d4273 100644 --- a/source/audio/midioutputdevice.cpp +++ b/source/audio/midioutputdevice.cpp @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + #include "midioutputdevice.h" #include @@ -22,6 +22,7 @@ #include #include #include +#include #ifdef __APPLE__ #include "midisoftwaresynth.h" @@ -38,7 +39,7 @@ MidiOutputDevice::MidiOutputDevice() : myMidiOut(nullptr) } catch (std::exception &e) { - std::cerr << e.what() << std::endl; + Log::e("could not start midisoftwaresynth: {}", e.what()); }; #endif From 5a33e411a463b8bf89a58bcab2420bf491c46c1d Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:50:20 -0500 Subject: [PATCH 06/19] Replace qDebug with Log::d in app/... --- source/app/powertabeditor.cpp | 16 +++++++--------- source/app/scorearea.cpp | 16 ++++++++++------ source/app/settingsmanager.cpp | 4 +++- source/app/tuningdictionary.cpp | 7 ++++--- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/source/app/powertabeditor.cpp b/source/app/powertabeditor.cpp index f6c952d2..ae81c76a 100644 --- a/source/app/powertabeditor.cpp +++ b/source/app/powertabeditor.cpp @@ -149,6 +149,7 @@ #include #include +#include #include #include @@ -260,14 +261,14 @@ void PowerTabEditor::openFile(QString filename) int validationResult = myDocumentManager->findDocument(path); if (validationResult > -1) { - qDebug() << "File: " << filename << " is already open"; + Log::d("file: {} is already open", filename.toStdString()); myTabWidget->setCurrentIndex(validationResult); return; } auto start = std::chrono::high_resolution_clock::now(); - qDebug() << "Opening file: " << filename; + Log::d("opening file: {}", filename.toStdString()); QFileInfo fileInfo(filename); std::optional format = myFileFormatManager->findFormat( @@ -285,9 +286,8 @@ void PowerTabEditor::openFile(QString filename) Document &doc = myDocumentManager->addDocument(*mySettingsManager); myFileFormatManager->importFile(doc.getScore(), path, *format); auto end = std::chrono::high_resolution_clock::now(); - qDebug() << "File loaded in" - << std::chrono::duration_cast(end - start) .count() - << "ms"; + + Log::d("file loaded in: {}ms", std::chrono::duration_cast(end - start).count()); doc.setFilename(path); setPreviousDirectory(filename); @@ -3616,7 +3616,7 @@ void PowerTabEditor::setPreviousDirectory(const QString &fileName) void PowerTabEditor::setupNewTab() { auto start = std::chrono::high_resolution_clock::now(); - qDebug() << "Tab creation started ..."; + Log::d("tab creation started..."); Q_ASSERT(myDocumentManager->hasOpenDocuments()); Document &doc = myDocumentManager->getCurrentDocument(); @@ -3788,9 +3788,7 @@ void PowerTabEditor::setupNewTab() scorearea->setFocus(); auto end = std::chrono::high_resolution_clock::now(); - qDebug() << "Tab opened in" - << std::chrono::duration_cast( - end - start).count() << "ms"; + Log::d("tab opened in: {}ms", std::chrono::duration_cast(end - start).count()); } namespace diff --git a/source/app/scorearea.cpp b/source/app/scorearea.cpp index 4ae21258..3a9c6949 100644 --- a/source/app/scorearea.cpp +++ b/source/app/scorearea.cpp @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + #include "scorearea.h" #include @@ -32,6 +32,7 @@ #include #include #include +#include void ScoreArea::Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { @@ -114,7 +115,7 @@ void ScoreArea::renderDocument(const Document &document) #endif std::vector> tasks; const int work_size = myRenderedSystems.size() / num_threads; - qDebug() << "Using" << num_threads << "worker thread(s)"; + Log::d("using {} worker thread(s)", num_threads); for (int i = 0; i < num_threads; ++i) { @@ -162,10 +163,13 @@ void ScoreArea::renderDocument(const Document &document) myScene.setSceneRect(myScene.itemsBoundingRect()); auto end = std::chrono::high_resolution_clock::now(); - qDebug() << "Score rendered in" - << std::chrono::duration_cast( - end - start).count() << "ms"; - qDebug() << "Rendered " << myScene.items().size() << "items"; + + namespace sc = std::chrono; + const auto time_elapsed = static_cast + (sc::duration_cast(end - start).count()); + + Log::d("score rendered in {} ms", time_elapsed); + Log::d("rendered {} items", myScene.items().size()); } void ScoreArea::redrawSystem(int index) diff --git a/source/app/settingsmanager.cpp b/source/app/settingsmanager.cpp index b1083223..be0f5bcc 100644 --- a/source/app/settingsmanager.cpp +++ b/source/app/settingsmanager.cpp @@ -17,6 +17,8 @@ #include "settingsmanager.h" +#include + #include #include @@ -47,7 +49,7 @@ void SettingsManager::load(const std::filesystem::path &dir) } catch (const std::exception &e) { - std::cerr << "Error loading " << path << ": " << e.what() << std::endl; + Log::e("error loading {}: {}", path.generic_string(), e.what()); } #endif } diff --git a/source/app/tuningdictionary.cpp b/source/app/tuningdictionary.cpp index 35b8be2e..284e876b 100644 --- a/source/app/tuningdictionary.cpp +++ b/source/app/tuningdictionary.cpp @@ -26,6 +26,7 @@ #include #include #include +#include static const char *theTuningDictFilename = "tunings.json"; @@ -70,11 +71,11 @@ TuningDictionary::load() if (entries.empty()) { - std::cerr << "Could not locate tuning dictionary." << std::endl; - std::cerr << "Candidate paths:" << std::endl; + Log::e("could not locate tuning dictionary"); + Log::e("candidate paths: "); for (std::filesystem::path dir : Paths::getDataDirs()) - std::cerr << (dir / theTuningDictFilename) << std::endl; + Log::e(" - {}", (dir / theTuningDictFilename).generic_string()); } return entries; From ab56bc3646a4ba448c6884d407e6cb03bfddc75d Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 22:06:36 -0500 Subject: [PATCH 07/19] Replace qDebug with Log::d in build/main.cpp --- source/build/main.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/source/build/main.cpp b/source/build/main.cpp index 882dc12f..2050b416 100644 --- a/source/build/main.cpp +++ b/source/build/main.cpp @@ -14,12 +14,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + #include #include #include #include #include +#include #include #include #include @@ -60,7 +61,7 @@ static void displayError(const std::string &reason) // If there is no QApplication instance, something went seriously wrong // during startup - just dump the error to the console. if (!QApplication::instance()) - std::cerr << message << std::endl; + Log::e("{}", message); else { CrashDialog dialog(QString::fromStdString(message), @@ -135,23 +136,25 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, QTranslator &ptb_translator) { QLocale locale; - qDebug() << "Finding translations for locale" << locale - << "with UI languages" << locale.uiLanguages(); + + Log::d("finding translations for locale:"); + for (const auto& loc : locale.uiLanguages()) + Log::d(" locale: {}", loc.toStdString()); for (auto &&path : Paths::getTranslationDirs()) { QString dir = Paths::toQString(path); - qDebug() << " - Checking" << dir; + Log::d(" - checking: {}", path.generic_string()); if (ptb_translator.isEmpty() && ptb_translator.load(locale, QStringLiteral("powertabeditor"), QStringLiteral("_"), dir)) { #if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) - qDebug() << "Loaded application translations from" - << ptb_translator.filePath(); + Log::d("loaded application translations from: {}", + ptb_translator.filePath().toStdString()); #else - qDebug() << "Loaded application translations"; + Log::d("loaded application translations"); #endif app.installTranslator(&ptb_translator); } @@ -161,10 +164,10 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, QStringLiteral("_"), dir)) { #if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) - qDebug() << "Loaded Qt base translations from" - << qt_translator.filePath(); + Log::d("loaded qt base translations from {}", + qt_translator.filePath().toStdString()); #else - qDebug() << "Loaded Qt base translations"; + Log::d("loaded Qt base translations"); #endif app.installTranslator(&qt_translator); } @@ -173,6 +176,10 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, int main(int argc, char *argv[]) { + Log::init(Log::Level::Debug, Paths::getLogPath()); + + Log::d("started powertab editor ({})", AppInfo::APPLICATION_VERSION); + // Register handlers for unhandled exceptions and segmentation faults. std::set_terminate(terminateHandler); std::signal(SIGSEGV, signalHandler); From 26cf8880bbc3ce705c5313c230231d55cbf6a394 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 23 Dec 2023 21:59:18 -0500 Subject: [PATCH 08/19] Show logs in infodialog's edit box Users should be able to copy the logs from the infodialog box, along with version information for slightly easier troubleshooting. --- source/dialogs/infodialog.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/source/dialogs/infodialog.cpp b/source/dialogs/infodialog.cpp index c3aec5fa..827f38a0 100644 --- a/source/dialogs/infodialog.cpp +++ b/source/dialogs/infodialog.cpp @@ -19,6 +19,9 @@ #include "ui_infodialog.h" #include +#include + +#include #include @@ -47,9 +50,15 @@ void InfoDialog::setInfo() tr("You can grab development binaries here:\n" " https://github.com/powertab/powertabeditor/actions"); - const auto message = QString("%1\n\n%2").arg( + const auto message = QString( + "%1\n\n" + "%2\n\n" + "===============================\n" + "Logs:\n%3" + ).arg( qname, - developmentBinaryLocation + developmentBinaryLocation, + QString::fromStdString(Log::all()) ); ui->appInfo->setText(message); From 34f0088c8c1697731b69a9c390a69aae01bea0d7 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:23:40 -0500 Subject: [PATCH 09/19] Add fmt for Linux CI/CD build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c0296d6..b651dcf0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Apt Dependencies - run: sudo apt update && sudo apt install ninja-build qtbase5-dev qttools5-dev libboost-dev libboost-date-time-dev libboost-iostreams-dev nlohmann-json3-dev libasound2-dev librtmidi-dev libminizip-dev doctest-dev + run: sudo apt update && sudo apt install ninja-build qtbase5-dev qttools5-dev libboost-dev libboost-date-time-dev libboost-iostreams-dev nlohmann-json3-dev libasound2-dev librtmidi-dev libminizip-dev doctest-dev libfmt-dev - name: Install Other Dependencies run: vcpkg install pugixml - name: Create Build Directory From e72a569fdf2475bc6af95b69d361f35556cd1a5f Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:33:39 -0500 Subject: [PATCH 10/19] Add fmt for macOS CI/CD build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b651dcf0..aaa40677 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: - name: Install Dependencies # CMake 3.17 is already installed - run: brew install boost doctest minizip ninja nlohmann-json pugixml qt5 pugixml rtmidi + run: brew install boost doctest minizip ninja nlohmann-json pugixml qt5 pugixml rtmidi fmt - name: Generate Project run: cmake -S ${GITHUB_WORKSPACE} -B ${{runner.workspace}}/build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake - name: Build From 64307adbec5e43cbf6334c09128fa6f5c4bdea25 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:33:57 -0500 Subject: [PATCH 11/19] Add fmt for Windows CI/CD build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aaa40677..9e397a6b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -98,7 +98,7 @@ jobs: run: echo "::set-output name=VERSION_ID::$(git describe --tags --long --always)" - name: Install Dependencies - run: vcpkg install --triplet ${{ matrix.arch }}-windows boost-algorithm boost-date-time boost-endian boost-functional boost-iostreams boost-range boost-rational boost-signals2 boost-stacktrace doctest minizip nlohmann-json pugixml + run: vcpkg install --triplet ${{ matrix.arch }}-windows boost-algorithm boost-date-time boost-endian boost-functional boost-iostreams boost-range boost-rational boost-signals2 boost-stacktrace doctest minizip nlohmann-json pugixml fmt # Building Qt via vcpkg would take a while ... - name: Install Qt From 3ce38cb035ed8f0ad206fd1c8f40b3f62b4792e7 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:26:26 -0500 Subject: [PATCH 12/19] Add fmtlib dependency mention in README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c4991e74..addb887e 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Power Tab Editor 2.0 - A powerful cross platform guitar tablature viewer and edi * [pugixml](https://pugixml.org/) * [minizip](https://github.com/madler/zlib) * [doctest](https://github.com/onqtam/doctest) +* [fmtlib](https://github.com/fmtlib/fmt) * (Linux only) - ALSA library (e.g. `libasound2-dev`) * (Linux only) - MIDI sequencer (e.g. `timidity-daemon`) * A compiler with C++17 support. From 2ca68d1c5b64722347ad7d60bf772026bb318454 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:26:54 -0500 Subject: [PATCH 13/19] Mention C++20 compiler requirement in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index addb887e..49bf94be 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Power Tab Editor 2.0 - A powerful cross platform guitar tablature viewer and edi * [fmtlib](https://github.com/fmtlib/fmt) * (Linux only) - ALSA library (e.g. `libasound2-dev`) * (Linux only) - MIDI sequencer (e.g. `timidity-daemon`) -* A compiler with C++17 support. +* A compiler with C++20 support. ### Building: #### Windows: From 5ba2a3d4b201c7a561aca10ca03acb5e0bf0b399 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:29:15 -0500 Subject: [PATCH 14/19] Add fmtlib (>=8.0.0) in appropriate CMake files --- cmake/PTE_ThirdParty.cmake | 1 + cmake/third_party/fmt.cmake | 1 + source/util/CMakeLists.txt | 2 +- test/CMakeLists.txt | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 cmake/third_party/fmt.cmake diff --git a/cmake/PTE_ThirdParty.cmake b/cmake/PTE_ThirdParty.cmake index ad5da565..c02c0bb0 100644 --- a/cmake/PTE_ThirdParty.cmake +++ b/cmake/PTE_ThirdParty.cmake @@ -11,3 +11,4 @@ include ( third_party/nlohmann_json ) include ( third_party/pugixml ) include ( third_party/Qt ) include ( third_party/rtmidi ) +include ( third_party/fmt ) diff --git a/cmake/third_party/fmt.cmake b/cmake/third_party/fmt.cmake new file mode 100644 index 00000000..31a2e99a --- /dev/null +++ b/cmake/third_party/fmt.cmake @@ -0,0 +1 @@ +find_package ( fmt 8.0.0 REQUIRED ) diff --git a/source/util/CMakeLists.txt b/source/util/CMakeLists.txt index 038bd08f..b2037adf 100644 --- a/source/util/CMakeLists.txt +++ b/source/util/CMakeLists.txt @@ -64,5 +64,5 @@ pte_library( HEADERS ${headers} DEPENDS PUBLIC Boost::headers - PRIVATE nlohmann_json::nlohmann_json ${platform_depends} + PRIVATE fmt::fmt nlohmann_json::nlohmann_json ${platform_depends} ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5ff47c82..3524e10c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -188,6 +188,7 @@ pte_executable( PCH_EXCLUDE test_main.cpp DEPENDS doctest::doctest + fmt::fmt pteapp rtmidi::rtmidi ) From f5da24a1187a70cc352294e4df309fba1419501b Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Mon, 25 Dec 2023 12:29:56 -0500 Subject: [PATCH 15/19] Replace use of std::format with fmt:format in logger I'm leaving this commit here instead of squashing, so that in the future, we could technically just reverse this commit. --- source/util/log.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/source/util/log.h b/source/util/log.h index 05f39716..91e7af1d 100644 --- a/source/util/log.h +++ b/source/util/log.h @@ -23,10 +23,12 @@ #include #include #include -#include - +#include #include +#include +/* TODO: fmtlib: replace with std, when all compilers support `format'. */ +#include namespace Log { @@ -48,6 +50,8 @@ extern std::filesystem::path logPath; extern std::optional logFile; +extern std::mutex lock; + void init(enum Level lvl, std::filesystem::path logPath); /** @@ -58,6 +62,8 @@ std::string all(); template void backend(enum Level level, std::string_view fmt, Args&&... args) { + std::lock_guard _guard(Log::lock); + /* formats a timestamp in a rfc3339 fashion; I believe the system clock * defaults to epoch, which should be UTC, but I might be wrong. */ const auto timestamp_now_str_fn = []() -> std::string { @@ -85,7 +91,7 @@ void backend(enum Level level, std::string_view fmt, Args&&... args) std::cout << timestamp_now_str_fn() << ": " << level_str_fn(level) << ": " - << std::vformat(fmt, std::make_format_args(args...)) + << fmt::vformat(fmt, fmt::make_format_args(args...)) << std::endl; if (!logFile) { @@ -97,10 +103,10 @@ void backend(enum Level level, std::string_view fmt, Args&&... args) logFile.value() << timestamp_now_str_fn() << ": " << level_str_fn(level) << ": " - << std::vformat(fmt, std::make_format_args(args...)) + << fmt::vformat(fmt, fmt::make_format_args(args...)) << std::endl; } else { - std::cout << std::format( + std::cout << fmt::format( "{} {} could not open file at ({})", timestamp_now_str_fn(), level_str_fn(Level::Warning), From b69149bdcc5421d9773b668bff38c0b8f40c8af3 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sat, 30 Dec 2023 14:35:48 -0500 Subject: [PATCH 16/19] Implement poorman's log rotating This implements a very simple circular buffer that will take the last N lines in a log file, and keep those instead. Right now the maximum N is hardcoded, but the code is written in a fashion that we can revisit some of this, and add toggles where we see fit (for example in a settings dialog or so). I tested out the functionality this way: $ yes some-log-line | nl | head -5000 > log.txt And then running PTE2 with a max log size of 1000, I noticed the logfile like so: $ head -10 log.txt 4001 some-log-line 4002 some-log-line 4003 some-log-line 4004 some-log-line 4005 some-log-line 4006 some-log-line 4007 some-log-line 4008 some-log-line 4009 some-log-line 4010 some-log-line and the bottom: $ tail -10 log.txt 2023-12-30T14:38:32Z: [debug]: finding translations for locale: 2023-12-30T14:38:32Z: [debug]: locale: en-US 2023-12-30T14:38:32Z: [debug]: locale: en 2023-12-30T14:38:32Z: [debug]: locale: en-Latn-US 2023-12-30T14:38:32Z: [debug]: - checking: /home/psyomn/.local/share/powertab/powertabeditor/translations 2023-12-30T14:38:32Z: [debug]: - checking: /usr/local/share/powertab/powertabeditor/translations 2023-12-30T14:38:32Z: [debug]: - checking: /usr/share/powertab/powertabeditor/translations 2023-12-30T14:38:32Z: [debug]: - checking: /home/psyomn/programming/cc/fork/powertabeditor/build/bin/data/translations 2023-12-30T14:38:32Z: [debug]: - checking: /usr/share/qt/translations 2023-12-30T14:38:32Z: [debug]: loaded qt base translations from /usr/share/qt/translations/qtbase_en.qm and last checks: $ nl log.txt | head -1 1 4001 some-log-line $ nl log.txt | tail -1 1011 2023-12-30T14:38:32Z: [debug]: loaded qt base translations from /usr/share/qt/translations/qtbase_en.qm --- source/util/log.cpp | 37 +++++++++++++++++++++++++++++++++++++ source/util/log.h | 8 ++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/source/util/log.cpp b/source/util/log.cpp index 9db0dc35..338c7b8e 100644 --- a/source/util/log.cpp +++ b/source/util/log.cpp @@ -25,10 +25,47 @@ std::filesystem::path logPath; std::mutex lock; +void trim(const unsigned int count) +{ + /* a poorman's circular buffer that keeps the last 'count' log lines. */ + std::vector v(count); + + std::ifstream input(logPath); + std::size_t index = 0; + std::string s; + while (std::getline(input, s)) { + v[index % count] = s; + index++; + } + + input.close(); + + // required because of len/cap discrepancy in vector used as a circular + // buffer. + const auto upto = (count == index ? index : count); + + std::ofstream output(logPath, std::ios::trunc); + if (output.good()) { + for (size_t i = 0; i < upto; ++i) + output << v[(index + i) % count] << std::endl; + } else { + std::cerr << "could not open log file for trimming" << std::endl; + } + output.close(); +} + void init(enum Level lvl, std::filesystem::path lp) { FilterLevel = lvl; logPath = lp; + + // keep only the last X lines + trim(1000); + + if (!logFile) { + const auto mode = std::ios_base::out | std::ios_base::app; + logFile = std::ofstream(logPath, mode); + } } std::string all() diff --git a/source/util/log.h b/source/util/log.h index 91e7af1d..0c65aa4a 100644 --- a/source/util/log.h +++ b/source/util/log.h @@ -24,8 +24,8 @@ #include #include #include -#include #include +#include /* TODO: fmtlib: replace with std, when all compilers support `format'. */ #include @@ -59,6 +59,7 @@ void init(enum Level lvl, std::filesystem::path logPath); */ std::string all(); + template void backend(enum Level level, std::string_view fmt, Args&&... args) { @@ -94,11 +95,6 @@ void backend(enum Level level, std::string_view fmt, Args&&... args) << fmt::vformat(fmt, fmt::make_format_args(args...)) << std::endl; - if (!logFile) { - const auto mode = std::ios_base::out | std::ios_base::app; - logFile = std::ofstream(logPath, mode); - } - if (logFile.value().good()) { logFile.value() << timestamp_now_str_fn() << ": " From aabde63ab83d628881c57a1f09e67a4431a2e19e Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sun, 7 Jan 2024 01:14:23 -0500 Subject: [PATCH 17/19] Add format wrapper for QString This adds a custom format wrapper for QString objects, so that they can be logged directly. --- source/app/log.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 source/app/log.h diff --git a/source/app/log.h b/source/app/log.h new file mode 100644 index 00000000..50c39fad --- /dev/null +++ b/source/app/log.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Simon Symeonidis + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ + +#ifndef APP_LOG_H +#define APP_LOG_H + +#include + +#include +#include +#include +#include + +/* Adapted from: https://fmt.dev/latest/api.html#udt */ +template <> +struct fmt::formatter : fmt::formatter +{ + auto format(const QString& qstr, fmt::format_context& ctx) const { + return fmt::formatter::format(qstr.toStdString(), ctx); + } +}; + +#endif From 5bb4293c1bd6615b994bdb0eede25c1230664b21 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sun, 7 Jan 2024 01:19:36 -0500 Subject: [PATCH 18/19] Use implicit QString formatter in logger --- source/app/CMakeLists.txt | 1 + source/app/powertabeditor.cpp | 6 +++--- source/build/main.cpp | 8 ++++---- source/util/log.h | 5 +++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/source/app/CMakeLists.txt b/source/app/CMakeLists.txt index 68de5d64..6a5cc02f 100644 --- a/source/app/CMakeLists.txt +++ b/source/app/CMakeLists.txt @@ -23,6 +23,7 @@ set( headers command.h documentmanager.h paths.h + log.h powertabeditor.h recentfiles.h scorearea.h diff --git a/source/app/powertabeditor.cpp b/source/app/powertabeditor.cpp index ae81c76a..b596bfb7 100644 --- a/source/app/powertabeditor.cpp +++ b/source/app/powertabeditor.cpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -149,7 +150,6 @@ #include #include -#include #include #include @@ -261,14 +261,14 @@ void PowerTabEditor::openFile(QString filename) int validationResult = myDocumentManager->findDocument(path); if (validationResult > -1) { - Log::d("file: {} is already open", filename.toStdString()); + Log::d("file: {} is already open", filename); myTabWidget->setCurrentIndex(validationResult); return; } auto start = std::chrono::high_resolution_clock::now(); - Log::d("opening file: {}", filename.toStdString()); + Log::d("opening file: {}", filename); QFileInfo fileInfo(filename); std::optional format = myFileFormatManager->findFormat( diff --git a/source/build/main.cpp b/source/build/main.cpp index 2050b416..a741a9e5 100644 --- a/source/build/main.cpp +++ b/source/build/main.cpp @@ -16,11 +16,11 @@ */ #include +#include #include #include #include #include -#include #include #include #include @@ -139,7 +139,7 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, Log::d("finding translations for locale:"); for (const auto& loc : locale.uiLanguages()) - Log::d(" locale: {}", loc.toStdString()); + Log::d(" locale: {}", loc); for (auto &&path : Paths::getTranslationDirs()) { @@ -152,7 +152,7 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, { #if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) Log::d("loaded application translations from: {}", - ptb_translator.filePath().toStdString()); + ptb_translator.filePath()); #else Log::d("loaded application translations"); #endif @@ -165,7 +165,7 @@ loadTranslations(QApplication &app, QTranslator &qt_translator, { #if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) Log::d("loaded qt base translations from {}", - qt_translator.filePath().toStdString()); + qt_translator.filePath()); #else Log::d("loaded Qt base translations"); #endif diff --git a/source/util/log.h b/source/util/log.h index 0c65aa4a..3dbe7101 100644 --- a/source/util/log.h +++ b/source/util/log.h @@ -14,8 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef APP_LOG_H -#define APP_LOG_H + +#ifndef UTIL_LOG_H +#define UTIL_LOG_H #include #include From e11f0b43806eac8ebb5b6a85403ac34feae9e683 Mon Sep 17 00:00:00 2001 From: Simon Symeonidis Date: Sun, 7 Jan 2024 16:35:07 -0500 Subject: [PATCH 19/19] Hide logging includes by limiting template use --- source/util/log.cpp | 61 ++++++++++++++++++++++++++++++++++++- source/util/log.h | 73 +++++++-------------------------------------- 2 files changed, 71 insertions(+), 63 deletions(-) diff --git a/source/util/log.cpp b/source/util/log.cpp index 338c7b8e..51147b1b 100644 --- a/source/util/log.cpp +++ b/source/util/log.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2021-2023 Simon Symeonidis +/* Copyright (C) 2021-2024 Simon Symeonidis * * 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 @@ -15,6 +15,15 @@ */ #include "log.h" +#include +#include +#include /* put_time */ +#include +#include +#include +#include +#include + namespace Log { enum Level FilterLevel = Level::Debug; @@ -68,6 +77,56 @@ void init(enum Level lvl, std::filesystem::path lp) } } +void emitLog(enum Level level, const std::string& str) +{ + std::lock_guard _guard(Log::lock); + + /* formats a timestamp in a rfc3339 fashion; I believe the system clock + * defaults to epoch, which should be UTC, but I might be wrong. */ + const auto timestamp_now_str_fn = []() -> std::string { + auto now = std::chrono::system_clock::now(); + auto now_t = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&now_t), "%Y-%m-%dT%H:%M:%SZ"); + return ss.str(); + }; + + const auto level_str_fn = [](enum Level l) noexcept { + switch (l) { + case Level::Debug: return "[debug]"; + case Level::Info: return "[info]"; + case Level::Notify: return "[notify]"; + case Level::Warning: return "[warn]"; + case Level::Error: return "[error]"; + /* unreachable */ + default: + return "unknown"; + } + }; + + std::cout + << timestamp_now_str_fn() << ": " + << level_str_fn(level) << ": " + << str + << std::endl; + + if (logFile.value().good()) { + logFile.value() + << timestamp_now_str_fn() << ": " + << level_str_fn(level) << ": " + << str + << std::endl; + } else { + std::cout << fmt::format( + "{} {} could not open file at ({})", + timestamp_now_str_fn(), + level_str_fn(Level::Warning), + logPath.generic_string() + ); + } +} + std::string all() { std::stringstream ss; diff --git a/source/util/log.h b/source/util/log.h index 3dbe7101..78543410 100644 --- a/source/util/log.h +++ b/source/util/log.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Simon Symeonidis + * Copyright (C) 2021-2024 Simon Symeonidis * * 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 @@ -18,15 +18,8 @@ #ifndef UTIL_LOG_H #define UTIL_LOG_H -#include -#include -#include /* put_time */ -#include -#include #include -#include -#include -#include +#include /* TODO: fmtlib: replace with std, when all compilers support `format'. */ #include @@ -47,12 +40,6 @@ enum class Level { */ extern enum Level CurrentLevel; -extern std::filesystem::path logPath; - -extern std::optional logFile; - -extern std::mutex lock; - void init(enum Level lvl, std::filesystem::path logPath); /** @@ -60,56 +47,18 @@ void init(enum Level lvl, std::filesystem::path logPath); */ std::string all(); +/** + * takes a string, and emits it. Our emitters consider only a log file on the + * platform, and print to console if there is a problem if the file can not be + * opened. The emitters will also add the current timestamp, and the log level + * of the message. + */ +void emitLog(enum Level level, const std::string& str); template -void backend(enum Level level, std::string_view fmt, Args&&... args) +void backend(const enum Level level, std::string_view fmt, Args&&... args) { - std::lock_guard _guard(Log::lock); - - /* formats a timestamp in a rfc3339 fashion; I believe the system clock - * defaults to epoch, which should be UTC, but I might be wrong. */ - const auto timestamp_now_str_fn = []() -> std::string { - auto now = std::chrono::system_clock::now(); - auto now_t = std::chrono::system_clock::to_time_t(now); - - std::stringstream ss; - ss << std::put_time(std::localtime(&now_t), "%Y-%m-%dT%H:%M:%SZ"); - return ss.str(); - }; - - const auto level_str_fn = [](enum Level l) noexcept { - switch (l) { - case Level::Debug: return "[debug]"; - case Level::Info: return "[info]"; - case Level::Notify: return "[notify]"; - case Level::Warning: return "[warn]"; - case Level::Error: return "[error]"; - /* unreachable */ - default: - return "unknown"; - } - }; - - std::cout - << timestamp_now_str_fn() << ": " - << level_str_fn(level) << ": " - << fmt::vformat(fmt, fmt::make_format_args(args...)) - << std::endl; - - if (logFile.value().good()) { - logFile.value() - << timestamp_now_str_fn() << ": " - << level_str_fn(level) << ": " - << fmt::vformat(fmt, fmt::make_format_args(args...)) - << std::endl; - } else { - std::cout << fmt::format( - "{} {} could not open file at ({})", - timestamp_now_str_fn(), - level_str_fn(Level::Warning), - logPath.generic_string() - ); - } + emitLog(level, fmt::vformat(fmt, fmt::make_format_args(args...))); } template