Path: blob/main/src/vs/workbench/contrib/logs/common/logsActions.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import { Action } from '../../../../base/common/actions.js';7import { ILoggerService, LogLevel, LogLevelToLocalizedString, isLogLevel } from '../../../../platform/log/common/log.js';8import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';9import { URI } from '../../../../base/common/uri.js';10import { IFileService } from '../../../../platform/files/common/files.js';11import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';12import { dirname, basename, isEqual } from '../../../../base/common/resources.js';13import { IEditorService } from '../../../services/editor/common/editorService.js';14import { IOutputChannelDescriptor, IOutputService, isMultiSourceOutputChannelDescriptor, isSingleSourceOutputChannelDescriptor } from '../../../services/output/common/output.js';15import { IDefaultLogLevelsService } from './defaultLogLevels.js';16import { Codicon } from '../../../../base/common/codicons.js';17import { ThemeIcon } from '../../../../base/common/themables.js';18import { DisposableStore } from '../../../../base/common/lifecycle.js';1920type LogLevelQuickPickItem = IQuickPickItem & { level: LogLevel };21type LogChannelQuickPickItem = IQuickPickItem & { id: string; channel: IOutputChannelDescriptor };2223export class SetLogLevelAction extends Action {2425static readonly ID = 'workbench.action.setLogLevel';26static readonly TITLE = nls.localize2('setLogLevel', "Set Log Level...");2728constructor(id: string, label: string,29@IQuickInputService private readonly quickInputService: IQuickInputService,30@ILoggerService private readonly loggerService: ILoggerService,31@IOutputService private readonly outputService: IOutputService,32@IDefaultLogLevelsService private readonly defaultLogLevelsService: IDefaultLogLevelsService,33) {34super(id, label);35}3637override async run(): Promise<void> {38const logLevelOrChannel = await this.selectLogLevelOrChannel();39if (logLevelOrChannel !== null) {40if (isLogLevel(logLevelOrChannel)) {41this.loggerService.setLogLevel(logLevelOrChannel);42} else {43await this.setLogLevelForChannel(logLevelOrChannel);44}45}46}4748private async selectLogLevelOrChannel(): Promise<LogChannelQuickPickItem | LogLevel | null> {49const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels();50const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = [];51const logLevel = this.loggerService.getLogLevel();52for (const channel of this.outputService.getChannelDescriptors()) {53if (!this.outputService.canSetLogLevel(channel)) {54continue;55}56const sources = isSingleSourceOutputChannelDescriptor(channel) ? [channel.source] : isMultiSourceOutputChannelDescriptor(channel) ? channel.source : [];57if (!sources.length) {58continue;59}60const channelLogLevel = sources.reduce((prev, curr) => Math.min(prev, this.loggerService.getLogLevel(curr.resource) ?? logLevel), logLevel);61const item: LogChannelQuickPickItem = {62id: channel.id,63label: channel.label,64description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined,65channel66};67if (channel.extensionId) {68extensionLogs.push(item);69} else {70logs.push(item);71}72}73const entries: (LogLevelQuickPickItem | LogChannelQuickPickItem | IQuickPickSeparator)[] = [];74entries.push({ type: 'separator', label: nls.localize('all', "All") });75entries.push(...this.getLogLevelEntries(defaultLogLevels.default, this.loggerService.getLogLevel(), true));76if (extensionLogs.length) {77entries.push({ type: 'separator', label: nls.localize('extensionLogs', "Extension Logs") });78entries.push(...extensionLogs.sort((a, b) => a.label.localeCompare(b.label)));79}80entries.push({ type: 'separator', label: nls.localize('loggers', "Logs") });81entries.push(...logs.sort((a, b) => a.label.localeCompare(b.label)));8283return new Promise((resolve, reject) => {84const disposables = new DisposableStore();85const quickPick = disposables.add(this.quickInputService.createQuickPick({ useSeparators: true }));86quickPick.placeholder = nls.localize('selectlog', "Set Log Level");87quickPick.items = entries;88let selectedItem: IQuickPickItem | undefined;89disposables.add(quickPick.onDidTriggerItemButton(e => {90quickPick.hide();91this.defaultLogLevelsService.setDefaultLogLevel((<LogLevelQuickPickItem>e.item).level);92}));93disposables.add(quickPick.onDidAccept(e => {94selectedItem = quickPick.selectedItems[0];95quickPick.hide();96}));97disposables.add(quickPick.onDidHide(() => {98const result = selectedItem ? (<LogLevelQuickPickItem>selectedItem).level ?? <LogChannelQuickPickItem>selectedItem : null;99disposables.dispose();100resolve(result);101}));102quickPick.show();103});104}105106private async setLogLevelForChannel(logChannel: LogChannelQuickPickItem): Promise<void> {107const defaultLogLevels = await this.defaultLogLevelsService.getDefaultLogLevels();108const defaultLogLevel = defaultLogLevels.extensions.find(e => e[0] === logChannel.channel.extensionId?.toLowerCase())?.[1] ?? defaultLogLevels.default;109const entries = this.getLogLevelEntries(defaultLogLevel, this.outputService.getLogLevel(logChannel.channel) ?? defaultLogLevel, !!logChannel.channel.extensionId);110111return new Promise((resolve, reject) => {112const disposables = new DisposableStore();113const quickPick = disposables.add(this.quickInputService.createQuickPick());114quickPick.placeholder = logChannel ? nls.localize('selectLogLevelFor', " {0}: Select log level", logChannel?.label) : nls.localize('selectLogLevel', "Select log level");115quickPick.items = entries;116quickPick.activeItems = entries.filter((entry) => entry.level === this.loggerService.getLogLevel());117let selectedItem: LogLevelQuickPickItem | undefined;118disposables.add(quickPick.onDidTriggerItemButton(e => {119quickPick.hide();120this.defaultLogLevelsService.setDefaultLogLevel((<LogLevelQuickPickItem>e.item).level, logChannel.channel.extensionId);121}));122disposables.add(quickPick.onDidAccept(e => {123selectedItem = quickPick.selectedItems[0] as LogLevelQuickPickItem;124quickPick.hide();125}));126disposables.add(quickPick.onDidHide(() => {127if (selectedItem) {128this.outputService.setLogLevel(logChannel.channel, selectedItem.level);129}130disposables.dispose();131resolve();132}));133quickPick.show();134});135}136137private getLogLevelEntries(defaultLogLevel: LogLevel, currentLogLevel: LogLevel, canSetDefaultLogLevel: boolean): LogLevelQuickPickItem[] {138const button: IQuickInputButton | undefined = canSetDefaultLogLevel ? { iconClass: ThemeIcon.asClassName(Codicon.checkAll), tooltip: nls.localize('resetLogLevel', "Set as Default Log Level") } : undefined;139return [140{ label: this.getLabel(LogLevel.Trace, currentLogLevel), level: LogLevel.Trace, description: this.getDescription(LogLevel.Trace, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Trace ? [button] : undefined },141{ label: this.getLabel(LogLevel.Debug, currentLogLevel), level: LogLevel.Debug, description: this.getDescription(LogLevel.Debug, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Debug ? [button] : undefined },142{ label: this.getLabel(LogLevel.Info, currentLogLevel), level: LogLevel.Info, description: this.getDescription(LogLevel.Info, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Info ? [button] : undefined },143{ label: this.getLabel(LogLevel.Warning, currentLogLevel), level: LogLevel.Warning, description: this.getDescription(LogLevel.Warning, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Warning ? [button] : undefined },144{ label: this.getLabel(LogLevel.Error, currentLogLevel), level: LogLevel.Error, description: this.getDescription(LogLevel.Error, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Error ? [button] : undefined },145{ label: this.getLabel(LogLevel.Off, currentLogLevel), level: LogLevel.Off, description: this.getDescription(LogLevel.Off, defaultLogLevel), buttons: button && defaultLogLevel !== LogLevel.Off ? [button] : undefined },146];147}148149private getLabel(level: LogLevel, current?: LogLevel): string {150const label = LogLevelToLocalizedString(level).value;151return level === current ? `$(check) ${label}` : label;152}153154private getDescription(level: LogLevel, defaultLogLevel: LogLevel): string | undefined {155return defaultLogLevel === level ? nls.localize('default', "Default") : undefined;156}157158}159160export class OpenWindowSessionLogFileAction extends Action {161162static readonly ID = 'workbench.action.openSessionLogFile';163static readonly TITLE = nls.localize2('openSessionLogFile', "Open Window Log File (Session)...");164165constructor(id: string, label: string,166@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,167@IFileService private readonly fileService: IFileService,168@IQuickInputService private readonly quickInputService: IQuickInputService,169@IEditorService private readonly editorService: IEditorService,170) {171super(id, label);172}173174override async run(): Promise<void> {175const sessionResult = await this.quickInputService.pick(176this.getSessions().then(sessions => sessions.map((s, index): IQuickPickItem => ({177id: s.toString(),178label: basename(s),179description: index === 0 ? nls.localize('current', "Current") : undefined180}))),181{182canPickMany: false,183placeHolder: nls.localize('sessions placeholder', "Select Session")184});185if (sessionResult) {186const logFileResult = await this.quickInputService.pick(187this.getLogFiles(URI.parse(sessionResult.id!)).then(logFiles => logFiles.map((s): IQuickPickItem => ({188id: s.toString(),189label: basename(s)190}))),191{192canPickMany: false,193placeHolder: nls.localize('log placeholder', "Select Log file")194});195if (logFileResult) {196return this.editorService.openEditor({ resource: URI.parse(logFileResult.id!), options: { pinned: true } }).then(() => undefined);197}198}199}200201private async getSessions(): Promise<URI[]> {202const logsPath = this.environmentService.logsHome.with({ scheme: this.environmentService.logFile.scheme });203const result: URI[] = [logsPath];204const stat = await this.fileService.resolve(dirname(logsPath));205if (stat.children) {206result.push(...stat.children207.filter(stat => !isEqual(stat.resource, logsPath) && stat.isDirectory && /^\d{8}T\d{6}$/.test(stat.name))208.sort()209.reverse()210.map(d => d.resource));211}212return result;213}214215private async getLogFiles(session: URI): Promise<URI[]> {216const stat = await this.fileService.resolve(session);217if (stat.children) {218return stat.children.filter(stat => !stat.isDirectory).map(stat => stat.resource);219}220return [];221}222}223224225226