aboutsummaryrefslogtreecommitdiff
path: root/deno/base/log.ts
blob: cc71dfa97291eaab8d7385d34372d21f59664ea3 (plain)
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
import { join } from "@std/path";

import { toFileNameString } from "./date.ts";

export type LogLevel = "error" | "warn" | "info";

export interface LogOptions {
  level?: LogLevel;
  cause?: unknown;
}

export interface ExternalLogStream extends Disposable {
  stream: WritableStream;
}

export class Logger {
  #defaultLevel = "info" as const;
  #externalLogDir?: string;

  get externalLogDir() {
    return this.#externalLogDir;
  }

  set externalLogDir(value: string | undefined) {
    this.#externalLogDir = value;
    if (value != null) {
      Deno.mkdirSync(value, {
        recursive: true,
      });
    }
  }

  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);
    }
  }

  info(message: string) {
    this.write(message, { level: "info" });
  }

  tagInfo(tag: string, message: string) {
    this.info(tag + " " + message);
  }

  warn(message: string) {
    this.write(message, { level: "warn" });
  }

  tagWarn(tag: string, message: string) {
    this.warn(tag + " " + message);
  }

  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(
    name: string,
    options?: {
      noTime?: boolean;
    },
  ): Promise<ExternalLogStream> {
    if (name.includes("/")) {
      throw new Error(`External log stream's name (${name}) contains '/'.`);
    }
    if (this.#externalLogDir == null) {
      throw new Error("External log directory is not set.");
    }

    const logPath = join(
      this.#externalLogDir,
      options?.noTime === true
        ? name
        : `${name}-${toFileNameString(new Date())}`,
    );

    const file = await Deno.open(logPath, {
      read: false,
      write: true,
      append: true,
      create: true,
    });
    return {
      stream: file.writable,
      [Symbol.dispose]: file[Symbol.dispose].bind(file),
    };
  }

  async createExternalLogStreamsForProgram(
    program: string,
  ): Promise<{ stdout: WritableStream; stderr: WritableStream } & Disposable> {
    const stdout = await this.createExternalLogStream(`${program}-stdout`);
    const stderr = await this.createExternalLogStream(`${program}-stderr`);
    return {
      stdout: stdout.stream,
      stderr: stderr.stream,
      [Symbol.dispose]: () => {
        stdout[Symbol.dispose]();
        stderr[Symbol.dispose]();
      },
    };
  }
}