Improves observable debugging helper

This commit is contained in:
Henning Dieterichs
2025-11-07 10:55:34 +01:00
committed by Henning Dieterichs
parent 331be293ce
commit f2499f2a67
6 changed files with 79 additions and 26 deletions
@@ -95,11 +95,6 @@ export interface IObservableWithChange<T, TChange = unknown> {
*/
readonly debugName: string;
/**
* ONLY FOR DEBUGGING!
*/
debugGetDependencyGraph(): string;
/**
* This property captures the type of the change object. Do not use it at runtime!
*/
@@ -40,10 +40,10 @@ import { addLogger, setLogObservableFn } from './logging/logging.js';
import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger.js';
import { DevToolsLogger } from './logging/debugger/devToolsLogger.js';
import { env } from '../process.js';
import { _setDebugGetDependencyGraph } from './observables/baseObservable.js';
import { debugGetDependencyGraph } from './logging/debugGetDependencyGraph.js';
import { _setDebugGetObservableGraph } from './observables/baseObservable.js';
import { debugGetObservableGraph } from './logging/debugGetDependencyGraph.js';
_setDebugGetDependencyGraph(debugGetDependencyGraph);
_setDebugGetObservableGraph(debugGetObservableGraph);
setLogObservableFn(logObservableToConsole);
// Remove "//" in the next line to enable logging
@@ -10,7 +10,12 @@ import { ObservableValue } from '../observables/observableValue.js';
import { AutorunObserver } from '../reactions/autorunImpl.js';
import { formatValue } from './consoleObservableLogger.js';
export function debugGetDependencyGraph(obs: IObservable<any> | IObserver, options?: { debugNamePostProcessor?: (name: string) => string }): string {
interface IOptions {
type: 'dependencies' | 'observers';
debugNamePostProcessor?: (name: string) => string;
}
export function debugGetObservableGraph(obs: IObservable<any> | IObserver, options: IOptions): string {
const debugNamePostProcessor = options?.debugNamePostProcessor ?? ((str: string) => str);
const info = Info.from(obs, debugNamePostProcessor);
if (!info) {
@@ -18,10 +23,15 @@ export function debugGetDependencyGraph(obs: IObservable<any> | IObserver, optio
}
const alreadyListed = new Set<IObservable<any> | IObserver>();
return formatObservableInfo(info, 0, alreadyListed).trim();
if (options.type === 'observers') {
return formatObservableInfoWithObservers(info, 0, alreadyListed, options).trim();
} else {
return formatObservableInfoWithDependencies(info, 0, alreadyListed, options).trim();
}
}
function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Set<IObservable<any> | IObserver>): string {
function formatObservableInfoWithDependencies(info: Info, indentLevel: number, alreadyListed: Set<IObservable<any> | IObserver>, options: IOptions): string {
const indent = '\t\t'.repeat(indentLevel);
const lines: string[] = [];
@@ -40,7 +50,35 @@ function formatObservableInfo(info: Info, indentLevel: number, alreadyListed: Se
if (info.dependencies.length > 0) {
lines.push(`${indent} dependencies:`);
for (const dep of info.dependencies) {
lines.push(formatObservableInfo(dep, indentLevel + 1, alreadyListed));
const info = Info.from(dep, options.debugNamePostProcessor ?? (name => name)) ?? Info.unknown(dep);
lines.push(formatObservableInfoWithDependencies(info, indentLevel + 1, alreadyListed, options));
}
}
return lines.join('\n');
}
function formatObservableInfoWithObservers(info: Info, indentLevel: number, alreadyListed: Set<IObservable<any> | IObserver>, options: IOptions): string {
const indent = '\t\t'.repeat(indentLevel);
const lines: string[] = [];
const isAlreadyListed = alreadyListed.has(info.sourceObj);
if (isAlreadyListed) {
lines.push(`${indent}* ${info.type} ${info.name} (already listed)`);
return lines.join('\n');
}
alreadyListed.add(info.sourceObj);
lines.push(`${indent}* ${info.type} ${info.name}:`);
lines.push(`${indent} value: ${formatValue(info.value, 50)}`);
lines.push(`${indent} state: ${info.state}`);
if (info.observers.length > 0) {
lines.push(`${indent} observers:`);
for (const observer of info.observers) {
const info = Info.from(observer, options.debugNamePostProcessor ?? (name => name)) ?? Info.unknown(observer);
lines.push(formatObservableInfoWithObservers(info, indentLevel + 1, alreadyListed, options));
}
}
@@ -57,7 +95,8 @@ class Info {
'autorun',
undefined,
state.stateStr,
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
Array.from(state.dependencies),
[]
);
} else if (obs instanceof Derived) {
const state = obs.debugGetState();
@@ -67,7 +106,8 @@ class Info {
'derived',
state.value,
state.stateStr,
Array.from(state.dependencies).map(dep => Info.from(dep, debugNamePostProcessor) || Info.unknown(dep))
Array.from(state.dependencies),
Array.from(obs.debugGetObservers())
);
} else if (obs instanceof ObservableValue) {
const state = obs.debugGetState();
@@ -77,7 +117,8 @@ class Info {
'observableValue',
state.value,
'upToDate',
[]
[],
Array.from(obs.debugGetObservers())
);
} else if (obs instanceof FromEventObservable) {
const state = obs.debugGetState();
@@ -87,7 +128,8 @@ class Info {
'fromEvent',
state.value,
state.hasValue ? 'upToDate' : 'initial',
[]
[],
Array.from(obs.debugGetObservers())
);
}
return undefined;
@@ -100,6 +142,7 @@ class Info {
'unknown',
undefined,
'unknown',
[],
[]
);
}
@@ -110,6 +153,7 @@ class Info {
public readonly type: string,
public readonly value: any,
public readonly state: string,
public readonly dependencies: Info[]
public readonly dependencies: (IObservable<any> | IObserver)[],
public readonly observers: (IObservable<any> | IObserver)[],
) { }
}
@@ -7,7 +7,7 @@ import { IObservableWithChange, IObserver, IReader, IObservable } from '../base.
import { DisposableStore } from '../commonFacade/deps.js';
import { DebugLocation } from '../debugLocation.js';
import { DebugOwner, getFunctionName } from '../debugName.js';
import { debugGetDependencyGraph } from '../logging/debugGetDependencyGraph.js';
import { debugGetObservableGraph } from '../logging/debugGetDependencyGraph.js';
import { getLogger, logObservable } from '../logging/logging.js';
import type { keepObserved, recomputeInitiallyAndOnChange } from '../utils/utils.js';
import { derivedOpts } from './derived.js';
@@ -31,9 +31,9 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) {
_keepObserved = keepObserved;
}
let _debugGetDependencyGraph: typeof debugGetDependencyGraph;
export function _setDebugGetDependencyGraph(debugGetDependencyGraph: typeof _debugGetDependencyGraph) {
_debugGetDependencyGraph = debugGetDependencyGraph;
let _debugGetObservableGraph: typeof debugGetObservableGraph;
export function _setDebugGetObservableGraph(debugGetObservableGraph: typeof _debugGetObservableGraph) {
_debugGetObservableGraph = debugGetObservableGraph;
}
export abstract class ConvenientObservable<T, TChange> implements IObservableWithChange<T, TChange> {
@@ -128,8 +128,21 @@ export abstract class ConvenientObservable<T, TChange> implements IObservableWit
return this.get();
}
debugGetDependencyGraph(): string {
return _debugGetDependencyGraph(this);
get debug(): DebugHelper {
return new DebugHelper(this);
}
}
class DebugHelper {
constructor(public readonly observable: IObservableWithChange<any, any>) {
}
getDependencyGraph(): string {
return _debugGetObservableGraph(this.observable, { type: 'dependencies' });
}
getObserverGraph(): string {
return _debugGetObservableGraph(this.observable, { type: 'observers' });
}
}
@@ -225,7 +225,8 @@ export function mapObservableArrayCached<TIn, TOut, TKey = TIn>(owner: DebugOwne
m = new ArrayMap(map);
}
}, (reader) => {
m.setItems(items.read(reader));
const i = items.read(reader);
m.setItems(i);
return m.getItems();
});
return self;
@@ -7,7 +7,7 @@ import assert from 'assert';
import { observableValue, derived, autorun } from '../../../common/observable.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../utils.js';
// eslint-disable-next-line local/code-no-deep-import-of-internal
import { debugGetDependencyGraph } from '../../../common/observableInternal/logging/debugGetDependencyGraph.js';
import { debugGetObservableGraph } from '../../../common/observableInternal/logging/debugGetDependencyGraph.js';
suite('debug', () => {
const ds = ensureNoDisposablesAreLeakedInTestSuite();
@@ -50,7 +50,7 @@ suite('debug', () => {
let idx = 0;
assert.deepStrictEqual(
debugGetDependencyGraph(myComputed3, { debugNamePostProcessor: name => `name${++idx}` }),
debugGetObservableGraph(myComputed3, { type: 'dependencies', debugNamePostProcessor: name => `name${++idx}` }),
'* derived name1:\n value: 0\n state: upToDate\n dependencies:\n\t\t* derived name2:\n\t\t value: 0\n\t\t state: upToDate\n\t\t dependencies:\n\t\t\t\t* derived name3:\n\t\t\t\t value: 0\n\t\t\t\t state: upToDate\n\t\t\t\t dependencies:\n\t\t\t\t\t\t* observableValue name4:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t\t\t* observableValue name5:\n\t\t\t\t\t\t value: 0\n\t\t\t\t\t\t state: upToDate\n\t\t\t\t* observableValue name6 (already listed)\n\t\t\t\t* observableValue name7 (already listed)\n\t\t* observableValue name8 (already listed)\n\t\t* observableValue name9 (already listed)',
);
});