log: support udp log server
This commit is contained in:
parent
ee1a7167f6
commit
76c3695f6e
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
};
|
|
@ -110,6 +110,7 @@ void SaveSettings()
|
|||
void SaveAndroidSettings();
|
||||
SaveAndroidSettings();
|
||||
#endif
|
||||
LogManager::GetInstance()->UpdateConfig();
|
||||
}
|
||||
|
||||
void flycast_term()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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_ */
|
|
@ -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 */
|
Loading…
Reference in New Issue