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.ts115
1 files changed, 115 insertions, 0 deletions
diff --git a/services/docker/mail-server/relay/logger.ts b/services/docker/mail-server/relay/logger.ts
new file mode 100644
index 0000000..dc6acfe
--- /dev/null
+++ b/services/docker/mail-server/relay/logger.ts
@@ -0,0 +1,115 @@
+import { join } from "@std/path";
+import { toWritableStream, Writer } from "@std/io";
+
+import "./util.ts";
+
+export interface LogOptions {
+ time?: Date;
+ error?: boolean;
+}
+
+export type LogFile = Pick<Deno.FsFile, "writable"> & Disposable;
+
+export class Logger {
+ #path: string | null = null;
+
+ #wrapWriter(writer: Writer): LogFile {
+ return {
+ writable: toWritableStream(writer, { autoClose: false }),
+ [Symbol.dispose]() {},
+ };
+ }
+
+ #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 });
+ }
+ }
+
+ 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,
+ create: true,
+ });
+ }
+
+ async openLogForProgram(
+ program: string,
+ 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]();
+ },
+ };
+ }
+}
+
+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);