aboutsummaryrefslogtreecommitdiff
path: root/services/docker/mail-server/relay/logger.ts
diff options
context:
space:
mode:
Diffstat (limited to 'services/docker/mail-server/relay/logger.ts')
-rw-r--r--services/docker/mail-server/relay/logger.ts127
1 files changed, 96 insertions, 31 deletions
diff --git a/services/docker/mail-server/relay/logger.ts b/services/docker/mail-server/relay/logger.ts
index 160d70a..dc6acfe 100644
--- a/services/docker/mail-server/relay/logger.ts
+++ b/services/docker/mail-server/relay/logger.ts
@@ -1,32 +1,83 @@
-import * as path from "@std/path";
+import { join } from "@std/path";
+import { toWritableStream, Writer } from "@std/io";
-import { createSingleton, generateTimeStringForFileName } from "./util.ts";
+import "./util.ts";
+
+export interface LogOptions {
+ time?: Date;
+ error?: boolean;
+}
+
+export type LogFile = Pick<Deno.FsFile, "writable"> & Disposable;
export class Logger {
- constructor(public readonly path: string) {}
+ #path: string | null = null;
- warn(message: string) {
- console.log(message);
+ #wrapWriter(writer: Writer): LogFile {
+ return {
+ writable: toWritableStream(writer, { autoClose: false }),
+ [Symbol.dispose]() {},
+ };
}
- generateLogFilePath(
- prefix: string = "",
- suffix: string = "",
- instant?: Temporal.Instant | Date,
- ): string {
- return path.join(
- this.path,
- `${prefix}-${generateTimeStringForFileName(instant)}-${suffix}`,
- );
+ #stdoutWrapper: LogFile = this.#wrapWriter(Deno.stdout);
+ #stderrWrapper: LogFile = this.#wrapWriter(Deno.stderr);
+
+ constructor() {
+ }
+
+ get path() {
+ return this.#path;
+ }
+
+ set path(path) {
+ this.#path = path;
+ if (path != null) {
+ Deno.mkdirSync(path, { recursive: true });
+ }
}
- openLogFile(
- prefix: string = "",
- suffix: string = "",
- instant?: Temporal.Instant | Date,
- ): Promise<Deno.FsFile> {
- const logPath = this.generateLogFilePath(prefix, suffix, instant);
- return Deno.open(logPath, {
+ log(...args: unknown[]) {
+ console.log(...args);
+ }
+
+ warn(...args: unknown[]) {
+ console.warn(...args);
+ }
+
+ error(...args: unknown[]) {
+ console.error(...args);
+ }
+
+ #extractOptions(options?: LogOptions): Required<LogOptions> {
+ return {
+ time: options?.time ?? new Date(),
+ error: options?.error ?? false,
+ };
+ }
+
+ async openLog(
+ prefix: string,
+ suffix: string,
+ options?: LogOptions,
+ ): Promise<LogFile> {
+ if (prefix.includes("/")) {
+ throw new Error(`Log file prefix ${prefix} contains '/'.`);
+ }
+ if (suffix.includes("/")) {
+ throw new Error(`Log file suffix ${suffix} contains '/'.`);
+ }
+
+ const { time, error } = this.#extractOptions(options);
+ if (this.#path == null) {
+ return error ? this.#stderrWrapper : this.#stdoutWrapper;
+ }
+
+ const logPath = join(
+ this.#path,
+ `${prefix}-${time.toFileNameString()}-${suffix}`,
+ );
+ return await Deno.open(logPath, {
read: false,
write: true,
append: true,
@@ -34,17 +85,31 @@ export class Logger {
});
}
- async logProgramOutput(
- process: Deno.ChildProcess,
+ async openLogForProgram(
program: string,
- instant?: Temporal.Instant | Date,
- ): Promise<void> {
- const stdoutFile = await this.openLogFile(program, "stdout", instant);
- const stderrFile = await this.openLogFile(program, "stderr", instant);
-
- process.stdout.pipeTo(stdoutFile.writable);
- process.stderr.pipeTo(stderrFile.writable);
+ options?: Omit<LogOptions, "error">,
+ ): Promise<{ stdout: LogFile; stderr: LogFile } & Disposable> {
+ const stdout = await this.openLog(program, "stdout", {
+ ...options,
+ error: false,
+ });
+ const stderr = await this.openLog(program, "stderr", {
+ ...options,
+ error: true,
+ });
+ return {
+ stdout,
+ stderr,
+ [Symbol.dispose]: () => {
+ stdout[Symbol.dispose]();
+ stderr[Symbol.dispose]();
+ },
+ };
}
}
-export const [getLogger, setLogger] = createSingleton<Logger>("Logger");
+const logger = new Logger();
+export default logger;
+export const log = logger.log.bind(logger);
+export const warn = logger.warn.bind(logger);
+export const error = logger.error.bind(logger);