mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-07-08 01:31:00 +08:00
feat: Add real-time log streaming and logging stack configuration
fix: Fix exit signal handle
This commit is contained in:
4
api.yml
4
api.yml
@@ -190,6 +190,9 @@ components:
|
||||
description: Whether to enable server time tracking
|
||||
thumbnail_format:
|
||||
$ref: "#/components/schemas/ThumbnailFormat"
|
||||
logging_stack:
|
||||
type: boolean
|
||||
description: Enable logging stack for all log levels
|
||||
Config:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/ConfigOptional"
|
||||
@@ -226,6 +229,7 @@ components:
|
||||
- max_import_img_count
|
||||
- enable_server_timing
|
||||
- thumbnail_format
|
||||
- logging_stack
|
||||
ConfigUpdated:
|
||||
description: result of updateConfig
|
||||
type: object
|
||||
|
||||
@@ -24,6 +24,7 @@ import * as $api_gallery_meta_gids_ from "./routes/api/gallery/meta/[gids].ts";
|
||||
import * as $api_health_check from "./routes/api/health_check.ts";
|
||||
import * as $api_log from "./routes/api/log.ts";
|
||||
import * as $api_log_id_ from "./routes/api/log/[id].ts";
|
||||
import * as $api_log_realtime from "./routes/api/log/realtime.ts";
|
||||
import * as $api_shared_token from "./routes/api/shared_token.ts";
|
||||
import * as $api_shared_token_list from "./routes/api/shared_token/list.ts";
|
||||
import * as $api_status from "./routes/api/status.ts";
|
||||
@@ -76,6 +77,7 @@ const manifest = {
|
||||
"./routes/api/health_check.ts": $api_health_check,
|
||||
"./routes/api/log.ts": $api_log,
|
||||
"./routes/api/log/[id].ts": $api_log_id_,
|
||||
"./routes/api/log/realtime.ts": $api_log_realtime,
|
||||
"./routes/api/shared_token.ts": $api_shared_token,
|
||||
"./routes/api/shared_token/list.ts": $api_shared_token_list,
|
||||
"./routes/api/status.ts": $api_status,
|
||||
|
||||
66
routes/api/log/realtime.ts
Normal file
66
routes/api/log/realtime.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { return_error } from "../../../server/utils.ts";
|
||||
import { User, UserPermission } from "../../../db.ts";
|
||||
import { base_logger, LogEntry } from "../../../utils/logger.ts";
|
||||
import { ExitTarget } from "../../../signal_handler.ts";
|
||||
import { toJSON } from "../../../utils.ts";
|
||||
|
||||
export type LogRealtimeClientData = { type: "ping" } | { type: "close" } | {
|
||||
type: "pong";
|
||||
};
|
||||
|
||||
export const handler: Handlers = {
|
||||
GET(req, ctx) {
|
||||
const u = <User | undefined> ctx.state.user;
|
||||
if (
|
||||
u && !u.is_admin &&
|
||||
!(Number(u.permissions) & UserPermission.QueryLog)
|
||||
) {
|
||||
return return_error(403, "Permission denied.");
|
||||
}
|
||||
const { socket, response } = Deno.upgradeWebSocket(req);
|
||||
const handle = (
|
||||
e: CustomEvent<LogEntry>,
|
||||
) => {
|
||||
if (socket.readyState === socket.OPEN) {
|
||||
socket.send(toJSON({ type: e.type, detail: e.detail }));
|
||||
}
|
||||
};
|
||||
const close_handle = () => {
|
||||
sendMessage({ type: "close" });
|
||||
socket.close();
|
||||
};
|
||||
const removeListener = () => {
|
||||
base_logger.removeEventListener("new_log", handle);
|
||||
ExitTarget.removeEventListener("close", close_handle);
|
||||
};
|
||||
function sendMessage(mes: { type: string }) {
|
||||
if (socket.readyState === socket.OPEN) {
|
||||
socket.send(toJSON(mes));
|
||||
}
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
sendMessage({ type: "ping" });
|
||||
}, 30000);
|
||||
socket.onclose = () => {
|
||||
clearInterval(interval);
|
||||
removeListener();
|
||||
};
|
||||
socket.onmessage = (e) => {
|
||||
try {
|
||||
const d: LogRealtimeClientData = JSON.parse(e.data);
|
||||
if (d.type == "close") {
|
||||
sendMessage({ type: "close" });
|
||||
socket.close();
|
||||
} else if (d.type == "ping") {
|
||||
sendMessage({ type: "pong" });
|
||||
}
|
||||
} catch (_) {
|
||||
null;
|
||||
}
|
||||
};
|
||||
base_logger.addEventListener("new_log", handle);
|
||||
ExitTarget.addEventListener("close", close_handle);
|
||||
return response;
|
||||
},
|
||||
};
|
||||
13
server.ts
13
server.ts
@@ -6,6 +6,7 @@ import twindPlugin from "$fresh/plugins/twind.ts";
|
||||
import twindConfig from "./twind.config.ts";
|
||||
import { load_translation } from "./server/i18ns.ts";
|
||||
import { base_logger } from "./utils/logger.ts";
|
||||
import { ExitTarget } from "./signal_handler.ts";
|
||||
|
||||
let task_manager: TaskManager | undefined = undefined;
|
||||
let cfg_path: string | undefined = undefined;
|
||||
@@ -37,15 +38,19 @@ export async function startServer(path: string) {
|
||||
if (!(e instanceof AlreadyClosedError)) throw e;
|
||||
});
|
||||
await load_translation(task_manager.aborts);
|
||||
setInterval(() => {
|
||||
const tasks: number[] = [];
|
||||
tasks.push(setInterval(() => {
|
||||
task_manager?.db.remove_expired_token();
|
||||
}, 86_400_000);
|
||||
setInterval(() => {
|
||||
}, 86_400_000));
|
||||
tasks.push(setInterval(() => {
|
||||
if (!task_manager) return;
|
||||
task_manager.db.remove_expired_ehmeta(
|
||||
task_manager.cfg.eh_metadata_cache_time,
|
||||
);
|
||||
}, 3600_000);
|
||||
}, 3600_000));
|
||||
ExitTarget.addEventListener("close", () => {
|
||||
for (const t of tasks) clearInterval(t);
|
||||
});
|
||||
return start(manifest, {
|
||||
signal: task_manager.aborts,
|
||||
plugins: [twindPlugin(twindConfig)],
|
||||
|
||||
@@ -59,7 +59,11 @@ export function format_message(
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
class BaseLogger {
|
||||
type EventMap = {
|
||||
new_log: LogEntry;
|
||||
};
|
||||
|
||||
class BaseLogger extends EventTarget {
|
||||
db?: Db;
|
||||
#cfg?: Config;
|
||||
#exist_table: Set<string> = new Set();
|
||||
@@ -144,16 +148,36 @@ class BaseLogger {
|
||||
level >= LogLevel.Warn
|
||||
? stackTrace(2)
|
||||
: undefined;
|
||||
this.db.query(
|
||||
"INSERT INTO log (time, message, level, type, stack) VALUES (?, ?, ?, ?, ?);",
|
||||
const now = new Date();
|
||||
const result = this.db.query<[number | bigint]>(
|
||||
"INSERT INTO log (time, message, level, type, stack) VALUES (?, ?, ?, ?, ?) RETURNING id;",
|
||||
[
|
||||
Date.now(),
|
||||
now.getTime(),
|
||||
message,
|
||||
level,
|
||||
type,
|
||||
stack === undefined ? null : stack,
|
||||
],
|
||||
);
|
||||
if (result) {
|
||||
const entry: LogEntry = {
|
||||
id: result[0][0],
|
||||
time: now,
|
||||
message,
|
||||
level,
|
||||
type,
|
||||
stack,
|
||||
};
|
||||
this.dispatchEvent("new_log", entry);
|
||||
}
|
||||
}
|
||||
// @ts-ignore Better type inference
|
||||
addEventListener<T extends keyof EventMap>(
|
||||
type: T,
|
||||
callback: (e: CustomEvent<EventMap[T]>) => void | Promise<void>,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void {
|
||||
super.addEventListener(type, <EventListener> callback, options);
|
||||
}
|
||||
clear(
|
||||
type?: string | null,
|
||||
@@ -245,6 +269,13 @@ class BaseLogger {
|
||||
if (!this.db) return;
|
||||
this.db.query("DELETE FROM log WHERE id = ?;", [id]);
|
||||
}
|
||||
// @ts-ignore Different parameters
|
||||
dispatchEvent<T extends keyof EventMap>(
|
||||
type: T,
|
||||
detail: EventMap[T],
|
||||
): boolean {
|
||||
return super.dispatchEvent(new CustomEvent(type, { detail }));
|
||||
}
|
||||
error(type: string, ...messages: unknown[]) {
|
||||
this.add(type, LogLevel.Error, ...messages);
|
||||
}
|
||||
@@ -369,6 +400,18 @@ class BaseLogger {
|
||||
if (!this.db) return;
|
||||
this.db.query("VACUUM;");
|
||||
}
|
||||
// @ts-ignore Better type inference
|
||||
removeEventListener<T extends keyof EventMap>(
|
||||
type: T,
|
||||
callback: (e: CustomEvent<EventMap[T]>) => void | Promise<void>,
|
||||
options?: boolean | EventListenerOptions,
|
||||
): void {
|
||||
super.removeEventListener(
|
||||
type,
|
||||
<EventListener> callback,
|
||||
options,
|
||||
);
|
||||
}
|
||||
trace(type: string, ...messages: unknown[]) {
|
||||
this.add(type, LogLevel.Trace, ...messages);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user