log: support udp log server

This commit is contained in:
Flyinghead 2025-01-02 10:35:27 +01:00
parent ee1a7167f6
commit 76c3695f6e
13 changed files with 263 additions and 44 deletions

2
.gitmodules vendored
View File

@ -43,4 +43,4 @@
url = https://github.com/google/googletest.git
[submodule "core/deps/asio"]
path = core/deps/asio
url = https://github.com/chriskohlhoff/asio.git
url = https://github.com/flyinghead/asio.git

View File

@ -146,6 +146,13 @@ if(NINTENDO_SWITCH)
if(USE_GLES)
target_compile_definitions(${PROJECT_NAME} PRIVATE GLES)
endif()
# asio
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASIO_DISABLE_LOCAL_SOCKETS
ASIO_DISABLE_SERIAL_PORT
ESHUTDOWN=110
SA_RESTART=0
SA_NOCLDWAIT=0)
elseif(LIBRETRO)
add_library(${PROJECT_NAME} SHARED core/emulator.cpp)

View File

@ -135,6 +135,7 @@ Option<bool, false> DiscordPresence("DiscordPresence", true);
#if defined(__ANDROID__) && !defined(LIBRETRO)
Option<bool, false> UseSafFilePicker("UseSafFilePicker", true);
#endif
OptionString LogServer("LogServer", "", "log");
// Profiler
Option<bool> ProfilerEnabled("Profiler.Enabled");

View File

@ -495,6 +495,7 @@ extern Option<bool, false> DiscordPresence;
#if defined(__ANDROID__) && !defined(LIBRETRO)
extern Option<bool, false> UseSafFilePicker;
#endif
extern OptionString LogServer;
// Profiling
extern Option<bool> ProfilerEnabled;

@ -1 +1 @@
Subproject commit 03ae834edbace31a96157b89bf50e5ee464e5ef9
Subproject commit d3402006e84efb6114ff93e4f2b8508412ed80d5

View File

@ -15,6 +15,7 @@
#include "ConsoleListener.h"
#include "InMemoryListener.h"
#include "NetworkListener.h"
#include "Log.h"
#include "StringUtil.h"
#include "cfg/cfg.h"
@ -118,8 +119,6 @@ LogManager::LogManager()
m_log[LogTypes::SAVESTATE] = {"SAVESTATE", "Save States"};
m_log[LogTypes::SH4] = {"SH4", "SH4 Modules"};
RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener());
// Set up log listeners
int verbosity = cfgLoadInt("log", "Verbosity", LogTypes::LDEBUG);
@ -128,47 +127,56 @@ LogManager::LogManager()
verbosity = 1;
if (verbosity > MAX_LOGLEVEL)
verbosity = MAX_LOGLEVEL;
SetLogLevel(static_cast<LogTypes::LOG_LEVELS>(verbosity));
if (cfgLoadBool("log", "LogToFile", false))
{
#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP)
std::string logPath = get_writable_data_path("flycast.log");
#else
std::string logPath = "flycast.log";
#endif
FileLogListener *listener = new FileLogListener(logPath);
if (!listener->IsValid())
{
const char *home = nowide::getenv("HOME");
if (home != nullptr)
{
delete listener;
listener = new FileLogListener(home + ("/" + logPath));
}
}
RegisterListener(LogListener::FILE_LISTENER, listener);
EnableListener(LogListener::FILE_LISTENER, true);
}
RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener());
EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true));
// EnableListener(LogListener::LOG_WINDOW_LISTENER, Config::Get(LOGGER_WRITE_TO_WINDOW));
RegisterListener(LogListener::IN_MEMORY_LISTENER, new InMemoryListener());
EnableListener(LogListener::IN_MEMORY_LISTENER, true);
for (LogContainer& container : m_log)
{
container.m_enable = cfgLoadBool("log", container.m_short_name, true);
}
m_path_cutoff_point = DeterminePathCutOffPoint();
UpdateConfig();
}
LogManager::~LogManager()
void LogManager::UpdateConfig()
{
// The log window listener pointer is owned by the GUI code.
delete m_listeners[LogListener::CONSOLE_LISTENER];
delete m_listeners[LogListener::FILE_LISTENER];
delete m_listeners[LogListener::IN_MEMORY_LISTENER];
bool logToFile = cfgLoadBool("log", "LogToFile", false);
if (logToFile != IsListenerEnabled(LogListener::FILE_LISTENER))
{
if (!logToFile) {
m_listeners[LogListener::FILE_LISTENER].reset();
}
else {
#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP)
std::string logPath = get_writable_data_path("flycast.log");
#else
std::string logPath = "flycast.log";
#endif
FileLogListener *listener = new FileLogListener(logPath);
if (!listener->IsValid())
{
const char *home = nowide::getenv("HOME");
if (home != nullptr)
{
delete listener;
listener = new FileLogListener(home + ("/" + logPath));
}
}
RegisterListener(LogListener::FILE_LISTENER, listener);
}
EnableListener(LogListener::FILE_LISTENER, logToFile);
}
std::string newLogServer = cfgLoadStr("log", "LogServer", "");
if (logServer != newLogServer)
{
logServer = newLogServer;
RegisterListener(LogListener::NETWORK_LISTENER, new NetworkListener(logServer));
EnableListener(LogListener::NETWORK_LISTENER, !logServer.empty());
}
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
@ -241,7 +249,7 @@ const char* LogManager::GetFullName(LogTypes::LOG_TYPE type) const
void LogManager::RegisterListener(LogListener::LISTENER id, LogListener* listener)
{
m_listeners[id] = listener;
m_listeners[id] = std::unique_ptr<LogListener>(listener);
}
void LogManager::EnableListener(LogListener::LISTENER id, bool enable)

View File

@ -6,6 +6,7 @@
#include <array>
#include <cstdarg>
#include <memory>
#include "BitSet.h"
#include "Log.h"
@ -23,6 +24,7 @@ public:
CONSOLE_LISTENER,
LOG_WINDOW_LISTENER,
IN_MEMORY_LISTENER,
NETWORK_LISTENER,
NUMBER_OF_LISTENERS // Must be last
};
@ -52,6 +54,7 @@ public:
void RegisterListener(LogListener::LISTENER id, LogListener* listener);
void EnableListener(LogListener::LISTENER id, bool enable);
bool IsListenerEnabled(LogListener::LISTENER id) const;
void UpdateConfig();
private:
struct LogContainer
@ -66,7 +69,6 @@ private:
};
LogManager();
~LogManager();
LogManager(const LogManager&) = delete;
LogManager& operator=(const LogManager&) = delete;
@ -75,7 +77,8 @@ private:
LogTypes::LOG_LEVELS m_level;
std::array<LogContainer, LogTypes::NUMBER_OF_LOGS> m_log{};
std::array<LogListener*, LogListener::NUMBER_OF_LISTENERS> m_listeners{};
std::array<std::unique_ptr<LogListener>, LogListener::NUMBER_OF_LISTENERS> m_listeners{};
BitSet32 m_listener_ids;
size_t m_path_cutoff_point = 0;
std::string logServer;
};

View File

@ -0,0 +1,88 @@
/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast 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 2 of the License, or
(at your option) any later version.
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <asio.hpp>
#include <stdio.h>
#include "LogManager.h"
class NetworkListener : public LogListener
{
public:
NetworkListener(const std::string& dest)
{
if (dest.empty())
return;
std::string host;
std::string port("31667");
auto colon = dest.find(':');
if (colon != std::string::npos) {
port = dest.substr(colon + 1);
host = dest.substr(0, colon);
}
else {
host = dest;
}
asio::ip::udp::resolver resolver(io_context);
asio::error_code ec;
auto it = resolver.resolve(host, port, ec);
if (ec || it.empty()) {
fprintf(stderr, "Unknown hostname %s: %s\n", host.c_str(), ec.message().c_str());
}
else
{
asio::ip::udp::endpoint endpoint = *it.begin();
socket.connect(endpoint, ec);
if (ec)
fprintf(stderr, "Connect to log server failed: %s\n", ec.message().c_str());
}
}
void Log(LogTypes::LOG_LEVELS level, const char* msg) override
{
if (!socket.is_open())
return;
const char *reset_attr = "\x1b[0m";
std::string color_attr;
switch (level)
{
case LogTypes::LOG_LEVELS::LNOTICE:
// light green
color_attr = "\x1b[92m";
break;
case LogTypes::LOG_LEVELS::LERROR:
// light red
color_attr = "\x1b[91m";
break;
case LogTypes::LOG_LEVELS::LWARNING:
// light yellow
color_attr = "\x1b[93m";
break;
default:
break;
}
std::string str = color_attr + msg + reset_attr;
asio::error_code ec;
socket.send(asio::buffer(str), 0, ec);
}
private:
asio::io_context io_context;
asio::ip::udp::socket socket { io_context };
};

View File

@ -110,6 +110,7 @@ void SaveSettings()
void SaveAndroidSettings();
SaveAndroidSettings();
#endif
LogManager::GetInstance()->UpdateConfig();
}
void flycast_term()

View File

@ -1609,6 +1609,8 @@ static void contentpath_warning_popup()
}
}
#if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER
static void gui_debug_tab()
{
header("Logging");
@ -1640,6 +1642,9 @@ static void gui_debug_tab()
}
ImGui::EndCombo();
}
ImGui::InputText("Log Server", &config::LogServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr);
ImGui::SameLine();
ShowHelpMarker("Log to this hostname[:port] with UDP. Default port is 31667.");
}
#if FC_PROFILER
ImGui::Spacing();
@ -1663,6 +1668,7 @@ static void gui_debug_tab()
}
#endif
}
#endif
static void addContentPathCallback(const std::string& path)
{
@ -2839,14 +2845,8 @@ static void gui_settings_advanced()
"Dump all textures into data/texdump/<game id>");
bool logToFile = cfgLoadBool("log", "LogToFile", false);
bool newLogToFile = logToFile;
ImGui::Checkbox("Log to File", &newLogToFile);
if (logToFile != newLogToFile)
{
cfgSaveBool("log", "LogToFile", newLogToFile);
LogManager::Shutdown();
LogManager::Init();
}
if (ImGui::Checkbox("Log to File", &logToFile))
cfgSaveBool("log", "LogToFile", logToFile);
ImGui::SameLine();
ShowHelpMarker("Log debug information to flycast.log");
#ifdef SENTRY_UPLOAD

View File

@ -1,5 +1,8 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
// Seems to be missing in newlib, dumb stub (file permissions is not a thing on fat32 anyways)
mode_t umask(mode_t mask)
@ -7,3 +10,32 @@ mode_t umask(mode_t mask)
return mask;
}
int pause()
{
sleep(0xffffffff);
return -1;
}
// FIXME always failing stub
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
switch (how)
{
case SIG_BLOCK:
case SIG_UNBLOCK:
case SIG_SETMASK:
break;
default:
errno = EINVAL;
return -1;
}
errno = ENOSYS;
return -1;
}
// Map an interface index into its name.
char *if_indextoname(unsigned ifindex, char *ifname)
{
errno = ENXIO;
return NULL;
}

28
shell/switch/sys/uio.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef SYS_UIO_H_
#define SYS_UIO_H_
#include <sys/types.h>
#include <sys/_iovec.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Read data from file descriptor FD, and put the result in the
buffers described by IOVEC, which is a vector of COUNT 'struct iovec's.
The buffers are filled in the order specified.
Operates just like 'read' (see <unistd.h>) except that data are
put in IOVEC instead of a contiguous buffer. */
extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count);
/* Write data pointed by the buffers described by IOVEC, which
is a vector of COUNT 'struct iovec's, to file descriptor FD.
The data is written in the order specified.
Operates just like 'write' (see <unistd.h>) except that the data
are taken from IOVEC instead of a contiguous buffer. */
extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count);
#ifdef __cplusplus
}
#endif
#endif /* SYS_UIO_H_ */

50
shell/switch/sys/un.h Normal file
View File

@ -0,0 +1,50 @@
/* Copyright (C) 1991-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#ifndef _SYS_UN_H
#define _SYS_UN_H 1
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
struct sockaddr_un
{
sa_family_t sun_family;
char sun_path[108]; /* Path name. */
};
/* Should be defined in sockets.h */
struct ipv6_mreq
{
struct in6_addr ipv6mr_multiaddr;
unsigned int ipv6mr_interface;
};
/* Should be declared in net/if.h */
char* if_indextoname(unsigned int, char*);
unsigned int if_nametoindex(const char*);
#ifdef __cplusplus
}
#endif
#endif /* sys/un.h */