1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
import { join } from "@std/path";
import { toWritableStream, Writer } from "@std/io";
import "./better-js.ts";
export interface LogOptions {
time?: Date;
error?: boolean;
}
export type LogFile = Pick<Deno.FsFile, "writable"> & Disposable;
export class Log {
#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 });
}
}
infoOrError(isError: boolean, ...args: unknown[]) {
this[isError ? "error" : "info"].call(this, ...args);
}
info(...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 log = new Log();
export default log;
|