-v raises the CLI's log level per repeat; -q prints only fatal errors (closes #212)
check / check (push) Canceled after 0s
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
This commit was merged in pull request #216.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// 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}`,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user