Skip to content

Commit 94781a1

Browse files
committed
Fixes circular logging issue
Extracts replacers to be split by env
1 parent b2b2a37 commit 94781a1

File tree

19 files changed

+137
-96
lines changed

19 files changed

+137
-96
lines changed

src/env/browser/json.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export function loggingJsonReplacer(key: string, value: unknown): unknown {
2+
if (key === '' || value == null || typeof value !== 'object') return value;
3+
4+
if (value instanceof Error) return String(value);
5+
6+
return value;
7+
}
8+
9+
export function serializeJsonReplacer(this: any, key: string, value: unknown): unknown {
10+
if (value instanceof Date) return value.getTime();
11+
if (value instanceof Map || value instanceof Set) return [...value.entries()];
12+
if (value instanceof Function || value instanceof Error) return undefined;
13+
if (value instanceof RegExp) return value.toString();
14+
15+
const original = this[key];
16+
return original instanceof Date ? original.getTime() : value;
17+
}

src/env/node/json.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Uri } from 'vscode';
2+
import { isContainer } from '../../container';
3+
import { isBranch } from '../../git/models/branch';
4+
import { isCommit } from '../../git/models/commit';
5+
import { isRepository } from '../../git/models/repository';
6+
import { isTag } from '../../git/models/tag';
7+
import { isViewNode } from '../../views/nodes/abstract/viewNode';
8+
9+
export function loggingJsonReplacer(key: string, value: unknown): unknown {
10+
if (key === '' || value == null || typeof value !== 'object') return value;
11+
if (key.startsWith('_')) return undefined;
12+
13+
if (value instanceof Error) return String(value);
14+
if (value instanceof Uri) {
15+
if ('sha' in value && typeof value.sha === 'string' && value.sha) {
16+
return `${value.sha}:${value.toString()}`;
17+
}
18+
return value.toString();
19+
}
20+
if (isRepository(value) || isBranch(value) || isCommit(value) || isTag(value) || isViewNode(value)) {
21+
return value.toString();
22+
}
23+
if (isContainer(value)) return '<container>';
24+
25+
return value;
26+
}
27+
28+
export function serializeJsonReplacer(this: any, key: string, value: unknown): unknown {
29+
if (value instanceof Date) return value.getTime();
30+
if (value instanceof Map || value instanceof Set) return [...value.entries()];
31+
if (value instanceof Function || value instanceof Error) return undefined;
32+
if (value instanceof RegExp) return value.toString();
33+
if (value instanceof Uri) return value.toString();
34+
if (isContainer(value)) return undefined;
35+
36+
const original = this[key];
37+
return original instanceof Date ? original.getTime() : original instanceof Uri ? original.toString() : value;
38+
}

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ExtensionContext } from 'vscode';
22
import { version as codeVersion, env, ExtensionMode, Uri, window, workspace } from 'vscode';
33
import { hrtime } from '@env/hrtime';
4+
import { loggingJsonReplacer } from '@env/json';
45
import { isWeb } from '@env/platform';
56
import { Api } from './api/api';
67
import type { CreatePullRequestActionContext, GitLensApi, OpenPullRequestActionContext } from './api/gitlens';
@@ -97,6 +98,7 @@ export async function activate(context: ExtensionContext): Promise<GitLensApi |
9798

9899
return undefined;
99100
},
101+
sanitizer: loggingJsonReplacer,
100102
},
101103
logLevel,
102104
context.extensionMode === ExtensionMode.Development,

src/system/-webview/serialize.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/system/decorators/-webview/resolver.ts

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,15 @@
11
import { Uri } from 'vscode';
2+
import { loggingJsonReplacer } from '@env/json';
23
import { isContainer } from '../../../container';
34
import { isBranch } from '../../../git/models/branch';
45
import { isCommit } from '../../../git/models/commit';
6+
import { isRepository } from '../../../git/models/repository';
57
import { isTag } from '../../../git/models/tag';
68
import { isViewNode } from '../../../views/nodes/abstract/viewNode';
79

8-
function replacer(key: string, value: any): any {
9-
if (key === '' || value == null || typeof value !== 'object') return value;
10-
11-
if (value instanceof Error) return String(value);
12-
if (value instanceof Uri) {
13-
if ('sha' in value && typeof value.sha === 'string' && value.sha) {
14-
return `${value.sha}:${value.toString()}`;
15-
}
16-
return value.toString();
17-
}
18-
if (isBranch(value) || isCommit(value) || isTag(value) || isViewNode(value)) {
19-
return value.toString();
20-
}
21-
if (isContainer(value)) return '<container>';
22-
23-
return value;
24-
}
25-
26-
export function defaultResolver(...args: any[]): string {
10+
export function defaultResolver(...args: unknown[]): string {
2711
if (args.length === 0) return '';
28-
if (args.length > 1) return JSON.stringify(args, replacer);
12+
if (args.length > 1) return JSON.stringify(args, loggingJsonReplacer);
2913

3014
const [arg] = args;
3115
if (arg == null) return '';
@@ -49,12 +33,12 @@ export function defaultResolver(...args: any[]): string {
4933
}
5034
return arg.toString();
5135
}
52-
if (isBranch(arg) || isCommit(arg) || isTag(arg) || isViewNode(arg)) {
36+
if (isRepository(arg) || isBranch(arg) || isCommit(arg) || isTag(arg) || isViewNode(arg)) {
5337
return arg.toString();
5438
}
5539
if (isContainer(arg)) return '<container>';
5640

57-
return JSON.stringify(arg, replacer);
41+
return JSON.stringify(arg, loggingJsonReplacer);
5842
}
5943
}
6044

src/system/logger.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export interface LogChannelProvider {
1515
readonly name: string;
1616
createChannel(name: string): LogChannel;
1717
toLoggable?(o: unknown): string | undefined;
18-
sanitize?: (key: string, value: any) => any;
18+
19+
sanitizeKeys?: Set<string>;
20+
sanitizer?: (key: string, value: unknown) => unknown;
1921
}
2022

2123
export interface LogChannel {
@@ -25,17 +27,21 @@ export interface LogChannel {
2527
show?(preserveFocus?: boolean): void;
2628
}
2729

28-
const sanitizedKeys = new Set<string>(['accessToken', 'password', 'token']);
29-
const defaultSanitize = function (key: string, value: any): any {
30-
return sanitizedKeys.has(key) ? `<${value}>` : value;
31-
};
30+
const defaultSanitizeKeys = ['accessToken', 'password', 'token'];
3231

3332
export const Logger = new (class Logger {
3433
private output: LogChannel | undefined;
35-
private provider: LogChannelProvider | undefined;
34+
private provider: RequireSome<LogChannelProvider, 'sanitizeKeys'> | undefined;
3635

3736
configure(provider: LogChannelProvider, logLevel: LogLevel, debugging: boolean = false) {
38-
this.provider = provider;
37+
if (provider.sanitizeKeys != null) {
38+
for (const key of defaultSanitizeKeys) {
39+
provider.sanitizeKeys.add(key);
40+
}
41+
} else {
42+
provider.sanitizeKeys = new Set(defaultSanitizeKeys);
43+
}
44+
this.provider = provider as RequireSome<LogChannelProvider, 'sanitizeKeys'>;
3945

4046
this._isDebugging = debugging;
4147
this.logLevel = logLevel;
@@ -197,21 +203,30 @@ export const Logger = new (class Logger {
197203
this.output?.show?.(preserveFocus);
198204
}
199205

200-
toLoggable(o: any, sanitize?: ((key: string, value: any) => any) | undefined): string {
206+
toLoggable(o: any, sanitizer?: ((key: string, value: unknown) => unknown) | undefined): string {
201207
if (typeof o !== 'object') return String(o);
202208

203-
sanitize ??= this.provider!.sanitize ?? defaultSanitize;
204-
205209
if (Array.isArray(o)) {
206-
return `[${o.map(i => this.toLoggable(i, sanitize)).join(', ')}]`;
210+
return `[${o.map(i => this.toLoggable(i, sanitizer)).join(', ')}]`;
207211
}
208212

209213
const loggable = this.provider!.toLoggable?.(o);
210214
if (loggable != null) return loggable;
211215

212216
try {
213-
return JSON.stringify(o, sanitize);
217+
return JSON.stringify(o, (key: string, value: unknown): unknown => {
218+
if (this.provider!.sanitizeKeys.has(key)) return `<${key}>`;
219+
220+
if (sanitizer != null) {
221+
value = sanitizer(key, value);
222+
}
223+
if (this.provider?.sanitizer != null) {
224+
value = this.provider.sanitizer(key, value);
225+
}
226+
return value;
227+
});
214228
} catch {
229+
debugger;
215230
return '<error>';
216231
}
217232
}

src/system/serialize.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Uri } from 'vscode';
2+
import { serializeJsonReplacer } from '@env/json';
3+
import type { Branded } from './brand';
4+
5+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
6+
export type Serialized<T> = T extends Function
7+
? never
8+
: T extends Date
9+
? number
10+
: T extends Uri
11+
? string
12+
: T extends Branded<infer U, any>
13+
? U
14+
: T extends any[]
15+
? Serialized<T[number]>[]
16+
: T extends object
17+
? {
18+
[K in keyof T]: T[K] extends Date ? number : Serialized<T[K]>;
19+
}
20+
: T;
21+
22+
export function serialize<T extends object>(obj: T): Serialized<T>;
23+
export function serialize<T extends object>(obj: T | undefined): Serialized<T> | undefined;
24+
export function serialize<T extends object>(obj: T | undefined): Serialized<T> | undefined {
25+
if (obj == null) return undefined;
26+
27+
try {
28+
return JSON.parse(JSON.stringify(obj, serializeJsonReplacer)) as Serialized<T>;
29+
} catch (ex) {
30+
debugger;
31+
throw ex;
32+
}
33+
}

src/webviews/apps/commitDetails/commitDetails.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*global*/
2-
import type { Serialized } from '../../../system/-webview/serialize';
2+
import type { Serialized } from '../../../system/serialize';
33
import type { State } from '../../commitDetails/protocol';
44
import { App } from '../shared/appBase';
55
import { DOM } from '../shared/dom';

src/webviews/apps/commitDetails/components/commit-details-app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js';
44
import { when } from 'lit/directives/when.js';
55
import type { ViewFilesLayout } from '../../../../config';
66
import type { Commands } from '../../../../constants.commands';
7-
import type { Serialized } from '../../../../system/-webview/serialize';
7+
import type { Serialized } from '../../../../system/serialize';
88
import { pluralize } from '../../../../system/string';
99
import type { DraftState, ExecuteCommitActionsParams, Mode, State } from '../../../commitDetails/protocol';
1010
import {

src/webviews/apps/commitDetails/components/gl-commit-details.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
import type { IssueIntegrationId, SupportedCloudIntegrationIds } from '../../../../constants.integrations';
1111
import type { IssueOrPullRequest } from '../../../../git/models/issueOrPullRequest';
1212
import type { PullRequestShape } from '../../../../git/models/pullRequest';
13-
import type { Serialized } from '../../../../system/-webview/serialize';
13+
import type { Serialized } from '../../../../system/serialize';
1414
import type { State } from '../../../commitDetails/protocol';
1515
import { messageHeadlineSplitterToken } from '../../../commitDetails/protocol';
1616
import type { TreeItemAction, TreeItemBase } from '../../shared/components/tree/base';

0 commit comments

Comments
 (0)