🔊 CLI better logging: new option for finer level, changed count tracking, bit of grammar

This commit is contained in:
Eric Doughty-Papassideris
2024-03-28 00:33:56 +01:00
committed by ericlinagora
parent b72a958817
commit bde006c63e
6 changed files with 86 additions and 26 deletions
@@ -3,6 +3,7 @@ import yargs from "yargs";
import type { TdrivePlatform } from "../../../core/platform/platform"; import type { TdrivePlatform } from "../../../core/platform/platform";
import runWithPlatform from "../../lib/run_with_platform"; import runWithPlatform from "../../lib/run_with_platform";
import parseYargsCommaSeparatedStringArray from "../../utils/yargs-comma-array"; import parseYargsCommaSeparatedStringArray from "../../utils/yargs-comma-array";
import runWithLoggerLevel from "../../utils/run-with-logger-level";
import type BaseReindexer from "./index-all/base-reindexer"; import type BaseReindexer from "./index-all/base-reindexer";
import type ReindexerOptions from "./index-all/reindexer-options"; import type ReindexerOptions from "./index-all/reindexer-options";
@@ -58,6 +59,14 @@ const command: yargs.CommandModule<unknown, unknown> = {
"When processing documents, limit to those owned by the users with the provided comma separated e-mails", "When processing documents, limit to those owned by the users with the provided comma separated e-mails",
group: "Filtering for documents repository", group: "Filtering for documents repository",
}, },
verboseDuringRun: {
type: "boolean",
alias: "V",
description:
"Set to verbose logging except for startup and shutdown of Platform. Use flag once for 'info' level, twice for 'debug'.",
group: "Output",
count: true,
},
}, },
handler: async argv => { handler: async argv => {
const repositories = parseYargsCommaSeparatedStringArray( const repositories = parseYargsCommaSeparatedStringArray(
@@ -67,19 +76,28 @@ const command: yargs.CommandModule<unknown, unknown> = {
argv.filterDocumentsByUserEMail as string /* ignore typechecker */, argv.filterDocumentsByUserEMail as string /* ignore typechecker */,
); );
await runWithPlatform("Re-index", async ({ spinner, platform }) => { await runWithPlatform("Re-index", async ({ spinner, platform }) => {
try { return await runWithLoggerLevel(
for (const repositoryName of repositories) argv.verboseDuringRun
await RepositoryNameToCTOR.get(repositoryName) ? (argv.verboseDuringRun as number) > 1
.ctor(platform, { ? "debug"
spinner, : "info"
repairEntities: !!argv.repairEntities, : undefined,
filterDocumentsByUserEMail, async () => {
}) try {
.run(); for (const repositoryName of repositories)
} catch (err) { await RepositoryNameToCTOR.get(repositoryName)
spinner.fail(err.stack || err); .ctor(platform, {
return 1; spinner,
} repairEntities: !!argv.repairEntities,
filterDocumentsByUserEMail,
})
.run();
} catch (err) {
spinner.fail(err.stack || err);
return 1;
}
},
);
}); });
}, },
}; };
@@ -12,12 +12,16 @@ import waitTimeoutMS from "../../../utils/wait-timeout";
import iterateOverRepoPages from "../../../utils/iterate-over-repository-pages"; import iterateOverRepoPages from "../../../utils/iterate-over-repository-pages";
import type ReindexerOptions from "./reindexer-options"; import type ReindexerOptions from "./reindexer-options";
const pluralificationate = (noun: string) => noun.replace(/s$/i, "").replace(/y$/i, "ie") + "s";
/** This is an abstract base class for re-index cli commands; it stores runtime options /** This is an abstract base class for re-index cli commands; it stores runtime options
* into fields; runs repair first if requested and re-indexes generically from the database * into fields; runs repair first if requested and re-indexes generically from the database
* to search services. */ * to search services. */
export default abstract class BaseReindexer<Entity> { export default abstract class BaseReindexer<Entity> {
protected readonly database: DatabaseServiceAPI; protected readonly database: DatabaseServiceAPI;
protected readonly search: SearchServiceAPI; protected readonly search: SearchServiceAPI;
private readonly tablePlural: string;
constructor( constructor(
protected readonly platform: TdrivePlatform, protected readonly platform: TdrivePlatform,
protected readonly options: ReindexerOptions, protected readonly options: ReindexerOptions,
@@ -26,12 +30,15 @@ export default abstract class BaseReindexer<Entity> {
) { ) {
this.database = this.platform.getProvider<DatabaseServiceAPI>("database"); this.database = this.platform.getProvider<DatabaseServiceAPI>("database");
this.search = this.platform.getProvider<SearchServiceAPI>("search"); this.search = this.platform.getProvider<SearchServiceAPI>("search");
this.tablePlural = pluralificationate(this.table);
} }
private _dbRepo: Repository<Entity>; private _dbRepo: Repository<Entity>;
protected async dbRepository() { protected async dbRepository() {
return (this._dbRepo = return (this._dbRepo =
this._dbRepo || (await this.database.getRepository<Entity>(this.table, this.entity))); this._dbRepo || (await this.database.getRepository<Entity>(this.table, this.entity)));
} }
private _searchRepo: SearchRepository<Entity>; private _searchRepo: SearchRepository<Entity>;
protected async searchRepository() { protected async searchRepository() {
return (this._searchRepo = return (this._searchRepo =
@@ -39,19 +46,19 @@ export default abstract class BaseReindexer<Entity> {
} }
protected statusStart(info: string) { protected statusStart(info: string) {
this.options.spinner.start(`${this.table} > ${info}`); this.options.spinner.start(`${this.tablePlural} > ${info}`);
} }
protected statusSucceed(info: string) { protected statusSucceed(info: string) {
this.options.spinner.succeed(`${this.table} > ${info}`); this.options.spinner.succeed(`${this.tablePlural} > ${info}`);
} }
protected statusWarn(info: string) { protected statusWarn(info: string) {
this.options.spinner.warn(`${this.table} > ${info}`); this.options.spinner.warn(`${this.tablePlural} > ${info}`);
} }
protected statusFail(info: string) { protected statusFail(info: string) {
this.options.spinner.fail(`${this.table} > ${info}`); this.options.spinner.fail(`${this.tablePlural} > ${info}`);
} }
protected statusInfo(info: string) { protected statusInfo(info: string) {
this.options.spinner.info(`${this.table} > ${info}`); this.options.spinner.info(`${this.tablePlural} > ${info}`);
} }
/** Override in sub-classes to translate a page from database to search entities. /** Override in sub-classes to translate a page from database to search entities.
@@ -71,7 +78,7 @@ export default abstract class BaseReindexer<Entity> {
* on the entity type. * on the entity type.
*/ */
protected async repairEntities(_findOptions: FindOptions): Promise<void> { protected async repairEntities(_findOptions: FindOptions): Promise<void> {
this.statusWarn(`repairEntities > No repair action for ${this.table}`); this.statusWarn(`repairEntities > No repair action for ${this.tablePlural}`);
} }
/** Override in a sub-class to completely replace the re-indexing logic. /** Override in a sub-class to completely replace the re-indexing logic.
@@ -89,12 +96,12 @@ export default abstract class BaseReindexer<Entity> {
entities = await this.mapEntitiesToReIndexFromDBToSearch(entities); entities = await this.mapEntitiesToReIndexFromDBToSearch(entities);
await this.search.upsert(entities); await this.search.upsert(entities);
count += entities.length; count += entities.length;
this.statusStart(`Indexed ${count} ${this.table}...`); this.statusStart(`Indexed ${count} ${this.tablePlural}...`);
}, },
findOptions, findOptions,
); );
if (count === 0) this.statusWarn(`Index ${this.table} finished; but 0 items included`); if (count === 0) this.statusWarn(`Index ${this.tablePlural} finished; but 0 items included`);
else this.statusSucceed(`${count} ${this.table} indexed`); else this.statusSucceed(`${count} ${this.tablePlural} indexed`);
const giveFlushAChanceDurationMS = 10000; const giveFlushAChanceDurationMS = 10000;
this.statusStart(`Emptying flush (${giveFlushAChanceDurationMS / 1000}s)...`); this.statusStart(`Emptying flush (${giveFlushAChanceDurationMS / 1000}s)...`);
await waitTimeoutMS(giveFlushAChanceDurationMS); await waitTimeoutMS(giveFlushAChanceDurationMS);
@@ -60,6 +60,8 @@ export default class DocumentsReindexer extends BaseReindexer<DriveFile> {
"Download and re-extract keywords before re-indexing"; "Download and re-extract keywords before re-indexing";
protected override async repairEntities(findOptions: FindOptions): Promise<void> { protected override async repairEntities(findOptions: FindOptions): Promise<void> {
const repository = await this.dbRepository(); const repository = await this.dbRepository();
let count = 0,
countChanged = 0;
await iterateOverRepoPages( await iterateOverRepoPages(
repository, repository,
async entities => { async entities => {
@@ -70,6 +72,7 @@ export default class DocumentsReindexer extends BaseReindexer<DriveFile> {
this.statusInfo( this.statusInfo(
`-> Downloading ${entity.name} (${entity.size}b id: ${entity.id} creator: ${entity.creator}`, `-> Downloading ${entity.name} (${entity.size}b id: ${entity.id} creator: ${entity.creator}`,
); );
count++;
try { try {
if ( if (
entity.size === 0 || entity.size === 0 ||
@@ -97,11 +100,13 @@ export default class DocumentsReindexer extends BaseReindexer<DriveFile> {
if (content_keywords !== entity.content_keywords) { if (content_keywords !== entity.content_keywords) {
entity.content_keywords = content_keywords; entity.content_keywords = content_keywords;
repository.save(entity); repository.save(entity);
countChanged++;
} }
} catch (err) { } catch (err) {
this.statusFail(err.stack); this.statusFail(err.stack);
} }
} }
this.statusStart(`Repaired ${count} documents (${countChanged} changed)`);
}, },
findOptions, findOptions,
); );
@@ -24,7 +24,8 @@ export default class UsersReindexer extends BaseReindexer<User> {
CompanyUserTYPE, CompanyUserTYPE,
CompanyUser, CompanyUser,
); );
let count = 0; let count = 0,
countChanged = 0;
const repository = await this.dbRepository(); const repository = await this.dbRepository();
await iterateOverRepoPages(repository, async entities => { await iterateOverRepoPages(repository, async entities => {
for (const user of entities) { for (const user of entities) {
@@ -33,11 +34,18 @@ export default class UsersReindexer extends BaseReindexer<User> {
user.cache ||= { companies: [] }; user.cache ||= { companies: [] };
user.cache.companies = companies.getEntities().map(company => company.group_id); user.cache.companies = companies.getEntities().map(company => company.group_id);
const newCache = JSON.stringify(user.cache); const newCache = JSON.stringify(user.cache);
if (prevCache != newCache) await repository.save(user, undefined); if (prevCache != newCache) {
await repository.save(user, undefined);
countChanged++;
}
} }
count += entities.length; count += entities.length;
this.statusStart(`repairEntities > Adding companies to cache of ${count} users...`); this.statusStart(
`repairEntities > Adding companies to cache of ${count} users (${countChanged} changed)...`,
);
}); });
this.statusSucceed(`repairEntities > Added companies to cache of ${count} users`); this.statusSucceed(
`repairEntities > Added companies to cache of ${count} users (${countChanged} changed)`,
);
} }
} }
@@ -5,6 +5,8 @@ import tdrive from "../../tdrive";
import gr from "../../services/global-resolver"; import gr from "../../services/global-resolver";
import type { TdrivePlatform } from "../../core/platform/platform"; import type { TdrivePlatform } from "../../core/platform/platform";
//TODO: When this gets used for all commands; update verboseDuringRun from search/index-all and move to root index
/** /**
* Start the platform and its services, run the command (passed as * Start the platform and its services, run the command (passed as
* the `handler` callback), then cleanly shut down the platform. * the `handler` callback), then cleanly shut down the platform.
@@ -0,0 +1,20 @@
import { logger } from "../../core/platform/framework/logger";
type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
/** Run the callback with the provided log level set (if undefined it's left identical).
* Restores the previous level before returning.
*/
export default async function runWithLoggerLevel<R = void>(
level: LogLevel | undefined,
callback: () => R,
) {
if (level === undefined) return await callback();
const previousLogLevel = logger.level;
try {
logger.level = level;
return await callback();
} finally {
logger.level = previousLogLevel;
}
}