0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローディング・スピナーを邪魔しないCLIロガーを実装する

Last updated at Posted at 2024-01-19

以下のインターフェイスをもつロガーを実装する

const terminal = new TerminalLogger();

const spinner1 = terminal.spinner("loading1...").start()
const spinner2 = terminal.spinner("loading2...").start()

terminal.debug("this is logging in the middle")

spinner1.succeed()
spinner2.succeed()

スピナーと通常のロギングの責務をまとめて持たせることで、スピナーの再レンダリングなどのタイミングを隠蔽して、スピナーの途中でログを吐いても画面を汚さない。

複数スピナーが同時にレンダリングされるケースも考慮できる。

あとはスピナーライブラリの依存もカプセル化できるので、たとえばこの記事ではoraをスピナーライブラリとして使うが、そこから別のスピナーに乗り換えたい、などのケースも変更点を小さく実装できる。

実装

具体的な実装は以下。

ログレベルなどの制御は一旦無視。

import { randomUUID } from "crypto";
import ora, { Ora } from "ora";

export class TerminalLogger {
  private readonly logger: Console;
  private spinners: Map<string, Ora>;

  constructor() {
    this.logger = console;
    this.spinners = new Map();
  }

  error(msg?: unknown, ...params: unknown[]) {
    this.withRerenderSpinners(() => {
      this.logger.error(msg, ...params);
    });
  }

  info(msg?: unknown, ...params: unknown[]) {
    this.withRerenderSpinners(() => {
      this.logger.info(msg, ...params);
    });
  }

  debug(msg?: unknown, ...params: unknown[]) {
    this.withRerenderSpinners(() => {
      this.logger.debug(msg, ...params);
    });
  }

  spinner(msg?: string) {
    const spinner = ora(msg);
    const id = randomUUID();
    const controller = {
      start: (text?: string) => {
        spinner.start(text);
        return controller;
      },
      succeed: (text?: string) => {
        this.spinners.delete(id);
        return spinner.succeed(text);
      },
      fail: (text?: string) => {
        this.spinners.delete(id);
        return spinner.fail(text);
      },
    };

    this.spinners.set(id, spinner);
    return controller;
  }

  private withRerenderSpinners(cb: () => void) {
    this.spinners.forEach((s) => {
      s.clear();
    });
    cb();
    this.spinners.forEach((s) => {
      s.render();
    });
  }
}

まず spinner メソッドで新しくスピナーが生成されるたびにTerminalLoggerにいま表示中のスピナーインスタンスを保持させるようにしている。

こうしておけば、info/error/debugなどのロギングのタイミングで保持しているスピナーすべてイテレーションし、毎回クリア&再レンダーできる。それをやっているのが withRerenderSpinners メソッドになる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?