check / check (push) Canceled after 0s
Every line quak prints on stderr now goes through one log, in src/cli-log.ts, at one of six levels. The default, info, prints what quak printed before. -v adds each retry and the start and end of each refresh and ML data fetch; -vv adds each request with its result. -q prints only fatal lines, and is refused with -v; both are program options. Every non-zero exit logs one fatal line, so a run that exits 1 because files failed says how many and where to look. Every line reporting a failure is at error. --json changes stdout only, so backup and --json runs log their progress on stderr. withRetry and ApiClient report retries and requests through new callbacks. Model: opus-5-5
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
// The CLI's log level, and the one function the commands write stderr lines
|
|
// through.
|
|
//
|
|
// Every line quak prints on stderr is a log line at one of six levels, from
|
|
// the quietest: fatal, error, warning, info, debug, trace. A run prints the
|
|
// lines at its own level and at every quieter one. Its level is info unless
|
|
// `-v` or `-q` asks for another (see "CLI surface" in the README). Every
|
|
// command that exits non-zero logs a fatal line saying why, so even `-q`, which
|
|
// prints only fatal lines, never leaves a non-zero exit unexplained. An error
|
|
// line reports something that failed, such as a file or an album, and a
|
|
// warning something that did not fail but needs the user's attention. stdout
|
|
// carries only a command's output and is not a log.
|
|
//
|
|
// The library itself writes no lines: its layers report through callbacks, and
|
|
// the CLI logs what they report. `logRequests` gives the API client options
|
|
// that log each request and each retry, and `logRefresh` the library's
|
|
// `onProgress` that logs what a refresh and an ML data fetch report.
|
|
|
|
import type { ApiClientOptions } from "./api/client.js";
|
|
import { describeError } from "./errors.js";
|
|
import type { RefreshEvent } from "./library/index.js";
|
|
import type { RetryOptions } from "./retry.js";
|
|
|
|
const LEVELS = ["fatal", "error", "warning", "info", "debug", "trace"] as const;
|
|
|
|
export type LogLevel = (typeof LEVELS)[number];
|
|
|
|
// Writes `line` on stderr when the run's level prints lines at `level`.
|
|
export type Log = (level: LogLevel, line: string) => void;
|
|
|
|
// The level asked for by `-v` given `verbose` times, or by `-q`: info without
|
|
// either, debug for `-v`, trace for `-vv` and more, fatal for `-q`.
|
|
// `bin/quak.ts` refuses `-q` and `-v` together.
|
|
export const logLevel = (verbose: number, quiet: boolean): LogLevel => {
|
|
if (quiet) return "fatal";
|
|
if (verbose === 0) return "info";
|
|
return verbose === 1 ? "debug" : "trace";
|
|
};
|
|
|
|
// The log that writes each line at `level` or quieter to `stderr`, ending it
|
|
// with a newline.
|
|
export const stderrLog =
|
|
(stderr: { write(text: string): unknown }, level: LogLevel): Log =>
|
|
(lineLevel, line) => {
|
|
if (LEVELS.indexOf(lineLevel) <= LEVELS.indexOf(level)) {
|
|
stderr.write(line + "\n");
|
|
}
|
|
};
|
|
|
|
// API client options that log each request at trace, with the status of its
|
|
// response or the error it failed with, and each retry at debug, with why and
|
|
// how long it waits. `retry` is the retry policy, the default when not given.
|
|
export const logRequests = (
|
|
log: Log,
|
|
retry?: RetryOptions,
|
|
): ApiClientOptions => ({
|
|
retry: {
|
|
...retry,
|
|
onRetry: (err, attempt, delayMs) =>
|
|
log(
|
|
"debug",
|
|
`Attempt ${attempt} failed, retrying in ${Math.round(delayMs)} ms: ${describeError(err)}`,
|
|
),
|
|
},
|
|
onRequest: ({ method, url, status, error }) =>
|
|
log(
|
|
"trace",
|
|
`${method} ${url}: ${error === undefined ? `HTTP ${status}` : describeError(error)}`,
|
|
),
|
|
});
|
|
|
|
// How a log line names each operation the library reports the start and end
|
|
// of.
|
|
const OPERATION_NAMES: Record<RefreshEvent["operation"], string> = {
|
|
refresh: "Refresh",
|
|
fetchMLData: "ML data fetch",
|
|
precacheThumbnails: "Thumbnail precache",
|
|
precacheOriginals: "Original precache",
|
|
};
|
|
|
|
// A library `onProgress` that logs, at info, each collection whose files a
|
|
// refresh has fetched, and at debug the start and end of each refresh and ML
|
|
// data fetch, with the error of one that failed.
|
|
export const logRefresh =
|
|
(log: Log) =>
|
|
(event: RefreshEvent): void => {
|
|
if (event.status === "progress") {
|
|
log(
|
|
"info",
|
|
`Fetched the files of ${event.fetched} of ${event.total} collections`,
|
|
);
|
|
return;
|
|
}
|
|
const error = event.error === undefined ? "" : `: ${event.error}`;
|
|
log(
|
|
"debug",
|
|
`${OPERATION_NAMES[event.operation]} ${event.status}${error}`,
|
|
);
|
|
};
|