mirror of
https://github.com/netbirdio/netbird.git
synced 2026-06-11 18:02:48 -04:00
forward ui browser logs to go
This commit is contained in:
@@ -15,6 +15,11 @@ import { welcome } from "@/lib/welcome";
|
||||
import LoginWaitingForBrowserDialog from "@/modules/login/LoginWaitingForBrowserDialog.tsx";
|
||||
import { initI18n } from "@/lib/i18n";
|
||||
import { initPlatform } from "@/lib/platform";
|
||||
import { initLogForwarding } from "@/lib/logs";
|
||||
|
||||
// Install console.* + uncaught-error forwarding before anything else runs
|
||||
// so even init-time logs reach the Go log pipeline.
|
||||
initLogForwarding();
|
||||
|
||||
welcome();
|
||||
|
||||
|
||||
80
client/ui/frontend/src/lib/logs.ts
Normal file
80
client/ui/frontend/src/lib/logs.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { UILog } from "@bindings/services";
|
||||
|
||||
// Forwards browser console output and uncaught errors into the Go logrus
|
||||
// pipeline. Originals still fire, so DevTools is unchanged; the Go
|
||||
// --log-level does the gating.
|
||||
|
||||
type Level = "trace" | "debug" | "info" | "warn" | "error";
|
||||
|
||||
const METHOD_LEVELS: Record<string, Level> = {
|
||||
trace: "trace",
|
||||
debug: "debug",
|
||||
log: "info",
|
||||
info: "info",
|
||||
warn: "warn",
|
||||
error: "error",
|
||||
};
|
||||
|
||||
// Sources whose output is noise and shouldn't be forwarded.
|
||||
const IGNORED_SOURCES = new Set(["welcome.ts"]);
|
||||
|
||||
let installed = false;
|
||||
|
||||
function format(args: unknown[]): string {
|
||||
return args
|
||||
.map((a) => {
|
||||
if (typeof a === "string") return a;
|
||||
if (a instanceof Error) return a.stack || a.message;
|
||||
try {
|
||||
return JSON.stringify(a);
|
||||
} catch {
|
||||
return String(a);
|
||||
}
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
// First stack frame outside this module as "<file>:<line>" (best-effort;
|
||||
// minified prod stacks degrade to chunk names).
|
||||
function callerSource(): string {
|
||||
const stack = new Error().stack;
|
||||
if (!stack) return "";
|
||||
for (const line of stack.split("\n").slice(1)) {
|
||||
if (line.includes("/logs.ts")) continue;
|
||||
const m = line.match(/([^/\\() ]+\.[a-z]+):(\d+):\d+/i);
|
||||
if (m) return `${m[1]}:${m[2]}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function forward(level: Level, args: unknown[]) {
|
||||
try {
|
||||
const source = callerSource();
|
||||
if (IGNORED_SOURCES.has(source.split(":")[0])) return;
|
||||
// Fire-and-forget; don't touch console here (would recurse).
|
||||
void UILog.Log(level, source, format(args));
|
||||
} catch {
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
|
||||
export function initLogForwarding() {
|
||||
if (installed) return;
|
||||
installed = true;
|
||||
|
||||
const c = console as unknown as Record<string, (...a: unknown[]) => void>;
|
||||
for (const [method, level] of Object.entries(METHOD_LEVELS)) {
|
||||
const original = c[method]?.bind(console);
|
||||
c[method] = (...args: unknown[]) => {
|
||||
original?.(...args);
|
||||
forward(level, args);
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("error", (e) => {
|
||||
forward("error", [`uncaught error: ${e.message}`, e.error ?? ""]);
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (e) => {
|
||||
forward("error", ["unhandled promise rejection:", e.reason]);
|
||||
});
|
||||
}
|
||||
@@ -316,6 +316,7 @@ func registerServices(app *application.App, conn *Conn, s registeredServices) {
|
||||
app.RegisterService(application.NewService(services.NewPreferences(s.prefStore)))
|
||||
app.RegisterService(application.NewService(services.NewAutostart(app.Autostart)))
|
||||
app.RegisterService(application.NewService(services.NewVersion()))
|
||||
app.RegisterService(application.NewService(services.NewUILog()))
|
||||
}
|
||||
|
||||
// newMainWindow creates the hidden main window, sized to the user's last view
|
||||
|
||||
38
client/ui/services/uilog.go
Normal file
38
client/ui/services/uilog.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build !android && !ios && !freebsd && !js
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// UILog lets the frontend forward console output into the Go logrus
|
||||
// pipeline. The JS origin rides in the message ("[ui <file:line>] ...")
|
||||
// because logrus's ReportCaller always pins the source to this file.
|
||||
type UILog struct{}
|
||||
|
||||
func NewUILog() *UILog { return &UILog{} }
|
||||
|
||||
// Log forwards one frontend console entry. level is trace/debug/info/warn/
|
||||
// error (anything else → info); source is the JS origin (may be empty).
|
||||
func (s *UILog) Log(_ context.Context, level, source, msg string) {
|
||||
if source != "" {
|
||||
msg = "[ui " + source + "] " + msg
|
||||
} else {
|
||||
msg = "[ui] " + msg
|
||||
}
|
||||
switch level {
|
||||
case "trace":
|
||||
logrus.Trace(msg)
|
||||
case "debug":
|
||||
logrus.Debug(msg)
|
||||
case "warn", "warning":
|
||||
logrus.Warn(msg)
|
||||
case "error":
|
||||
logrus.Error(msg)
|
||||
default:
|
||||
logrus.Info(msg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user