Skip to content

Commit

Permalink
support default compound logs (microsoft#238089)
Browse files Browse the repository at this point in the history
  • Loading branch information
sandy081 authored Jan 16, 2025
1 parent 87a0d07 commit ab8fe3f
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 193 deletions.
22 changes: 21 additions & 1 deletion src/vs/platform/log/common/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ function format(args: any, verbose: boolean = false): string {
return result;
}

export type LoggerGroup = {
readonly id: string;
readonly name: string;
};

export interface ILogService extends ILogger {
readonly _serviceBrand: undefined;
}
Expand Down Expand Up @@ -136,6 +141,11 @@ export interface ILoggerOptions {
* Id of the extension that created this logger.
*/
extensionId?: string;

/**
* Group of the logger.
*/
group?: LoggerGroup;
}

export interface ILoggerResource {
Expand All @@ -146,6 +156,7 @@ export interface ILoggerResource {
readonly hidden?: boolean;
readonly when?: string;
readonly extensionId?: string;
readonly group?: LoggerGroup;
}

export type DidChangeLoggersEvent = {
Expand Down Expand Up @@ -616,7 +627,16 @@ export abstract class AbstractLoggerService extends Disposable implements ILogge
}
const loggerEntry: LoggerEntry = {
logger,
info: { resource, id, logLevel, name: options?.name, hidden: options?.hidden, extensionId: options?.extensionId, when: options?.when }
info: {
resource,
id,
logLevel,
name: options?.name,
hidden: options?.hidden,
group: options?.group,
extensionId: options?.extensionId,
when: options?.when
}
};
this.registerLogger(loggerEntry.info);
// TODO: @sandy081 Remove this once registerLogger can take ILogger
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadOutputService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut
const id = `extension-output-${extensionId}-#${idCounter}-${label}`;
const resource = URI.revive(file);

Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id, label, files: [resource], log: false, languageId, extensionId });
Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels).registerChannel({ id, label, source: { resource }, log: false, languageId, extensionId });
this._register(toDisposable(() => this.$dispose(id)));
return id;
}
Expand Down
66 changes: 37 additions & 29 deletions src/vs/workbench/contrib/logs/common/logs.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ import { Categories } from '../../../../platform/action/common/actionCommonCateg
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { SetLogLevelAction } from './logsActions.js';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js';
import { IFileService, whenProviderRegistered } from '../../../../platform/files/common/files.js';
import { IOutputChannelRegistry, IOutputService, Extensions } from '../../../services/output/common/output.js';
import { Disposable, DisposableMap, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { IOutputChannelRegistry, IOutputService, Extensions, isMultiSourceOutputChannelDescriptor, isSingleSourceOutputChannelDescriptor } from '../../../services/output/common/output.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { CONTEXT_LOG_LEVEL, ILoggerResource, ILoggerService, LogLevel, LogLevelToString, isLogLevel } from '../../../../platform/log/common/log.js';
import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { Event } from '../../../../base/common/event.js';
import { windowLogId, showWindowLogActionId } from '../../../services/log/common/logConstants.js';
import { createCancelablePromise } from '../../../../base/common/async.js';
import { IDefaultLogLevelsService } from './defaultLogLevels.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { CounterSet } from '../../../../base/common/map.js';
Expand Down Expand Up @@ -55,12 +53,10 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {

private readonly contextKeys = new CounterSet<string>();
private readonly outputChannelRegistry = Registry.as<IOutputChannelRegistry>(Extensions.OutputChannels);
private readonly loggerDisposables = this._register(new DisposableMap());

constructor(
@ILoggerService private readonly loggerService: ILoggerService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IFileService private readonly fileService: IFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
) {
super();
Expand Down Expand Up @@ -138,36 +134,48 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
}

private registerLogChannel(logger: ILoggerResource): void {
if (logger.group) {
this.registerCompoundLogChannel(logger.group.id, logger.group.name, logger);
return;
}

const channel = this.outputChannelRegistry.getChannel(logger.id);
if (channel?.files?.length === 1 && this.uriIdentityService.extUri.isEqual(channel.files[0], logger.resource)) {
if (channel && isSingleSourceOutputChannelDescriptor(channel) && this.uriIdentityService.extUri.isEqual(channel.source.resource, logger.resource)) {
return;
}
const disposables = new DisposableStore();
const promise = createCancelablePromise(async token => {
await whenProviderRegistered(logger.resource, this.fileService);
if (token.isCancellationRequested) {
return;
}
const existingChannel = this.outputChannelRegistry.getChannel(logger.id);
const remoteLogger = existingChannel?.files?.[0].scheme === Schemas.vscodeRemote ? this.loggerService.getRegisteredLogger(existingChannel.files[0]) : undefined;
if (remoteLogger) {
this.deregisterLogChannel(remoteLogger);
}
const hasToAppendRemote = existingChannel && logger.resource.scheme === Schemas.vscodeRemote;
const id = hasToAppendRemote ? `${logger.id}.remote` : logger.id;
const label = hasToAppendRemote ? nls.localize('remote name', "{0} (Remote)", logger.name ?? logger.id) : logger.name ?? logger.id;
this.outputChannelRegistry.registerChannel({ id, label, files: [logger.resource], log: true, extensionId: logger.extensionId });
disposables.add(toDisposable(() => this.outputChannelRegistry.removeChannel(id)));
if (remoteLogger) {
this.registerLogChannel(remoteLogger);

const existingChannel = this.outputChannelRegistry.getChannel(logger.id);
const remoteLogger = existingChannel && isSingleSourceOutputChannelDescriptor(existingChannel) && existingChannel.source.resource.scheme === Schemas.vscodeRemote ? this.loggerService.getRegisteredLogger(existingChannel.source.resource) : undefined;
if (remoteLogger) {
this.deregisterLogChannel(remoteLogger);
}
const hasToAppendRemote = existingChannel && logger.resource.scheme === Schemas.vscodeRemote;
const id = hasToAppendRemote ? `${logger.id}.remote` : logger.id;
const label = hasToAppendRemote ? nls.localize('remote name', "{0} (Remote)", logger.name ?? logger.id) : logger.name ?? logger.id;
this.outputChannelRegistry.registerChannel({ id, label, source: { resource: logger.resource }, log: true, extensionId: logger.extensionId });
}

private registerCompoundLogChannel(id: string, name: string, logger: ILoggerResource): void {
const channel = this.outputChannelRegistry.getChannel(id);
const source = { resource: logger.resource, name: logger.name ?? logger.id };
if (channel) {
if (isMultiSourceOutputChannelDescriptor(channel) && !channel.source.some(({ resource }) => this.uriIdentityService.extUri.isEqual(resource, logger.resource))) {
this.outputChannelRegistry.updateChannelSources(id, [...channel.source, source]);
}
});
disposables.add(toDisposable(() => promise.cancel()));
this.loggerDisposables.set(logger.resource.toString(), disposables);
} else {
this.outputChannelRegistry.registerChannel({ id, label: name, log: true, source: [source] });
}
}

private deregisterLogChannel(logger: ILoggerResource): void {
this.loggerDisposables.deleteAndDispose(logger.resource.toString());
if (logger.group) {
const channel = this.outputChannelRegistry.getChannel(logger.group.id);
if (channel && isMultiSourceOutputChannelDescriptor(channel)) {
this.outputChannelRegistry.updateChannelSources(logger.group.id, channel.source.filter(({ resource }) => !this.uriIdentityService.extUri.isEqual(resource, logger.resource)));
}
} else {
this.outputChannelRegistry.removeChannel(logger.id);
}
}

private registerShowWindowLogAction(): void {
Expand Down
10 changes: 5 additions & 5 deletions src/vs/workbench/contrib/logs/common/logsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IFileService } from '../../../../platform/files/common/files.js';
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
import { dirname, basename, isEqual } from '../../../../base/common/resources.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { IOutputChannelDescriptor, IOutputService } from '../../../services/output/common/output.js';
import { IOutputChannelDescriptor, IOutputService, isSingleSourceOutputChannelDescriptor } from '../../../services/output/common/output.js';
import { extensionTelemetryLogChannelId, telemetryLogId } from '../../../../platform/telemetry/common/telemetryUtils.js';
import { IDefaultLogLevelsService } from './defaultLogLevels.js';
import { Codicon } from '../../../../base/common/codicons.js';
Expand Down Expand Up @@ -52,11 +52,11 @@ export class SetLogLevelAction extends Action {
const extensionLogs: LogChannelQuickPickItem[] = [], logs: LogChannelQuickPickItem[] = [];
const logLevel = this.loggerService.getLogLevel();
for (const channel of this.outputService.getChannelDescriptors()) {
if (!SetLogLevelAction.isLevelSettable(channel) || channel.files?.length !== 1) {
if (!isSingleSourceOutputChannelDescriptor(channel) || !SetLogLevelAction.isLevelSettable(channel)) {
continue;
}
const channelLogLevel = this.loggerService.getLogLevel(channel.files[0]) ?? logLevel;
const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.files[0], label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId };
const channelLogLevel = this.loggerService.getLogLevel(channel.source.resource) ?? logLevel;
const item: LogChannelQuickPickItem = { id: channel.id, resource: channel.source.resource, label: channel.label, description: channelLogLevel !== logLevel ? this.getLabel(channelLogLevel) : undefined, extensionId: channel.extensionId };
if (channel.extensionId) {
extensionLogs.push(item);
} else {
Expand Down Expand Up @@ -97,7 +97,7 @@ export class SetLogLevelAction extends Action {
}

static isLevelSettable(channel: IOutputChannelDescriptor): boolean {
return channel.log && channel.files?.length === 1 && channel.id !== telemetryLogId && channel.id !== extensionTelemetryLogChannelId;
return channel.log && isSingleSourceOutputChannelDescriptor(channel) && channel.id !== telemetryLogId && channel.id !== extensionTelemetryLogChannelId;
}

private async setLogLevelForChannel(logChannel: LogChannelQuickPickItem): Promise<void> {
Expand Down
Loading

0 comments on commit ab8fe3f

Please sign in to comment.