Skip to content

Commit

Permalink
feat: Add real-time log streaming and logging stack configuration
Browse files Browse the repository at this point in the history
fix: Fix exit signal handle
  • Loading branch information
lifegpc committed Jan 3, 2025
1 parent ebd90ca commit 5a851be
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 8 deletions.
4 changes: 4 additions & 0 deletions api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -226,6 +229,7 @@ components:
- max_import_img_count
- enable_server_timing
- thumbnail_format
- logging_stack
ConfigUpdated:
description: result of updateConfig
type: object
Expand Down
2 changes: 2 additions & 0 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
66 changes: 66 additions & 0 deletions routes/api/log/realtime.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 4 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)],
Expand Down
51 changes: 47 additions & 4 deletions utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 5a851be

Please sign in to comment.