From 18c512645966411464a37c4ec6fce8d2c4a697ee Mon Sep 17 00:00:00 2001 From: Hleb Valoshka <375gnu@gmail.com> Date: Mon, 4 Jan 2021 21:56:01 +0300 Subject: [PATCH] Copy console output into a file Currently Qt frontend only, can be activated by passing -l/--log option: celestia-qt --log /tmp/celestia.log --- src/celestia/celestiacore.cpp | 21 +++++++- src/celestia/celestiacore.h | 6 +++ src/celestia/qt/qtappwin.cpp | 11 +++- src/celestia/qt/qtappwin.h | 3 +- src/celestia/qt/qtmain.cpp | 13 ++++- src/celutil/tee.h | 96 +++++++++++++++++++++++++++++++++++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/celutil/tee.h diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index d593c3522f..7f35fb407f 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -149,7 +149,8 @@ CelestiaCore::CelestiaCore() : #endif m_scriptMaps(new ScriptMaps()), oldFOV(stdFOV), - console(new Console(*renderer, 200, 120)) + console(new Console(*renderer, 200, 120)), + m_tee(std::cout, std::cerr) { for (int i = 0; i < KeyCount; i++) @@ -172,6 +173,9 @@ CelestiaCore::~CelestiaCore() delete timer; delete renderer; + + if (m_logfile.good()) + m_logfile.close(); } void CelestiaCore::readFavoritesFile() @@ -4666,3 +4670,18 @@ bool CelestiaCore::saveScreenShot(const fs::path& filename, ContentType type) co return false; } + +void CelestiaCore::setLogFile(fs::path &fn) +{ + m_logfile = std::ofstream(fn.string()); + if (m_logfile.good()) + { + m_tee = teestream(m_logfile, *console); + clog.rdbuf(m_tee.rdbuf()); + cerr.rdbuf(m_tee.rdbuf()); + } + else + { + fmt::fprintf(cerr, "Unable to open log file %s\n", fn); + } +} diff --git a/src/celestia/celestiacore.h b/src/celestia/celestiacore.h index b27ac577f8..a6e1838f50 100644 --- a/src/celestia/celestiacore.h +++ b/src/celestia/celestiacore.h @@ -10,6 +10,7 @@ #ifndef _CELESTIACORE_H_ #define _CELESTIACORE_H_ +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include "configfile.h" #include "favorites.h" #include "destination.h" @@ -299,6 +301,8 @@ class CelestiaCore // : public Watchable void notifyWatchers(int); + void setLogFile(fs::path&); + class Alerter { public: @@ -506,6 +510,8 @@ class CelestiaCore // : public Watchable string selectionNames; std::unique_ptr console; + std::ofstream m_logfile; + teestream m_tee; #ifdef CELX friend View* getViewByObserver(CelestiaCore*, Observer*); diff --git a/src/celestia/qt/qtappwin.cpp b/src/celestia/qt/qtappwin.cpp index 0546fc30ba..e2d32508e8 100644 --- a/src/celestia/qt/qtappwin.cpp +++ b/src/celestia/qt/qtappwin.cpp @@ -194,7 +194,8 @@ CelestiaAppWindow::~CelestiaAppWindow() void CelestiaAppWindow::init(const QString& qConfigFileName, - const QStringList& qExtrasDirectories) + const QStringList& qExtrasDirectories, + const QString& logFilename) { QString celestia_data_dir = QString::fromLocal8Bit(::getenv("CELESTIA_DATA_DIR")); @@ -236,6 +237,12 @@ void CelestiaAppWindow::init(const QString& qConfigFileName, setWindowIcon(QIcon(":/icons/celestia.png")); + if (!logFilename.isEmpty()) + { + fs::path fn(logFilename.toStdString()); + m_appCore->setLogFile(fn); + } + if (!m_appCore->initSimulation(configFileName, extrasDirectories, progress)) @@ -261,7 +268,7 @@ void CelestiaAppWindow::init(const QString& qConfigFileName, if (!gl::init() || !gl::checkVersion(gl::GL_2_1)) { QMessageBox::critical(0, "Celestia", _("Celestia was unable to initialize OpenGLĀ 2.1.")); - exit(1); + exit(1); } m_appCore->setCursorHandler(glWidget); diff --git a/src/celestia/qt/qtappwin.h b/src/celestia/qt/qtappwin.h index b82e795335..f72b8658b6 100644 --- a/src/celestia/qt/qtappwin.h +++ b/src/celestia/qt/qtappwin.h @@ -41,7 +41,8 @@ class CelestiaAppWindow : public QMainWindow, public CelestiaCore::ContextMenuHa ~CelestiaAppWindow(); void init(const QString& configFileName, - const QStringList& extrasDirectories); + const QStringList& extrasDirectories, + const QString& logFilename); void readSettings(); void writeSettings(); diff --git a/src/celestia/qt/qtmain.cpp b/src/celestia/qt/qtmain.cpp index 69f2c337cf..381287ad62 100644 --- a/src/celestia/qt/qtmain.cpp +++ b/src/celestia/qt/qtmain.cpp @@ -39,6 +39,7 @@ using namespace std; static bool startFullscreen = false; static bool runOnce = false; static QString startURL; +static QString logFilename; static QString startDirectory; static QString startScript; static QStringList extrasDirectories; @@ -111,7 +112,7 @@ int main(int argc, char *argv[]) QObject::connect(&window, SIGNAL(progressUpdate(const QString&, int, const QColor&)), &splash, SLOT(showMessage(const QString&, int, const QColor&))); - window.init(configFileName, extrasDirectories); + window.init(configFileName, extrasDirectories, logFilename); window.show(); splash.finish(&window); @@ -199,6 +200,16 @@ bool ParseCommandLine() { skipSplashScreen = true; } + else if (args.at(i) == "-l" || args.at(i) == "--log") + { + if (isLastArg) + { + CommandLineError("A filename expected after --log/-l"); + return false; + } + i++; + logFilename = args.at(i); + } else { string buf = fmt::sprintf("Invalid command line option '%s'", args.at(i).toUtf8().data()); diff --git a/src/celutil/tee.h b/src/celutil/tee.h new file mode 100644 index 0000000000..270690a61f --- /dev/null +++ b/src/celutil/tee.h @@ -0,0 +1,96 @@ +// tee.h +// +// Copyright (C) 2009, Thomas Guest +// 2021, the Celestia Development Team +// +// Tee stream implementation based on Thomas Guest post published at +// http://wordaligned.org/articles/cpp-streambufs +// +// This code is placed in the public domain. + +#pragma once + +#include + +template > +class basic_teebuf : + public std::basic_streambuf +{ + public: + typedef typename traits::int_type int_type; + typedef std::basic_streambuf streambuf_type; + + // Construct a streambuf which tees output to both input + // streambufs. + basic_teebuf(streambuf_type *sb1, + streambuf_type *sb2) : + sb1(sb1), + sb2(sb2) + {} + + basic_teebuf() = delete; + ~basic_teebuf() = default; + basic_teebuf(const basic_teebuf&) = default; + basic_teebuf(basic_teebuf&&) = default; + basic_teebuf& operator=(const basic_teebuf&) = default; + basic_teebuf& operator=(basic_teebuf&&) = default; + + private: + int_type overflow(int_type c) override + { + const auto eof = traits::eof(); + + if (traits::eq_int_type(c, eof)) + return traits::not_eof(c); + + const auto ch = traits::to_char_type(c); + const auto r1 = sb1->sputc(ch); + const auto r2 = sb2->sputc(ch); + + return traits::eq_int_type(r1, eof) || traits::eq_int_type(r2, eof) ? eof : c; + } + + int sync() override + { + const auto r1 = sb1->pubsync(); + const auto r2 = sb2->pubsync(); + return r1 == 0 && r2 == 0 ? 0 : -1; + } + + streambuf_type *sb1; + streambuf_type *sb2; +}; + +typedef basic_teebuf teebuf; +typedef basic_teebuf wteebuf; + + +template > +class basic_teestream : + public std::basic_ostream +{ + public: + typedef std::basic_ostream stream_type; + + // Construct an ostream which tees output to the supplied + // ostreams. + basic_teestream(stream_type &o1, stream_type &o2) : + std::basic_ostream(&tbuf), + tbuf(o1.rdbuf(), o2.rdbuf()) + {} + + basic_teestream() = delete; + ~basic_teestream() = default; + basic_teestream(const basic_teestream&) = default; + basic_teestream(basic_teestream&&) = default; + basic_teestream& operator=(const basic_teestream&) = default; + basic_teestream& operator=(basic_teestream&&) = default; + + private: + basic_teebuf tbuf; +}; + +typedef basic_teestream teestream; +typedef basic_teestream wteestream;