Skip to content

Commit

Permalink
Only retain last N plugins session logs.
Browse files Browse the repository at this point in the history
By default n===10.

N is Configurable using `--plugin-max-session-logs-folders=N`
CLI option.

fixes eclipse-theia#6866

Signed-off-by: I060847 <[email protected]>
  • Loading branch information
bd82 authored and vince-fugnitto committed Jan 29, 2020
1 parent 8748780 commit e34e39b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v0.15.0

- [terminal] always open terminal links on touchevents (e.g. when tapping a link on iPad) [#6875](https://github.com/eclipse-theia/theia/pull/6875)
- [plugin-ext] automatically remove old session logs folders, only the 10 (configurable using `--plugin-max-session-logs-folders=N`) most recent logs session folders will be retained [#6956](https://github.com/eclipse-theia/theia/pull/6956)

Breaking changes:

Expand Down
54 changes: 49 additions & 5 deletions packages/plugin-ext/src/main/node/paths/plugin-paths-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,43 @@
import { injectable, inject } from 'inversify';
import { FileSystem, FileStat } from '@theia/filesystem/lib/common';
import * as path from 'path';
import { readdir, remove } from 'fs-extra';
import * as crypto from 'crypto';
import URI from '@theia/core/lib/common/uri';
import { isWindows } from '@theia/core';
import { ILogger, isWindows } from '@theia/core';
import { PluginPaths } from './const';
import { PluginPathsService } from '../../common/plugin-paths-protocol';
import { THEIA_EXT, VSCODE_EXT, getTemporaryWorkspaceFileUri } from '@theia/workspace/lib/common';
import { PluginCliContribution } from '../plugin-cli-contribution';

const SESSION_TIMESTAMP_PATTERN = /^\d{8}T\d{6}$/;

// Service to provide configuration paths for plugin api.
@injectable()
export class PluginPathsServiceImpl implements PluginPathsService {

private readonly windowsDataFolders = [PluginPaths.WINDOWS_APP_DATA_DIR, PluginPaths.WINDOWS_ROAMING_DIR];

@inject(ILogger)
protected readonly logger: ILogger;

@inject(FileSystem)
protected readonly fileSystem: FileSystem;

@inject(PluginCliContribution)
protected readonly cliContribution: PluginCliContribution;

async getHostLogPath(): Promise<string> {
const parentLogsDir = await this.getLogsDirPath();

if (!parentLogsDir) {
throw new Error('Unable to get parent log directory');
}

const pluginDirPath = path.join(parentLogsDir, this.gererateTimeFolderName(), 'host');
const pluginDirPath = path.join(parentLogsDir, this.generateTimeFolderName(), 'host');
await this.fileSystem.createFolder(pluginDirPath);

// no `await` as We should never wait for the cleanup
this.cleanupOldLogs(parentLogsDir);
return new URI(pluginDirPath).path.toString();
}

Expand Down Expand Up @@ -94,8 +105,14 @@ export class PluginPathsServiceImpl implements PluginPathsService {
/**
* Generate time folder name in format: YYYYMMDDTHHMMSS, for example: 20181205T093828
*/
private gererateTimeFolderName(): string {
return new Date().toISOString().replace(/[-:]|(\..*)/g, '');
private generateTimeFolderName(): string {
const timeStamp = new Date().toISOString().replace(/[-:]|(\..*)/g, '');
// Helps ensure our timestamp generation logic is "valid".
// Changes to the timestamp structure may break old logs deletion logic.
if (!SESSION_TIMESTAMP_PATTERN.test(timeStamp)) {
this.logger.error(`Generated log folder name: "${timeStamp}" does not match expected pattern: ${SESSION_TIMESTAMP_PATTERN}`);
}
return timeStamp;
}

private async getLogsDirPath(): Promise<string> {
Expand Down Expand Up @@ -126,4 +143,31 @@ export class PluginPathsServiceImpl implements PluginPathsService {
return homeDirPath!;
}

private async cleanupOldLogs(parentLogsDir: string): Promise<void> {
// @ts-ignore - fs-extra types (Even latest version) is not updated with the `withFileTypes` option.
const dirEntries = await readdir(parentLogsDir, { withFileTypes: true });
// `Dirent` type is defined in @types/node since 10.10.0
// However, upgrading the @types/node in theia to 10.11 (as defined in engine field)
// Causes other packages to break in compilation, so we are using the infamous `any` type...
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subDirEntries = dirEntries.filter((dirent: any) => dirent.isDirectory() );
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subDirNames = subDirEntries.map((dirent: any) => dirent.name);
// We never clean a folder that is not a Theia logs session folder.
// Even if it does appears under the `parentLogsDir`...
const sessionSubDirNames = subDirNames.filter((dirName: string) => SESSION_TIMESTAMP_PATTERN.test(dirName));
// [].sort is ascending order and we need descending order (newest first).
const sortedSessionSubDirNames = sessionSubDirNames.sort().reverse();
const maxSessionLogsFolders = this.cliContribution.maxSessionLogsFolders();
// [5,4,3,2,1].slice(2) --> [2,1] --> only keep N latest session folders.
const oldSessionSubDirNames = sortedSessionSubDirNames.slice(maxSessionLogsFolders);

oldSessionSubDirNames.forEach((sessionDir: string) => {
const sessionDirPath = path.resolve(parentLogsDir, sessionDir);
// we are not waiting for the async `remove` to finish before returning
// in order to minimize impact on Theia startup time.
remove(sessionDirPath);
});
}

}
30 changes: 27 additions & 3 deletions packages/plugin-ext/src/main/node/plugin-cli-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ import { LocalDirectoryPluginDeployerResolver } from './resolvers/plugin-local-d
export class PluginCliContribution implements CliContribution {

static PLUGINS = 'plugins';
static PLUGIN_MAX_SESSION_LOGS_FOLDERS = 'plugin-max-session-logs-folders';
/**
* This is the default value used in VSCode, see:
* - https://github.com/Microsoft/vscode/blob/613447d6b3f458ef7fee227e3876303bf5184580/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts#L32
*/
static DEFAULT_PLUGIN_MAX_SESSION_LOGS_FOLDERS = 10;

protected _localDir: string | undefined;
protected _maxSessionLogsFolders: number;

configure(conf: Argv): void {
conf.option(PluginCliContribution.PLUGINS, {
Expand All @@ -33,17 +40,34 @@ export class PluginCliContribution implements CliContribution {
type: 'string',
nargs: 1
});

const maxLogSessionExample = `Example: --${PluginCliContribution.PLUGIN_MAX_SESSION_LOGS_FOLDERS}=5`;
conf.option(PluginCliContribution.PLUGIN_MAX_SESSION_LOGS_FOLDERS, {
description: `The maximum number of plugin logs sessions folders to retain. ${maxLogSessionExample}`,
type: 'number',
default: PluginCliContribution.DEFAULT_PLUGIN_MAX_SESSION_LOGS_FOLDERS,
nargs: 1
});
}

setArguments(args: Arguments): void {
const arg = args[PluginCliContribution.PLUGINS];
if (arg && String(arg).startsWith(`${LocalDirectoryPluginDeployerResolver.LOCAL_DIR}:`)) {
this._localDir = arg;
const pluginsArg = args[PluginCliContribution.PLUGINS];
if (pluginsArg && String(pluginsArg).startsWith(`${LocalDirectoryPluginDeployerResolver.LOCAL_DIR}:`)) {
this._localDir = pluginsArg;
}

const maxSessionLogsFoldersArg = args[PluginCliContribution.PLUGIN_MAX_SESSION_LOGS_FOLDERS];
if (maxSessionLogsFoldersArg && Number.isInteger(maxSessionLogsFoldersArg) && maxSessionLogsFoldersArg > 0) {
this._maxSessionLogsFolders = maxSessionLogsFoldersArg;
}
}

localDir(): string | undefined {
return this._localDir;
}

maxSessionLogsFolders(): number {
return this._maxSessionLogsFolders;
}

}

0 comments on commit e34e39b

Please sign in to comment.