Files
ppsspp/Core/Debugger/WebSocket/LogConfigSubscriber.cpp
Henrik RydgårdandClaude Sonnet 5 ce33c964f7 Debugger: add log.channels.list/log.channel.set over the WebSocket API
Lets a client query all log categories (Common/Log.h's Log enum) with their
current level/enabled state, and change a channel's level and/or enabled
state at runtime - same data LogConfigScreen already exposes in the UI, now
reachable from the debugger protocol. Levels are named strings (notice,
error, warning, info, debug, verbose) rather than the 1-6 numbers the
existing passive 'log' event uses (LogBroadcaster.cpp, left unchanged) -
clearer for a config-style API where you're not scanning a stream.

Registered as a new subscriber alongside the others in WebSocket.cpp, and
wired into all the build systems that need a new source file (CMake, the
Windows and UWP vcxprojs, Android.mk - libretro's Makefile.common doesn't
build any WebSocket debugger files so needs no entry).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 16:01:23 +02:00

137 lines
5.1 KiB
C++

// Copyright (c) 2026- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// This is about the log *channels themselves* (their level/enabled state) - the actual log
// message stream is a separate, passive broadcast, see LogBroadcaster.cpp's "log" event (which
// keeps its existing numeric 'level' - only this newer API uses level names, for readability).
#include "Common/Log/LogManager.h"
#include "Core/Debugger/WebSocket/LogConfigSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
static const char *LogLevelToString(LogLevel level) {
switch (level) {
case LogLevel::LNOTICE: return "notice";
case LogLevel::LERROR: return "error";
case LogLevel::LWARNING: return "warning";
case LogLevel::LINFO: return "info";
case LogLevel::LDEBUG: return "debug";
case LogLevel::LVERBOSE: return "verbose";
default: return "unknown";
}
}
static bool LogLevelFromString(const std::string &s, LogLevel *out) {
if (s == "notice") *out = LogLevel::LNOTICE;
else if (s == "error") *out = LogLevel::LERROR;
else if (s == "warning") *out = LogLevel::LWARNING;
else if (s == "info") *out = LogLevel::LINFO;
else if (s == "debug") *out = LogLevel::LDEBUG;
else if (s == "verbose") *out = LogLevel::LVERBOSE;
else return false;
return true;
}
DebuggerSubscriber *WebSocketLogConfigInit(DebuggerEventHandlerMap &map) {
map["log.channels.list"] = &WebSocketLogChannelsList;
map["log.channel.set"] = &WebSocketLogChannelSet;
return nullptr;
}
// List all log channels and their current level/enabled state (log.channels.list)
//
// No parameters.
//
// Response (same event name):
// - channels: array of objects, each with properties:
// - name: string channel name - pass this back as 'channel' to log.channel.set.
// - level: string severity level, one of 'notice', 'error', 'warning', 'info', 'debug',
// 'verbose' (in that order, least to most noisy.) A message is only actually logged for
// this channel if its own level is at or above this severity, and the channel is enabled.
// - enabled: boolean, whether this channel logs anything at all right now.
void WebSocketLogChannelsList(DebuggerRequest &req) {
JsonWriter &json = req.Respond();
json.pushArray("channels");
for (int i = 0; i < LogManager::GetNumChannels(); ++i) {
Log type = (Log)i;
const LogChannel *chan = g_logManager.GetLogChannel(type);
json.pushDict();
json.writeString("name", LogManager::GetLogTypeName(type));
json.writeString("level", LogLevelToString(chan->level));
json.writeBool("enabled", chan->enabled);
json.pop();
}
json.pop();
}
// Change a log channel's level and/or enabled state (log.channel.set)
//
// Parameters:
// - channel: string, channel name (as returned by log.channels.list - case sensitive.)
// - level: optional string, one of 'notice', 'error', 'warning', 'info', 'debug', 'verbose' -
// see log.channels.list for what this means.
// - enabled: optional boolean, whether this channel should log at all.
//
// At least one of level/enabled must be given.
//
// Response (same event name):
// - name: channel name, repeated back.
// - level: the channel's resulting level (string, see log.channels.list.)
// - enabled: the channel's resulting enabled state.
void WebSocketLogChannelSet(DebuggerRequest &req) {
std::string channelName;
if (!req.ParamString("channel", &channelName))
return;
bool hasLevel = req.HasParam("level");
LogLevel level = LogLevel::LNOTICE;
if (hasLevel) {
std::string levelStr;
if (!req.ParamString("level", &levelStr, DebuggerParamType::OPTIONAL))
return;
if (!LogLevelFromString(levelStr, &level))
return req.Fail("Invalid 'level', must be notice, error, warning, info, debug, or verbose");
}
bool hasEnabled = req.HasParam("enabled");
bool enabled = false;
if (hasEnabled && !req.ParamBool("enabled", &enabled, DebuggerParamType::OPTIONAL))
return;
if (!hasLevel && !hasEnabled)
return req.Fail("Must specify 'level' and/or 'enabled'");
for (int i = 0; i < LogManager::GetNumChannels(); ++i) {
Log type = (Log)i;
if (channelName != LogManager::GetLogTypeName(type))
continue;
if (hasLevel)
g_logManager.SetLogLevel(type, level);
if (hasEnabled)
g_logManager.SetEnabled(type, enabled);
const LogChannel *chan = g_logManager.GetLogChannel(type);
JsonWriter &json = req.Respond();
json.writeString("name", channelName);
json.writeString("level", LogLevelToString(chan->level));
json.writeBool("enabled", chan->enabled);
return;
}
req.Fail("No log channel found with that name");
}