Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Common/Cpp/Logging/FileLogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ void FileLogger::thread_loop(){
return m_stopping || !m_queue.empty();
});
if (m_stopping){
if (m_config.flush_when_exit && m_file.is_open()){
while (m_queue.size() > 0){
std::string msg = std::move(m_queue.front().first);
// Write to file
std::string line = normalize_newlines(msg);
std::string file_str = to_file_str(line);
m_file.write(file_str.c_str(), file_str.size());
m_queue.pop_front();
}
m_file.flush();
}
break;
}
auto& item = m_queue.front();
Expand Down
3 changes: 2 additions & 1 deletion Common/Cpp/Logging/FileLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ struct FileLoggerConfig{
size_t max_queue_size = 10000; // Max pending log entries before blocking
size_t max_file_size_bytes = 50 * 1024 * 1024; // Max file size before rotation (50MB default)
size_t last_log_max_lines = 10000; // Max lines to keep in memory for get_last()
bool flush_when_exit = true; // Whether to save remaining logs to file when program exists
};


// A Qt-free file logger that:
// A file logger that:
// 1. Writes log messages to a file asynchronously via a background thread
// 2. Supports log rotation when the file exceeds a configured size
// 3. Notifies registered listeners when a log message is written (for UI integration)
Expand Down
36 changes: 36 additions & 0 deletions Common/Cpp/Logging/GlobalLogger.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* Global Logger
*
* From: https://github.com/PokemonAutomation/
*
*/

#include "GlobalLogger.h"

namespace PokemonAutomation{


// We should define this function at Main.cpp of each executable
// that uses the Logging/ library.
//
// This function is required by Common/Cpp/Logging/GlobalLogger.h:global_logger_raw() to initialize
// the global file logger.
// This function is called the first time `global_logger_raw()` is called to initialize the static
// local global file logger object.
//
// The advantage of such design is that:
// - This forces each executable's main.cpp to define how the logger is configured.
// - This ensures thread-safety on the global logger object. The static local `FileLogger` defined
// in `global_logger_raw()` is guaranteed by C++ to be thread-safe when constructed.
FileLoggerConfig make_global_config();


Logger& global_logger_raw(){
// Call a function `make_global_config()` that is not defined in
// this Logging/ library! To use this global logger, you must
// define `make_global_config()` in the Main.cpp.
static FileLogger logger(make_global_config());
return logger;
}


}
27 changes: 27 additions & 0 deletions Common/Cpp/Logging/GlobalLogger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* Global Logger
*
* From: https://github.com/PokemonAutomation/
*
* Provides a global file logger instance for application-wide logging.
* This is a Qt-free logger that can be used before Qt is initialized.
*/

#ifndef PokemonAutomation_Logging_GlobalLogger_H
#define PokemonAutomation_Logging_GlobalLogger_H

#include "FileLogger.h"

namespace PokemonAutomation{


// Return a global raw `FileLogger`. `FileLogger` is defined in FileLogger.h.
// "raw" here means the logger does not add any timestamp or tags to the incoming log lines.
//
// To initialize the global logger, implement `FileLoggerConfig make_global_config()` at
// Main.cpp to return the config for the global logger. If this function is not implemented,
// the program will not compile.
Logger& global_logger_raw();


}
#endif
14 changes: 14 additions & 0 deletions SerialPrograms/Source/CommandLine/CommandLine_Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
using namespace PokemonAutomation;
using namespace PokemonAutomation::NintendoSwitch;

namespace PokemonAutomation{

// This function is required by Common/Cpp/Logging/GlobalLogger.h:global_logger_raw() to initialize
// the global file logger.
// This function is called the first time `global_logger_raw()` is called to initialize the static
// local global file logger object.
FileLoggerConfig make_global_config(){
return FileLoggerConfig{
.file_path = "./SerialProgramsCommandLine.log"
};
}

}

int main(int argc, char* argv[]){
// // Set up output redirection for logging
// OutputRedirector redirect_stdout(std::cout, "stdout", Color());
Expand Down
72 changes: 8 additions & 64 deletions SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <QCoreApplication>
#include <QMenuBar>
#include <QDir>
#include "Common/Cpp/Logging/GlobalLogger.h"
#include "CommonFramework/Globals.h"
#include "CommonFramework/GlobalSettingsPanel.h"
#include "CommonFramework/Windows/DpiScaler.h"
Expand All @@ -22,70 +23,13 @@ using std::endl;
namespace PokemonAutomation{


Logger& global_logger_raw(){
auto get_log_filepath = [&](){
QString application_name(QCoreApplication::applicationName());
if (application_name.size() == 0){
application_name = "SerialPrograms";
}
return USER_FILE_PATH() + (application_name + ".log").toStdString();
};

static FileWindowLogger logger(get_log_filepath(), LOG_HISTORY_LINES);
return logger;
}


FileWindowLogger::~FileWindowLogger(){
m_file_logger.remove_listener(*this);
}

FileWindowLogger::FileWindowLogger(const std::string& path, size_t max_queue_size)
: m_file_logger(FileLoggerConfig{
.file_path = path,
.max_queue_size = max_queue_size,
.max_file_size_bytes = 50 * 1024 * 1024, // 50MB
.last_log_max_lines = max_queue_size,
})
{
m_file_logger.add_listener(*this);
}

void FileWindowLogger::operator+=(FileWindowLoggerWindow& widget){
std::lock_guard<std::mutex> lg(m_window_lock);
m_windows.insert(&widget);
}

void FileWindowLogger::operator-=(FileWindowLoggerWindow& widget){
std::lock_guard<std::mutex> lg(m_window_lock);
m_windows.erase(&widget);
}

void FileWindowLogger::log(const std::string& msg, Color color){
m_file_logger.log(msg, color);
}

void FileWindowLogger::log(std::string&& msg, Color color){
m_file_logger.log(std::move(msg), color);
}

std::vector<std::string> FileWindowLogger::get_last() const{
return m_file_logger.get_last();
}

void FileWindowLogger::on_log(const std::string& msg, Color color){
void FileWindowLoggerWindow::on_log(const std::string& msg, Color color){
// This is called from FileLogger's background thread.
// Format the message for Qt display and send to all windows.
std::lock_guard<std::mutex> lg(m_window_lock);
if (!m_windows.empty()){
QString str = to_window_str(msg, color);
for (FileWindowLoggerWindow* window : m_windows){
window->log(str);
}
}
emit signal_log(to_window_str(msg, color));
}

QString FileWindowLogger::to_window_str(const std::string& msg, Color color){
QString FileWindowLoggerWindow::to_window_str(const std::string& msg, Color color){
// Convert message to HTML for display in QTextEdit.
// Replace spaces with &nbsp; and newlines with <br>.
std::string str;
Expand All @@ -111,10 +55,11 @@ QString FileWindowLogger::to_window_str(const std::string& msg, Color color){
}


FileWindowLoggerWindow::FileWindowLoggerWindow(FileWindowLogger& logger, QWidget* parent)
FileWindowLoggerWindow::FileWindowLoggerWindow(QWidget* parent)
: QMainWindow(parent)
, m_logger(logger)
, m_logger(dynamic_cast<FileLogger&>(global_logger_raw()))
{
m_logger.add_listener(*this);
if (objectName().isEmpty()){
setObjectName(QString::fromUtf8("TextWindow"));
}
Expand Down Expand Up @@ -149,7 +94,6 @@ FileWindowLoggerWindow::FileWindowLoggerWindow(FileWindowLogger& logger, QWidget
GlobalSettings::instance().LOG_WINDOW_SIZE->X_POS.add_listener(*this);
GlobalSettings::instance().LOG_WINDOW_SIZE->Y_POS.add_listener(*this);

m_logger += *this;
log("================================================================================");
log("<b>Window Startup...</b>");
log("Current path: " + QDir::currentPath());
Expand All @@ -161,7 +105,7 @@ FileWindowLoggerWindow::FileWindowLoggerWindow(FileWindowLogger& logger, QWidget

FileWindowLoggerWindow::~FileWindowLoggerWindow(){
remove_window(*this);
m_logger -= *this;
m_logger.remove_listener(*this);
GlobalSettings::instance().LOG_WINDOW_SIZE->WIDTH.remove_listener(*this);
GlobalSettings::instance().LOG_WINDOW_SIZE->HEIGHT.remove_listener(*this);
GlobalSettings::instance().LOG_WINDOW_SIZE->X_POS.remove_listener(*this);
Expand Down
56 changes: 11 additions & 45 deletions SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,25 @@

namespace PokemonAutomation{

class FileWindowLoggerWindow;


// A logger that writes to a file (via FileLogger) and can also display
// log messages in Qt GUI windows (FileWindowLoggerWindow).
//
// This class acts as a thin Qt wrapper around the Qt-free FileLogger,
// adding the ability to manage multiple Qt windows that display log output.
class FileWindowLogger : public Logger, private FileLogger::Listener{
public:
~FileWindowLogger();

// Construct a FileWindowLogger that writes to the given file path.
// The max_queue_size parameter controls how many log messages can be
// queued before the log() call blocks.
FileWindowLogger(const std::string& path, size_t max_queue_size);

// Add/remove Qt windows that will display log messages.
void operator+=(FileWindowLoggerWindow& widget);
void operator-=(FileWindowLoggerWindow& widget);

// Logger interface implementation - forwards to FileLogger.
virtual void log(const std::string& msg, Color color = Color()) override;
virtual void log(std::string&& msg, Color color = Color()) override;
virtual std::vector<std::string> get_last() const override;

private:
// FileLogger::Listener implementation - called when a message is logged.
// Formats the message for Qt display and sends to all registered windows.
virtual void on_log(const std::string& msg, Color color) override;

// Convert a log message to HTML for display in QTextEdit.
static QString to_window_str(const std::string& msg, Color color);

private:
FileLogger m_file_logger;

std::mutex m_window_lock;
std::set<FileWindowLoggerWindow*> m_windows;
};


// A Qt window that displays log output from a FileWindowLogger.
// Uses Qt signals/slots for thread-safe updates from the logger's background thread.
class FileWindowLoggerWindow : public QMainWindow, public ConfigOption::Listener{
class FileWindowLoggerWindow : public QMainWindow, public ConfigOption::Listener, public FileLogger::Listener{
Q_OBJECT

public:
FileWindowLoggerWindow(FileWindowLogger& logger, QWidget* parent = nullptr);
FileWindowLoggerWindow(QWidget* parent = nullptr);
virtual ~FileWindowLoggerWindow();

// Called by FileWindowLogger to display a log message.
// Thread-safe: emits a signal that is handled on the UI thread.

void log(QString msg);

// Callback function registered to the global logger.
// The global logger's background thread call it to display a log to the window.
// Thread-safe: emits a signal that is handled on the UI thread.
void on_log(const std::string& msg, Color color) override;

virtual void resizeEvent(QResizeEvent* event) override;
virtual void moveEvent(QMoveEvent* event) override;

Expand All @@ -82,7 +46,9 @@ class FileWindowLoggerWindow : public QMainWindow, public ConfigOption::Listener
private:
virtual void on_config_value_changed(void* object) override;

FileWindowLogger& m_logger;
static QString to_window_str(const std::string& msg, Color color);

FileLogger& m_logger;
QMenuBar* m_menubar;
QTextEdit* m_text;
bool m_pending_resize = false;
Expand Down
1 change: 1 addition & 0 deletions SerialPrograms/Source/CommonFramework/Logging/Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <iostream>
#include "Common/Cpp/Logging/TaggedLogger.h"
#include "Common/Cpp/Logging/GlobalLogger.h"
#include "Logger.h"

namespace PokemonAutomation{
Expand Down
7 changes: 1 addition & 6 deletions SerialPrograms/Source/CommonFramework/Logging/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@
#define PokemonAutomation_Logging_Logger_H

#include "Common/Cpp/Logging/AbstractLogger.h"
#include "Common/Cpp/Logging/GlobalLogger.h"

namespace PokemonAutomation{


// The base logger for the application. Use this to build other loggers.
// Its implementation is defined in FileWindowLogger.cpp, writing each input
// log into a log file named "<USER_FILE_PATH()>/<ApplicationName>.log".
// It prints each input log string as is with no tag or timestamp.
Logger& global_logger_raw();

// This logger wraps around `global_logger_raw()` to print each log with a
// timestamp and a default tag "Global". Use this logger directly in the
// application codebase.
Expand Down
16 changes: 16 additions & 0 deletions SerialPrograms/Source/CommonFramework/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ void set_working_directory(){
}
}

namespace PokemonAutomation{

// This function is required by Common/Cpp/Logging/GlobalLogger.h:global_logger_raw() to initialize
// the global file logger.
// This function is called the first time `global_logger_raw()` is called to initialize the static
// local global file logger object.
// Note: in order to make sure `USER_FILE_PATH()` and `QCoreApplication::applicationName()` work
// correctly you need to define `QApplication` before `make_global_config()` is called.
FileLoggerConfig make_global_config(){
return FileLoggerConfig{
.file_path = USER_FILE_PATH() + QCoreApplication::applicationName().toStdString() + ".log",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot QCoreApplication::applicationName() before initializing Qt from main.

};
}

}


int run_program(int argc, char *argv[]){
QApplication application(argc, argv);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ MainWindow::MainWindow(QWidget* parent)
);
}
{
m_output_window.reset(new FileWindowLoggerWindow((FileWindowLogger&)global_logger_raw()));
m_output_window.reset(new FileWindowLoggerWindow);
QPushButton* output = new QPushButton("Output Window", support_box);
buttons->addWidget(output);
connect(
Expand Down
2 changes: 2 additions & 0 deletions SerialPrograms/cmake/SourceFiles.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ file(GLOB LIBRARY_SOURCES
../Common/Cpp/Logging/AbstractLogger.h
../Common/Cpp/Logging/FileLogger.cpp
../Common/Cpp/Logging/FileLogger.h
../Common/Cpp/Logging/GlobalLogger.cpp
../Common/Cpp/Logging/GlobalLogger.h
../Common/Cpp/Logging/LastLogTracker.cpp
../Common/Cpp/Logging/LastLogTracker.h
../Common/Cpp/Logging/OutputRedirector.cpp
Expand Down
Loading