feat: add logger

This commit is contained in:
divocat
2025-10-18 22:15:43 +03:00
parent 7ab0384e0b
commit f6e347af78
12 changed files with 298 additions and 130 deletions

View File

@@ -1,3 +1,5 @@
import { logger } from '../podkop';
export async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
@@ -16,6 +18,6 @@ export async function withTimeout<T>(
} finally {
clearTimeout(timeoutId);
const elapsed = performance.now() - start;
console.log(`[${operationName}] Execution time: ${elapsed.toFixed(2)} ms`);
logger.info('[SHELL]', `[${operationName}] took ${elapsed.toFixed(2)} ms`);
}
}

View File

@@ -1,8 +1,10 @@
import { TabServiceInstance } from './tab.service';
import { store } from './store.service';
import { logger } from './logger.service';
export function coreService() {
TabServiceInstance.onChange((activeId, tabs) => {
logger.info('[TAB]', activeId);
store.set({
tabService: {
current: activeId || '',

View File

@@ -2,3 +2,4 @@ export * from './tab.service';
export * from './core.service';
export * from './socket.service';
export * from './store.service';
export * from './logger.service';

View File

@@ -0,0 +1,66 @@
import { downloadAsTxt } from '../../helpers/downloadAsTxt';
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export class Logger {
private logs: string[] = [];
private readonly levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
private format(level: LogLevel, ...args: unknown[]): string {
return `[${level.toUpperCase()}] ${args.join(' ')}`;
}
private push(level: LogLevel, ...args: unknown[]): void {
if (!this.levels.includes(level)) level = 'info';
const message = this.format(level, ...args);
this.logs.push(message);
switch (level) {
case 'error':
console.error(message);
break;
case 'warn':
console.warn(message);
break;
case 'info':
console.info(message);
break;
default:
console.log(message);
}
}
debug(...args: unknown[]): void {
this.push('debug', ...args);
}
info(...args: unknown[]): void {
this.push('info', ...args);
}
warn(...args: unknown[]): void {
this.push('warn', ...args);
}
error(...args: unknown[]): void {
this.push('error', ...args);
}
clear(): void {
this.logs = [];
}
getLogs(): string {
return this.logs.join('\n');
}
download(filename = 'logs.txt'): void {
if (typeof document === 'undefined') {
console.warn('Logger.download() доступен только в браузере');
return;
}
downloadAsTxt(this.getLogs(), filename);
}
}
export const logger = new Logger();

View File

@@ -1,3 +1,5 @@
import { logger } from './logger.service';
// eslint-disable-next-line
type Listener = (data: any) => void;
type ErrorListener = (error: Event | string) => void;
@@ -28,7 +30,11 @@ class SocketManager {
ws.close();
}
} catch (err) {
console.warn(`resetAll: failed to close socket ${url}`, err);
logger.error(
'[SOCKET]',
`resetAll: failed to close socket ${url}`,
err,
);
}
}
@@ -36,7 +42,7 @@ class SocketManager {
this.listeners.clear();
this.errorListeners.clear();
this.connected.clear();
console.info('[SocketManager] All connections and state have been reset.');
logger.info('[SOCKET]', 'All connections and state have been reset.');
}
connect(url: string): void {
@@ -47,7 +53,11 @@ class SocketManager {
try {
ws = new WebSocket(url);
} catch (err) {
console.error(`Failed to construct WebSocket for ${url}:`, err);
logger.error(
'[SOCKET]',
`failed to construct WebSocket for ${url}:`,
err,
);
this.triggerError(url, err instanceof Event ? err : String(err));
return;
}
@@ -59,7 +69,7 @@ class SocketManager {
ws.addEventListener('open', () => {
this.connected.set(url, true);
console.info(`Connected: ${url}`);
logger.info('[SOCKET]', 'Connected to', url);
});
ws.addEventListener('message', (event) => {
@@ -69,7 +79,7 @@ class SocketManager {
try {
handler(event.data);
} catch (err) {
console.error(`Handler error for ${url}:`, err);
logger.error('[SOCKET]', `Handler error for ${url}:`, err);
}
}
}
@@ -77,12 +87,12 @@ class SocketManager {
ws.addEventListener('close', () => {
this.connected.set(url, false);
console.warn(`Disconnected: ${url}`);
logger.warn('[SOCKET]', `Disconnected: ${url}`);
this.triggerError(url, 'Connection closed');
});
ws.addEventListener('error', (err) => {
console.error(`Socket error for ${url}:`, err);
logger.error('[SOCKET]', `Socket error for ${url}:`, err);
this.triggerError(url, err);
});
}
@@ -118,7 +128,7 @@ class SocketManager {
if (ws && this.connected.get(url)) {
ws.send(typeof data === 'string' ? data : JSON.stringify(data));
} else {
console.warn(`Cannot send: not connected to ${url}`);
logger.warn('[SOCKET]', `Cannot send: not connected to ${url}`);
this.triggerError(url, 'Not connected');
}
}
@@ -147,7 +157,7 @@ class SocketManager {
try {
cb(err);
} catch (e) {
console.error(`Error handler threw for ${url}:`, e);
logger.error('[SOCKET]', `Error handler threw for ${url}:`, e);
}
}
}

View File

@@ -5,7 +5,7 @@ import {
} from '../../../helpers';
import { prettyBytes } from '../../../helpers/prettyBytes';
import { CustomPodkopMethods, PodkopShellMethods } from '../../methods';
import { socket, store, StoreType } from '../../services';
import { logger, socket, store, StoreType } from '../../services';
import { renderSections, renderWidget } from './partials';
import { fetchServicesInfo } from '../../fetchers';
@@ -24,7 +24,7 @@ async function fetchDashboardSections() {
const { data, success } = await CustomPodkopMethods.getDashboardSections();
if (!success) {
console.log('[fetchDashboardSections]: failed to fetch');
logger.error('[DASHBOARD]', 'fetchDashboardSections: failed to fetch');
}
store.set({
@@ -38,7 +38,6 @@ async function fetchDashboardSections() {
}
async function connectToClashSockets() {
console.log('[SOCKET] connectToClashSockets');
socket.subscribe(
`${getClashWsUrl()}/traffic?token=`,
(msg) => {
@@ -53,8 +52,9 @@ async function connectToClashSockets() {
});
},
(_err) => {
console.log(
'[fetchDashboardSections]: failed to connect',
logger.error(
'[DASHBOARD]',
'connectToClashSockets - traffic: failed to connect to',
getClashWsUrl(),
);
store.set({
@@ -92,8 +92,9 @@ async function connectToClashSockets() {
});
},
(_err) => {
console.log(
'[fetchDashboardSections]: failed to connect',
logger.error(
'[DASHBOARD]',
'connectToClashSockets - connections: failed to connect to',
getClashWsUrl(),
);
store.set({
@@ -163,7 +164,7 @@ async function handleTestProxyLatency(tag: string) {
// Renderer
async function renderSectionsWidget() {
console.log('renderSectionsWidget');
logger.debug('[DASHBOARD]', 'renderSectionsWidget');
const sectionsWidget = store.get().sectionsWidget;
const container = document.getElementById('dashboard-sections-grid');
@@ -212,7 +213,7 @@ async function renderSectionsWidget() {
}
async function renderBandwidthWidget() {
console.log('renderBandwidthWidget');
logger.debug('[DASHBOARD]', 'renderBandwidthWidget');
const traffic = store.get().bandwidthWidget;
const container = document.getElementById('dashboard-widget-traffic');
@@ -242,7 +243,7 @@ async function renderBandwidthWidget() {
}
async function renderTrafficTotalWidget() {
console.log('renderTrafficTotalWidget');
logger.debug('[DASHBOARD]', 'renderTrafficTotalWidget');
const trafficTotalWidget = store.get().trafficTotalWidget;
const container = document.getElementById('dashboard-widget-traffic-total');
@@ -278,7 +279,7 @@ async function renderTrafficTotalWidget() {
}
async function renderSystemInfoWidget() {
console.log('renderSystemInfoWidget');
logger.debug('[DASHBOARD]', 'renderSystemInfoWidget');
const systemInfoWidget = store.get().systemInfoWidget;
const container = document.getElementById('dashboard-widget-system-info');
@@ -314,7 +315,7 @@ async function renderSystemInfoWidget() {
}
async function renderServicesInfoWidget() {
console.log('renderServicesInfoWidget');
logger.debug('[DASHBOARD]', 'renderServicesInfoWidget');
const servicesInfoWidget = store.get().servicesInfoWidget;
const container = document.getElementById('dashboard-widget-service-info');
@@ -422,19 +423,29 @@ function registerLifecycleListeners() {
diff.tabService &&
next.tabService.current !== prev.tabService.current
) {
console.log(
new Date().toISOString(),
'[Active Tab on dashboard]',
logger.debug(
'[DASHBOARD]',
'active tab diff event, active tab:',
diff.tabService.current,
);
const isDashboardVisible = next.tabService.current === 'dashboard';
if (isDashboardVisible) {
logger.debug(
'[DASHBOARD]',
'registerLifecycleListeners',
'onPageMount',
);
return onPageMount();
}
if (!isDashboardVisible) {
onPageUnmount();
logger.debug(
'[DASHBOARD]',
'registerLifecycleListeners',
'onPageUnmount',
);
return onPageUnmount();
}
}
});
@@ -442,6 +453,7 @@ function registerLifecycleListeners() {
export async function initController(): Promise<void> {
onMount('dashboard-status').then(() => {
logger.debug('[DASHBOARD]', 'initController', 'onMount');
onPageMount();
registerLifecycleListeners();
});

View File

@@ -45,8 +45,6 @@ export async function runDnsCheck() {
Boolean(data.bootstrap_dns_status) ||
Boolean(data.dns_status);
console.log('dnsChecks', dnsChecks);
function getStatus() {
if (allGood) {
return 'success';

View File

@@ -20,12 +20,6 @@ export async function runFakeIPCheck() {
const checkFakeIPResponse = await RemoteFakeIPMethods.getFakeIpCheck();
const checkIPResponse = await RemoteFakeIPMethods.getIpCheck();
console.log('runFakeIPCheck', {
routerFakeIPResponse,
checkFakeIPResponse,
checkIPResponse,
});
const checks = {
router: routerFakeIPResponse.success && routerFakeIPResponse.data.fakeip,
browserFakeIP:
@@ -36,8 +30,6 @@ export async function runFakeIPCheck() {
checkFakeIPResponse.data.IP !== checkIPResponse.data.IP,
};
console.log('checks', checks);
const allGood = checks.router || checks.browserFakeIP || checks.differentIP;
const atLeastOneGood =
checks.router && checks.browserFakeIP && checks.differentIP;

View File

@@ -54,8 +54,6 @@ export async function runNftCheck() {
Boolean(data.rules_proxy_counters) ||
Boolean(data.rules_other_mark_exist);
console.log('nftablesChecks', nftablesChecks);
function getStatus() {
if (allGood) {
return 'success';

View File

@@ -47,8 +47,6 @@ export async function runSingBoxCheck() {
Boolean(data.sing_box_process_running) ||
Boolean(data.sing_box_ports_listening);
console.log('singBoxChecks', singBoxChecks);
function getStatus() {
if (allGood) {
return 'success';

View File

@@ -4,7 +4,7 @@ import { runSingBoxCheck } from './checks/runSingBoxCheck';
import { runNftCheck } from './checks/runNftCheck';
import { runFakeIPCheck } from './checks/runFakeIPCheck';
import { loadingDiagnosticsChecksStore } from './diagnostic.store';
import { store, StoreType } from '../../services';
import { logger, store, StoreType } from '../../services';
import {
IRenderSystemInfoRow,
renderAvailableActions,
@@ -43,7 +43,7 @@ async function fetchSystemInfo() {
}
function renderDiagnosticsChecks() {
console.log('renderDiagnosticsChecks');
logger.debug('[DIAGNOSTIC]', 'renderDiagnosticsChecks');
const diagnosticsChecks = store
.get()
.diagnosticsChecks.sort((a, b) => a.order - b.order);
@@ -59,7 +59,7 @@ function renderDiagnosticsChecks() {
}
function renderDiagnosticRunActionWidget() {
console.log('renderDiagnosticRunActionWidget');
logger.debug('[DIAGNOSTIC]', 'renderDiagnosticRunActionWidget');
const { loading } = store.get().diagnosticsRunAction;
const container = document.getElementById('pdk_diagnostic-page-run-check');
@@ -86,7 +86,7 @@ async function handleRestart() {
try {
await PodkopShellMethods.restart();
} catch (e) {
console.log('handleRestart - e', e);
logger.error('[DIAGNOSTIC]', 'handleRestart - e', e);
} finally {
setTimeout(async () => {
await fetchServicesInfo();
@@ -113,7 +113,7 @@ async function handleStop() {
try {
await PodkopShellMethods.stop();
} catch (e) {
console.log('handleStop - e', e);
logger.error('[DIAGNOSTIC]', 'handleStop - e', e);
} finally {
await fetchServicesInfo();
store.set({
@@ -138,7 +138,7 @@ async function handleStart() {
try {
await PodkopShellMethods.start();
} catch (e) {
console.log('handleStart - e', e);
logger.error('[DIAGNOSTIC]', 'handleStart - e', e);
} finally {
setTimeout(async () => {
await fetchServicesInfo();
@@ -165,7 +165,7 @@ async function handleEnable() {
try {
await PodkopShellMethods.enable();
} catch (e) {
console.log('handleEnable - e', e);
logger.error('[DIAGNOSTIC]', 'handleEnable - e', e);
} finally {
await fetchServicesInfo();
store.set({
@@ -189,7 +189,7 @@ async function handleDisable() {
try {
await PodkopShellMethods.disable();
} catch (e) {
console.log('handleDisable - e', e);
logger.error('[DIAGNOSTIC]', 'handleDisable - e', e);
} finally {
await fetchServicesInfo();
store.set({
@@ -220,7 +220,7 @@ async function handleShowGlobalCheck() {
);
}
} catch (e) {
console.log('handleShowGlobalCheck - e', e);
logger.error('[DIAGNOSTIC]', 'handleShowGlobalCheck - e', e);
} finally {
store.set({
diagnosticsActions: {
@@ -250,7 +250,7 @@ async function handleViewLogs() {
);
}
} catch (e) {
console.log('handleViewLogs - e', e);
logger.error('[DIAGNOSTIC]', 'handleViewLogs - e', e);
} finally {
store.set({
diagnosticsActions: {
@@ -280,7 +280,7 @@ async function handleShowSingBoxConfig() {
);
}
} catch (e) {
console.log('handleViewLogs - e', e);
logger.error('[DIAGNOSTIC]', 'handleShowSingBoxConfig - e', e);
} finally {
store.set({
diagnosticsActions: {
@@ -294,7 +294,7 @@ async function handleShowSingBoxConfig() {
function renderDiagnosticAvailableActionsWidget() {
const diagnosticsActions = store.get().diagnosticsActions;
const servicesInfoWidget = store.get().servicesInfoWidget;
console.log('renderDiagnosticActionsWidget');
logger.debug('[DIAGNOSTIC]', 'renderDiagnosticAvailableActionsWidget');
const podkopEnabled = Boolean(servicesInfoWidget.data.podkop);
const singBoxRunning = Boolean(servicesInfoWidget.data.singbox);
@@ -363,7 +363,7 @@ function renderDiagnosticAvailableActionsWidget() {
}
function renderDiagnosticSystemInfoWidget() {
console.log('renderDiagnosticSystemInfoWidget');
logger.debug('[DIAGNOSTIC]', 'renderDiagnosticSystemInfoWidget');
const diagnosticsSystemInfo = store.get().diagnosticsSystemInfo;
const container = document.getElementById('pdk_diagnostic-page-system-info');
@@ -471,14 +471,13 @@ async function runChecks() {
await runFakeIPCheck();
} catch (e) {
console.log('runChecks - e', e);
logger.error('[DIAGNOSTIC]', 'runChecks - e', e);
} finally {
store.set({ diagnosticsRunAction: { loading: false } });
}
}
function onPageMount() {
console.log('diagnostic controller initialized.');
// Cleanup before mount
onPageUnmount();
@@ -523,19 +522,29 @@ function registerLifecycleListeners() {
diff.tabService &&
next.tabService.current !== prev.tabService.current
) {
console.log(
new Date().toISOString(),
'[Active Tab on diagnostics]',
logger.debug(
'[DIAGNOSTIC]',
'active tab diff event, active tab:',
diff.tabService.current,
);
const isDashboardVisible = next.tabService.current === 'diagnostic';
const isDIAGNOSTICVisible = next.tabService.current === 'diagnostic';
if (isDashboardVisible) {
if (isDIAGNOSTICVisible) {
logger.debug(
'[DIAGNOSTIC]',
'registerLifecycleListeners',
'onPageMount',
);
return onPageMount();
}
if (!isDashboardVisible) {
onPageUnmount();
if (!isDIAGNOSTICVisible) {
logger.debug(
'[DIAGNOSTIC]',
'registerLifecycleListeners',
'onPageUnmount',
);
return onPageUnmount();
}
}
});
@@ -543,6 +552,7 @@ function registerLifecycleListeners() {
export async function initController(): Promise<void> {
onMount('diagnostic-status').then(() => {
logger.debug('[DIAGNOSTIC]', 'initController', 'onMount');
onPageMount();
registerLifecycleListeners();
});