Skip to content

Commit f08fe7f

Browse files
committed
feat(DebugLogViewer): add LAN access feature for debug log viewer
- Introduced a new setting to enable LAN access for the debug log viewer, allowing it to listen on all network interfaces. - Updated related classes and settings to support the new LAN access functionality, including UI bindings and localization. - Enhanced logging to provide clearer messages regarding the access mode and available LAN URLs.
1 parent 180cddc commit f08fe7f

10 files changed

Lines changed: 109 additions & 20 deletions

File tree

Data/Models/RitsuLibSettings.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ public sealed class RitsuLibSettings
7070
public bool DebugCompatAncientArchitect { get; set; } = true;
7171

7272
/// <summary>
73-
/// Starts the loopback-only browser debug log viewer for this session.
74-
/// 为本会话启动仅监听 loopback 的浏览器调试日志查看器
73+
/// Starts the browser debug log viewer for this session. It listens on loopback unless LAN access is enabled.
74+
/// 为本会话启动浏览器调试日志查看器;除非启用局域网访问,否则仅监听 loopback。
7575
/// </summary>
7676
[JsonPropertyName("debug_log_viewer_enabled")]
7777
public bool DebugLogViewerEnabled { get; set; } = true;
@@ -91,8 +91,15 @@ public sealed class RitsuLibSettings
9191
public bool DebugLogViewerAutoOpen { get; set; }
9292

9393
/// <summary>
94-
/// Loopback HTTP port for the debug log viewer.
95-
/// 调试日志查看器的 loopback HTTP 端口。
94+
/// When true, binds the debug log viewer to all network interfaces so devices on the same LAN can connect.
95+
/// 为 true 时,调试日志查看器会监听所有网络接口,使同一局域网设备可以连接。
96+
/// </summary>
97+
[JsonPropertyName("debug_log_viewer_lan_access_enabled")]
98+
public bool DebugLogViewerLanAccessEnabled { get; set; }
99+
100+
/// <summary>
101+
/// HTTP port for the debug log viewer.
102+
/// 调试日志查看器的 HTTP 端口。
96103
/// </summary>
97104
[JsonPropertyName("debug_log_viewer_port")]
98105
public int DebugLogViewerPort { get; set; } = 18742;
@@ -105,8 +112,8 @@ public sealed class RitsuLibSettings
105112
public int DebugLogViewerPortFallbackCount { get; set; } = 20;
106113

107114
/// <summary>
108-
/// Stable browser access token for the loopback debug log viewer.
109-
/// 本机调试日志查看器使用的稳定浏览器访问 token。
115+
/// Stable browser access token for the debug log viewer.
116+
/// 调试日志查看器使用的稳定浏览器访问 token。
110117
/// </summary>
111118
[JsonPropertyName("debug_log_viewer_access_token")]
112119
public string DebugLogViewerAccessToken { get; set; } = "";

Data/RitsuLibSettingsStore.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ internal static RitsuDebugLogViewerOptions GetDebugLogViewerOptions()
224224
s.DebugLogViewerEnabled,
225225
s.DebugLogViewerMirrorGameLogs,
226226
s.DebugLogViewerAutoOpen,
227+
s.DebugLogViewerLanAccessEnabled,
227228
Math.Clamp(s.DebugLogViewerPort, 1, 65535),
228229
Math.Clamp(s.DebugLogViewerPortFallbackCount, 0, 100),
229230
s.DebugLogViewerAccessToken,

Diagnostics/Logging/RitsuDebugLogPipeline.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ public static void Initialize(RitsuDebugLogViewerOptions options)
4747
_ring = new(Math.Clamp(options.RingBufferCapacity, 512, 100000));
4848
_cts = new();
4949

50-
_server = new(options.AccessToken, Snapshot, BuildStatus, ResolveViewerAssetRoot());
50+
_server = new(
51+
options.AccessToken,
52+
options.LanAccessEnabled,
53+
Snapshot,
54+
BuildStatus,
55+
ResolveViewerAssetRoot());
5156
_server.Start(options.Port, options.PortFallbackCount);
5257

5358
_worker = Task.Run(WorkerLoopAsync);
@@ -63,13 +68,12 @@ public static void Initialize(RitsuDebugLogViewerOptions options)
6368
_initialized = true;
6469
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
6570

66-
RitsuLibFramework.Logger.Info(
67-
$"[DebugLogViewer] Local debug log viewer listening at {_server.Url}");
71+
RitsuLibFramework.Logger.Info(CreateViewerStartMessage(_server));
6872
}
6973
catch (Exception ex)
7074
{
7175
CleanupAfterFailedStart();
72-
RitsuLibFramework.Logger.Warn($"[DebugLogViewer] Failed to start local viewer: {ex.Message}");
76+
RitsuLibFramework.Logger.Warn($"[DebugLogViewer] Failed to start viewer: {ex.Message}");
7377
}
7478
}
7579
}
@@ -104,6 +108,9 @@ public static object BuildStatus()
104108
sessionStartedAtUtc = SessionStartedAtUtc,
105109
processId = Environment.ProcessId,
106110
url = ViewerUrl,
111+
accessMode = _server?.AccessMode ?? "loopback",
112+
lanAccessEnabled = _server?.LanAccessEnabled ?? false,
113+
lanUrls = _server?.LanUrls ?? [],
107114
clients = _server?.ClientCount ?? 0,
108115
bufferCount = _ring?.Count ?? 0,
109116
bufferCapacity = _ring?.Capacity ?? 0,
@@ -135,6 +142,17 @@ public static (bool Success, string Message) TryOpenViewerInBrowser()
135142
: (false, $"Error {error}: Could not open browser. URL: {url}");
136143
}
137144

145+
private static string CreateViewerStartMessage(RitsuDebugLogViewerServer server)
146+
{
147+
if (!server.LanAccessEnabled)
148+
return $"[DebugLogViewer] Local debug log viewer listening at {server.Url}";
149+
150+
var lanUrls = server.LanUrls;
151+
return lanUrls.Count == 0
152+
? $"[DebugLogViewer] LAN debug log viewer listening at {server.Url}; no LAN IPv4 address was found."
153+
: $"[DebugLogViewer] LAN debug log viewer listening at {server.Url}; LAN URLs: {string.Join(", ", lanUrls)}";
154+
}
155+
138156
private static async Task WorkerLoopAsync()
139157
{
140158
var token = _cts!.Token;

Diagnostics/Logging/RitsuDebugLogViewerOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ internal sealed record RitsuDebugLogViewerOptions(
44
bool Enabled,
55
bool MirrorGameLogs,
66
bool AutoOpen,
7+
bool LanAccessEnabled,
78
int Port,
89
int PortFallbackCount,
910
string AccessToken,

Diagnostics/Logging/RitsuDebugLogViewerServer.cs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ internal sealed class RitsuDebugLogViewerServer : IDisposable
2525

2626
public RitsuDebugLogViewerServer(
2727
string token,
28+
bool lanAccessEnabled,
2829
Func<int, RitsuDebugLogRecord[]> historyProvider,
2930
Func<object> statusProvider,
3031
string? assetRoot)
3132
{
3233
_token = token;
34+
LanAccessEnabled = lanAccessEnabled;
3335
_historyProvider = historyProvider;
3436
_statusProvider = statusProvider;
3537
_assetRoot = string.IsNullOrWhiteSpace(assetRoot) ? null : assetRoot;
@@ -39,7 +41,13 @@ public RitsuDebugLogViewerServer(
3941

4042
public int ClientCount => _clients.Count;
4143

42-
public string Url => $"http://127.0.0.1:{Port}/?token={Uri.EscapeDataString(_token)}";
44+
public bool LanAccessEnabled { get; }
45+
46+
public string AccessMode => LanAccessEnabled ? "lan" : "loopback";
47+
48+
public string Url => BuildUrl("127.0.0.1");
49+
50+
public IReadOnlyList<string> LanUrls => LanAccessEnabled ? GetLanUrls() : [];
4351

4452
public void Dispose()
4553
{
@@ -59,7 +67,7 @@ public void Start(int requestedPort, int fallbackCount)
5967
var port = firstPort + i;
6068
try
6169
{
62-
_listener = new(IPAddress.Loopback, port);
70+
_listener = new(LanAccessEnabled ? IPAddress.Any : IPAddress.Loopback, port);
6371
_listener.Start();
6472
break;
6573
}
@@ -73,7 +81,7 @@ public void Start(int requestedPort, int fallbackCount)
7381

7482
if (_listener == null)
7583
throw new InvalidOperationException(
76-
$"Could not bind local debug viewer port range {firstPort}-{firstPort + maxAttempts - 1}.",
84+
$"Could not bind debug viewer port range {firstPort}-{firstPort + maxAttempts - 1}.",
7785
lastException);
7886

7987
Port = ((IPEndPoint)_listener.LocalEndpoint).Port;
@@ -291,6 +299,40 @@ private static int ParseLimit(string query)
291299
return int.TryParse(GetQueryValue(query, "limit"), out var limit) ? Math.Clamp(limit, 1, 20000) : 5000;
292300
}
293301

302+
private string BuildUrl(string host)
303+
{
304+
return $"http://{host}:{Port}/?token={Uri.EscapeDataString(_token)}";
305+
}
306+
307+
private IReadOnlyList<string> GetLanUrls()
308+
{
309+
if (Port <= 0)
310+
return [];
311+
312+
try
313+
{
314+
return Dns.GetHostAddresses(Dns.GetHostName())
315+
.Where(IsUsableLanAddress)
316+
.Select(address => BuildUrl(address.ToString()))
317+
.Distinct(StringComparer.OrdinalIgnoreCase)
318+
.ToArray();
319+
}
320+
catch (Exception ex) when (ex is SocketException or InvalidOperationException)
321+
{
322+
RitsuDebugLogPipeline.ReportInternalWarning($"Could not enumerate LAN addresses: {ex.Message}");
323+
return [];
324+
}
325+
}
326+
327+
private static bool IsUsableLanAddress(IPAddress address)
328+
{
329+
if (address.AddressFamily != AddressFamily.InterNetwork || IPAddress.IsLoopback(address))
330+
return false;
331+
332+
var bytes = address.GetAddressBytes();
333+
return bytes is not [0, 0, 0, 0] and not [169, 254, _, _];
334+
}
335+
294336
private static string? GetQueryValue(string query, string key)
295337
{
296338
return (from pair in query.Split('&', StringSplitOptions.RemoveEmptyEntries)

Settings/Integration/RitsuLibModSettingsBootstrap.DebugLogViewerPage.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ private static void RegisterDebugLogViewerPage(RitsuLibModSettingsUiBindings ui)
2121
ui.DebugLogViewerAutoOpen,
2222
T("ritsulib.debugLogViewer.autoOpen.description",
2323
"When enabled, RitsuLib waits 3 seconds after starting the viewer. If no browser page is listening, it opens one."))
24+
.AddToggle(
25+
"debug_log_viewer_lan_access",
26+
T("ritsulib.debugLogViewer.lanAccess.label", "Allow LAN access"),
27+
ui.DebugLogViewerLanAccessEnabled,
28+
T("ritsulib.debugLogViewer.lanAccess.description",
29+
"When enabled, the debug log viewer listens on all network interfaces so other devices on the same LAN can open it with the tokenized URL. Changes apply on next launch."))
2430
.AddIntSlider(
2531
"debug_log_viewer_port",
2632
T("ritsulib.debugLogViewer.port.label", "Viewer port"),
@@ -30,15 +36,15 @@ private static void RegisterDebugLogViewerPage(RitsuLibModSettingsUiBindings ui)
3036
1,
3137
value => value.ToString(),
3238
T("ritsulib.debugLogViewer.port.description",
33-
"Loopback HTTP port. If the port is busy, RitsuLib tries the following ports in order. Changes apply on next launch."))
39+
"HTTP port. If the port is busy, RitsuLib tries the following ports in order. Changes apply on next launch."))
3440
.AddButton(
3541
"debug_log_viewer_open_now",
3642
T("ritsulib.debugLogViewer.openNow.label", "Open viewer"),
3743
T("ritsulib.debugLogViewer.openNow.button", "Open log viewer"),
3844
OpenDebugLogViewerFromSettings,
3945
ModSettingsButtonTone.Accent,
4046
T("ritsulib.debugLogViewer.openNow.description",
41-
"Opens the currently running local log viewer in your system browser."))),
47+
"Opens the currently running log viewer in your system browser."))),
4248
"debug-log-viewer");
4349
}
4450

Settings/Integration/RitsuLibModSettingsUiBindings.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ private RitsuLibModSettingsUiBindings()
1515
public IModSettingsValueBinding<bool> DebugCompatLocTable { get; private init; } = null!;
1616
public IModSettingsValueBinding<bool> DebugCompatUnlockEpoch { get; private init; } = null!;
1717
public IModSettingsValueBinding<bool> DebugLogViewerAutoOpen { get; private init; } = null!;
18+
public IModSettingsValueBinding<bool> DebugLogViewerLanAccessEnabled { get; private init; } = null!;
1819
public IModSettingsValueBinding<int> DebugLogViewerPort { get; private init; } = null!;
1920

2021
public IModSettingsValueBinding<bool> DebugCompatAncientArchitect { get; private init; } =
@@ -152,6 +153,13 @@ public static RitsuLibModSettingsUiBindings Create()
152153
settings => settings.DebugLogViewerAutoOpen,
153154
(settings, value) => settings.DebugLogViewerAutoOpen = value),
154155
() => defaults.DebugLogViewerAutoOpen),
156+
DebugLogViewerLanAccessEnabled = ModSettingsBindings.WithDefault(
157+
ModSettingsBindings.Global<RitsuLibSettings, bool>(
158+
Const.ModId,
159+
Const.SettingsKey,
160+
settings => settings.DebugLogViewerLanAccessEnabled,
161+
(settings, value) => settings.DebugLogViewerLanAccessEnabled = value),
162+
() => defaults.DebugLogViewerLanAccessEnabled),
155163
DebugLogViewerPort = ModSettingsBindings.WithDefault(
156164
ModSettingsBindings.Global<RitsuLibSettings, int>(
157165
Const.ModId,
@@ -510,6 +518,5 @@ private static string NormalizeToastAnimation(string? value)
510518
_ => "fadeslide",
511519
};
512520
}
513-
514521
}
515522
}

Settings/Localization/ModSettingsUi/eng.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,13 @@
223223
"ritsulib.debugLogViewer.description": "Configure the browser-based live log viewer. Port changes apply on next game launch.",
224224
"ritsulib.debugLogViewer.autoOpen.label": "Open browser automatically",
225225
"ritsulib.debugLogViewer.autoOpen.description": "When enabled, RitsuLib waits 3 seconds after starting the viewer. If no browser page is listening, it opens one.",
226+
"ritsulib.debugLogViewer.lanAccess.label": "Allow LAN access",
227+
"ritsulib.debugLogViewer.lanAccess.description": "When enabled, the debug log viewer listens on all network interfaces so other devices on the same LAN can open it with the tokenized URL. Changes apply on next launch.",
226228
"ritsulib.debugLogViewer.port.label": "Viewer port",
227-
"ritsulib.debugLogViewer.port.description": "Loopback HTTP port. If the port is busy, RitsuLib tries the following ports in order. Changes apply on next launch.",
229+
"ritsulib.debugLogViewer.port.description": "HTTP port. If the port is busy, RitsuLib tries the following ports in order. Changes apply on next launch.",
228230
"ritsulib.debugLogViewer.openNow.label": "Open viewer",
229231
"ritsulib.debugLogViewer.openNow.button": "Open log viewer",
230-
"ritsulib.debugLogViewer.openNow.description": "Opens the currently running local log viewer in your system browser.",
232+
"ritsulib.debugLogViewer.openNow.description": "Opens the currently running log viewer in your system browser.",
231233
"ritsulib.section.harmonyDump.title": "Harmony patch dump",
232234
"ritsulib.section.harmonyDump.description": "Export a text report of patched methods (prefix/postfix/transpiler/finalizer) for debugging mod interactions.",
233235
"ritsulib.section.selfCheck.title": "Self-check mode",

Settings/Localization/ModSettingsUi/zhs.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,13 @@
223223
"ritsulib.debugLogViewer.description": "配置基于浏览器的实时日志查看器。端口修改会在下次启动游戏时生效。",
224224
"ritsulib.debugLogViewer.autoOpen.label": "自动打开浏览器",
225225
"ritsulib.debugLogViewer.autoOpen.description": "启用后,RitsuLib 会在查看器启动后等待 3 秒;如果没有浏览器页面正在监听,就自动打开一个页面。",
226+
"ritsulib.debugLogViewer.lanAccess.label": "允许局域网访问",
227+
"ritsulib.debugLogViewer.lanAccess.description": "启用后,调试日志查看器会监听所有网络接口,同一局域网内的其他设备可通过带 token 的 URL 打开。修改会在下次启动时生效。",
226228
"ritsulib.debugLogViewer.port.label": "查看器端口",
227-
"ritsulib.debugLogViewer.port.description": "本机 HTTP 端口。如果端口被占用,RitsuLib 会按顺序尝试后续端口。修改会在下次启动时生效。",
229+
"ritsulib.debugLogViewer.port.description": "HTTP 端口。如果端口被占用,RitsuLib 会按顺序尝试后续端口。修改会在下次启动时生效。",
228230
"ritsulib.debugLogViewer.openNow.label": "打开查看器",
229231
"ritsulib.debugLogViewer.openNow.button": "打开日志查看器",
230-
"ritsulib.debugLogViewer.openNow.description": "在系统浏览器中打开当前正在运行的本机日志查看器",
232+
"ritsulib.debugLogViewer.openNow.description": "在系统浏览器中打开当前正在运行的日志查看器",
231233
"ritsulib.section.harmonyDump.title": "Harmony 补丁导出",
232234
"ritsulib.section.harmonyDump.description": "导出已补丁方法的文本报告(前缀/后缀/Transpiler/Finalizer),用于排查模组冲突。",
233235
"ritsulib.section.selfCheck.title": "自检模式",

Viewer/src/logTypes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ export type Status = {
2020
sessionStartedAtUtc?: string;
2121
processId?: number;
2222
url?: string;
23+
accessMode?: "loopback" | "lan";
24+
lanAccessEnabled?: boolean;
25+
lanUrls?: string[];
2326
bufferCount: number;
2427
bufferCapacity: number;
2528
queueDepth: number;

0 commit comments

Comments
 (0)