diff options
author | Yuqian Yang <crupest@crupest.life> | 2025-06-11 15:26:43 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-06-11 15:26:43 +0800 |
commit | ca70902efa25b3855e4a018db7e30f7f2ada1d77 (patch) | |
tree | 7d83930d54e2afe6601eda31b8727231540e0033 | |
parent | 417a389ecdaddcf3fade720d31e717f299ed11b8 (diff) | |
download | crupest-ca70902efa25b3855e4a018db7e30f7f2ada1d77.tar.gz crupest-ca70902efa25b3855e4a018db7e30f7f2ada1d77.tar.bz2 crupest-ca70902efa25b3855e4a018db7e30f7f2ada1d77.zip |
fix(mail-server): log output bug.
-rw-r--r-- | deno/base/log.ts | 97 | ||||
-rw-r--r-- | deno/mail-relay/app.ts | 11 | ||||
-rw-r--r-- | deno/mail-relay/dumb-smtp-server.ts | 40 | ||||
-rw-r--r-- | deno/mail-relay/mail.ts | 14 |
4 files changed, 63 insertions, 99 deletions
diff --git a/deno/base/log.ts b/deno/base/log.ts index d0a5b80..cc71dfa 100644 --- a/deno/base/log.ts +++ b/deno/base/log.ts @@ -4,43 +4,19 @@ import { toFileNameString } from "./date.ts"; export type LogLevel = "error" | "warn" | "info"; -export interface LogEntry { - content: [unknown, ...unknown[]]; +export interface LogOptions { level?: LogLevel; cause?: unknown; } -export interface LogEntryBuilder { - withLevel(level: LogLevel): LogEntryBuilder; - withCause(cause: unknown): LogEntryBuilder; - setError(error: boolean): LogEntryBuilder; - write(): void; -} - export interface ExternalLogStream extends Disposable { stream: WritableStream; } export class Logger { - #indentSize = 2; + #defaultLevel = "info" as const; #externalLogDir?: string; - #contextStack: { depth: number; level: LogLevel }[] = [ - { depth: 0, level: "info" }, - ]; - - get #context() { - return this.#contextStack.at(-1)!; - } - - get indentSize() { - return this.#indentSize; - } - - set indentSize(value: number) { - this.#indentSize = value; - } - get externalLogDir() { return this.#externalLogDir; } @@ -54,64 +30,37 @@ export class Logger { } } - write(entry: LogEntry): void { - const { content, level, cause } = entry; - const [message, ...rest] = content; - console[level ?? this.#context.level]( - " ".repeat(this.#indentSize * this.#context.depth) + String(message), - ...(cause != null ? [cause, ...rest] : rest), - ); + write(message: string, options?: LogOptions): void { + const logFunction = console[options?.level ?? this.#defaultLevel]; + if (options?.cause != null) { + logFunction.call(console, message, options.cause); + } else { + logFunction.call(console, message); + } } - push(entry: LogEntry): Disposable { - this.write(entry); - this.#contextStack.push({ - depth: this.#context.depth + 1, - level: entry.level ?? this.#context.level, - }); - return { - [Symbol.dispose]: () => { - this.#contextStack.pop(); - }, - }; + info(message: string) { + this.write(message, { level: "info" }); } - info(message: unknown, ...args: unknown[]) { - this.write({ level: "info", content: [message, ...args] }); + tagInfo(tag: string, message: string) { + this.info(tag + " " + message); } - warn(message: unknown, ...args: unknown[]) { - this.write({ level: "warn", content: [message, ...args] }); + warn(message: string) { + this.write(message, { level: "warn" }); } - error(message: unknown, ...args: unknown[]) { - this.write({ level: "error", content: [message, ...args] }); + tagWarn(tag: string, message: string) { + this.warn(tag + " " + message); } - builder(message: unknown, ...args: unknown[]): LogEntryBuilder { - const entry: LogEntry = { - content: [message, ...args], - level: "info", - cause: undefined, - }; - const builder: LogEntryBuilder = { - withCause: (cause) => { - entry.cause = cause; - return builder; - }, - withLevel: (level) => { - entry.level = level; - return builder; - }, - setError: (error) => { - if (error) entry.level = "error"; - return builder; - }, - write: () => { - this.write(entry); - }, - }; - return builder; + error(message: string, cause?: unknown) { + this.write(message, { level: "info", cause }); + } + + tagError(tag: string, message: string, cause?: unknown) { + this.error(tag + " " + message, cause); } async createExternalLogStream( diff --git a/deno/mail-relay/app.ts b/deno/mail-relay/app.ts index 3cac44b..d96fa1d 100644 --- a/deno/mail-relay/app.ts +++ b/deno/mail-relay/app.ts @@ -43,7 +43,7 @@ export function createHono( const hono = new Hono(); hono.onError((err, c) => { - logger.error(err); + logger.error("Hono handler throws an error.", err); return c.json({ msg: "Server error, check its log." }, 500); }); hono.use(honoLogger()); @@ -81,10 +81,9 @@ export async function sendMail(logger: Logger, port: number) { method: "post", body: text, }); - logger.builder(res).setError(!res.ok).write(); - logger - .builder("Body\n" + (await res.text())) - .setError(!res.ok) - .write(); + logger.write(Deno.inspect(res), { level: res.ok ? "info" : "error" }); + logger.write(Deno.inspect(await res.text()), { + level: res.ok ? "info" : "error", + }); if (!res.ok) Deno.exit(-1); } diff --git a/deno/mail-relay/dumb-smtp-server.ts b/deno/mail-relay/dumb-smtp-server.ts index 15cafa5..1a1090a 100644 --- a/deno/mail-relay/dumb-smtp-server.ts +++ b/deno/mail-relay/dumb-smtp-server.ts @@ -17,6 +17,8 @@ function createResponses(host: string, port: number | string) { } as const; } +const LOG_TAG = "[dumb-smtp]"; + export class DumbSmtpServer { #logger; #deliverer; @@ -33,9 +35,12 @@ export class DumbSmtpServer { async #handleConnection(conn: Deno.Conn) { using disposeStack = new DisposableStack(); disposeStack.defer(() => { - this.#logger.info("Close smtp session tcp connection."); + this.#logger.tagInfo(LOG_TAG, "Close session's tcp connection."); conn.close(); }); + + this.#logger.tagInfo(LOG_TAG, "New session's tcp connection established."); + const writer = conn.writable.getWriter(); disposeStack.defer(() => writer.releaseLock()); const reader = conn.readable.getReader(); @@ -43,8 +48,10 @@ export class DumbSmtpServer { const [decoder, encoder] = [new TextDecoder(), new TextEncoder()]; const decode = (data: Uint8Array) => decoder.decode(data); - const send = async (s: string) => + const send = async (s: string) => { + this.#logger.tagInfo(LOG_TAG, "Send line: " + s); await writer.write(encoder.encode(s + CRLF)); + }; let buffer: string = ""; let rawMail: string | null = null; @@ -65,7 +72,7 @@ export class DumbSmtpServer { buffer = buffer.slice(eolPos + CRLF.length); if (rawMail == null) { - this.#logger.info("Smtp server received line:", line); + this.#logger.tagInfo(LOG_TAG, "Received line: " + line); const upperLine = line.toUpperCase(); if (upperLine.startsWith("EHLO") || upperLine.startsWith("HELO")) { await send(this.#responses["EHLO"]); @@ -75,26 +82,32 @@ export class DumbSmtpServer { await send(this.#responses["RCPT"]); } else if (upperLine === "DATA") { await send(this.#responses["DATA"]); - this.#logger.info("Begin to receive mail data..."); + this.#logger.tagInfo(LOG_TAG, "Begin to receive mail data..."); rawMail = ""; } else if (upperLine === "QUIT") { await send(this.#responses["QUIT"]); return; } else { - this.#logger.warn("Smtp server command unrecognized:", line); + this.#logger.tagWarn( + LOG_TAG, + "Unrecognized command from client: " + line, + ); await send(this.#responses["INVALID"]); return; } } else { if (line === ".") { try { - this.#logger.info("Done receiving mail data, begin to relay..."); + this.#logger.tagInfo( + LOG_TAG, + "Mail data Received, begin to relay...", + ); const { message } = await this.#deliverer.deliverRaw(rawMail); await send(`250 2.6.0 ${message}`); rawMail = null; - this.#logger.info("Done SMTP mail session."); + this.#logger.tagInfo(LOG_TAG, "Relay succeeded."); } catch (err) { - this.#logger.info(err); + this.#logger.tagError(LOG_TAG, "Relay failed.", err); await send("554 5.3.0 Error: check server log"); return; } @@ -110,16 +123,19 @@ export class DumbSmtpServer { async serve(options: { hostname: string; port: number }) { const listener = Deno.listen(options); this.#responses = createResponses(options.hostname, options.port); - this.#logger.info( - `Dumb SMTP server starts running on ${this.#responses.serverName}.`, + this.#logger.tagInfo( + LOG_TAG, + `Dumb SMTP server starts to listen on ${this.#responses.serverName}.`, ); for await (const conn of listener) { try { await this.#handleConnection(conn); } catch (cause) { - this.#logger.error( - "One smtp connection session throws an error " + cause, + this.#logger.tagError( + LOG_TAG, + "Tcp connection throws an error.", + cause, ); } } diff --git a/deno/mail-relay/mail.ts b/deno/mail-relay/mail.ts index 8c977fe..12d5972 100644 --- a/deno/mail-relay/mail.ts +++ b/deno/mail-relay/mail.ts @@ -31,7 +31,9 @@ class MailSimpleParsedHeaders { if (match != null) { return match[1]; } else { - this.#logger?.warn("Invalid message-id header of mail: ", messageIdField); + this.#logger?.warn( + "Invalid message-id header of mail: " + messageIdField, + ); return undefined; } } @@ -240,8 +242,7 @@ export abstract class MailDeliverer { await hook.callback(context); } - context.logger.info("Deliver result:"); - context.logger.info(context.result); + context.logger.info("Deliver result:\n" + Deno.inspect(context.result)); if (context.result.hasError()) { throw new Error("Mail failed to deliver."); @@ -290,8 +291,8 @@ export class RecipientFromHeadersHook implements MailDeliverHook { .forEach((r) => context.recipients.add(r)); context.logger.info( - "Recipients found from mail headers: ", - [...context.recipients].join(" "), + "Recipients found from mail headers: " + + [...context.recipients].join(", "), ); } return Promise.resolve(); @@ -304,8 +305,7 @@ export class FallbackRecipientHook implements MailDeliverHook { callback(context: MailDeliverContext) { if (context.recipients.size === 0) { context.logger.info( - "No recipients, fill with fallback: ", - [...this.fallback].join(" "), + "No recipients, fill with fallback: " + [...this.fallback].join(", "), ); this.fallback.forEach((a) => context.recipients.add(a)); } |