Get stack trace using an outside trace function

This commit is contained in:
Zalmoxisus 2018-12-10 18:58:46 +02:00
parent dabc7d9b8e
commit 8e869c8b6d
3 changed files with 83 additions and 15 deletions

View File

@ -50,7 +50,7 @@ export default function configureStore(initialState) {
- **pauseActionType** *string* - if specified, whenever `pauseRecording(false)` lifted action is dispatched and there are actions in the history log, will add this action type. If not specified, will commit when paused. - **pauseActionType** *string* - if specified, whenever `pauseRecording(false)` lifted action is dispatched and there are actions in the history log, will add this action type. If not specified, will commit when paused.
- **shouldStartLocked** *boolean* - if specified as `true`, it will not allow any non-monitor actions to be dispatched till `lockChanges(false)` is dispatched. Default is `false`. - **shouldStartLocked** *boolean* - if specified as `true`, it will not allow any non-monitor actions to be dispatched till `lockChanges(false)` is dispatched. Default is `false`.
- **shouldHotReload** *boolean* - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Default to `true`. - **shouldHotReload** *boolean* - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Default to `true`.
- **shouldIncludeCallstack** *boolean* - if set to `true`, will include callstack for every dispatched action. Default to `false`. - **trace** *boolean* or *function* - if set to `true`, will include stack trace for every dispatched action. You can use a function (with action object as argument) which should return `new Error().stack` string, getting the stack outside of reducers. Default to `false`.
### License ### License

View File

@ -23,7 +23,7 @@ export const ActionTypes = {
* Action creators to change the History state. * Action creators to change the History state.
*/ */
export const ActionCreators = { export const ActionCreators = {
performAction(action, shouldIncludeCallstack) { performAction(action, trace) {
if (!isPlainObject(action)) { if (!isPlainObject(action)) {
throw new Error( throw new Error(
'Actions must be plain objects. ' + 'Actions must be plain objects. ' +
@ -38,10 +38,13 @@ export const ActionCreators = {
); );
} }
return { let stack;
type: ActionTypes.PERFORM_ACTION, action, timestamp: Date.now(), if (trace) {
stack: shouldIncludeCallstack ? Error().stack : undefined if (typeof trace === 'function') stack = trace(action);
}; else stack = Error().stack;
}
return { type: ActionTypes.PERFORM_ACTION, action, timestamp: Date.now(), stack };
}, },
reset() { reset() {
@ -188,8 +191,8 @@ function recomputeStates(
/** /**
* Lifts an app's action into an action on the lifted store. * Lifts an app's action into an action on the lifted store.
*/ */
export function liftAction(action, shouldIncludeCallstack) { export function liftAction(action, trace) {
return ActionCreators.performAction(action, shouldIncludeCallstack); return ActionCreators.performAction(action, trace);
} }
/** /**
@ -502,7 +505,7 @@ export function liftReducerWith(reducer, initialCommittedState, monitorReducer,
minInvalidatedStateIndex = 0; minInvalidatedStateIndex = 0;
// iterate through actions // iterate through actions
liftedAction.nextLiftedState.forEach(action => { liftedAction.nextLiftedState.forEach(action => {
actionsById[nextActionId] = liftAction(action, options.shouldIncludeCallstack); actionsById[nextActionId] = liftAction(action, options.trace || options.shouldIncludeCallstack);
stagedActionIds.push(nextActionId); stagedActionIds.push(nextActionId);
nextActionId++; nextActionId++;
}); });
@ -595,7 +598,7 @@ export function unliftState(liftedState) {
*/ */
export function unliftStore(liftedStore, liftReducer, options) { export function unliftStore(liftedStore, liftReducer, options) {
let lastDefinedState; let lastDefinedState;
const { shouldIncludeCallstack } = options; const trace = options.trace || options.shouldIncludeCallstack;
function getState() { function getState() {
const state = unliftState(liftedStore.getState()); const state = unliftState(liftedStore.getState());
@ -611,7 +614,7 @@ export function unliftStore(liftedStore, liftReducer, options) {
liftedStore, liftedStore,
dispatch(action) { dispatch(action) {
liftedStore.dispatch(liftAction(action, shouldIncludeCallstack)); liftedStore.dispatch(liftAction(action, trace));
return action; return action;
}, },

View File

@ -686,6 +686,71 @@ describe('instrument', () => {
}); });
}); });
describe('trace option', () => {
let monitoredStore;
let monitoredLiftedStore;
let exportedState;
it('should not include stack trace', () => {
monitoredStore = createStore(counter, instrument());
monitoredLiftedStore = monitoredStore.liftedStore;
monitoredStore.dispatch({ type: 'INCREMENT' });
exportedState = monitoredLiftedStore.getState();
expect(exportedState.actionsById[0].stack).toBe(undefined);
expect(exportedState.actionsById[1].stack).toBe(undefined);
});
it('should include stack trace', () => {
monitoredStore = createStore(counter, instrument(undefined, { trace: true }));
monitoredLiftedStore = monitoredStore.liftedStore;
monitoredStore.dispatch({ type: 'INCREMENT' });
exportedState = monitoredLiftedStore.getState();
expect(exportedState.actionsById[0].stack).toBe(undefined);
expect(exportedState.actionsById[1].stack).toBeA('string');
expect(exportedState.actionsById[1].stack).toMatch(/^Error/);
expect(exportedState.actionsById[1].stack).toContain('at Object.performAction');
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
});
it('should get stack trace from a function', () => {
const traceFn = () => new Error().stack;
monitoredStore = createStore(counter, instrument(undefined, { trace: traceFn }));
monitoredLiftedStore = monitoredStore.liftedStore;
monitoredStore.dispatch({ type: 'INCREMENT' });
exportedState = monitoredLiftedStore.getState();
expect(exportedState.actionsById[0].stack).toBe(undefined);
expect(exportedState.actionsById[1].stack).toBeA('string');
expect(exportedState.actionsById[1].stack).toContain('at Object.performAction');
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
});
it('should get stack trace inside setTimeout using a function', (done) => {
const stack = new Error().stack;
setTimeout(() => {
const traceFn = () => stack + new Error().stack;
monitoredStore = createStore(counter, instrument(undefined, { trace: traceFn }));
monitoredLiftedStore = monitoredStore.liftedStore;
monitoredStore.dispatch({ type: 'INCREMENT' });
exportedState = monitoredLiftedStore.getState();
expect(exportedState.actionsById[0].stack).toBe(undefined);
expect(exportedState.actionsById[1].stack).toBeA('string');
expect(exportedState.actionsById[1].stack).toContain('at Object.performAction');
expect(exportedState.actionsById[1].stack).toContain('instrument.js');
expect(exportedState.actionsById[1].stack).toContain('instrument.spec.js');
expect(exportedState.actionsById[1].stack).toContain('/mocha/');
done();
});
});
});
describe('Import State', () => { describe('Import State', () => {
let monitoredStore; let monitoredStore;
let monitoredLiftedStore; let monitoredLiftedStore;
@ -736,8 +801,8 @@ describe('instrument', () => {
expect(importMonitoredLiftedStore.getState()).toEqual(expectedImportedState); expect(importMonitoredLiftedStore.getState()).toEqual(expectedImportedState);
}); });
it('should include callstack', () => { it('should include stack trace', () => {
let importMonitoredStore = createStore(counter, instrument(undefined, { shouldIncludeCallstack: true })); let importMonitoredStore = createStore(counter, instrument(undefined, { trace: true }));
let importMonitoredLiftedStore = importMonitoredStore.liftedStore; let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
importMonitoredStore.dispatch({ type: 'DECREMENT' }); importMonitoredStore.dispatch({ type: 'DECREMENT' });
@ -801,8 +866,8 @@ describe('instrument', () => {
expect(filterStackAndTimestamps(importMonitoredLiftedStore.getState())).toEqual(exportedState); expect(filterStackAndTimestamps(importMonitoredLiftedStore.getState())).toEqual(exportedState);
}); });
it('should include callstack', () => { it('should include stack trace', () => {
let importMonitoredStore = createStore(counter, instrument(undefined, { shouldIncludeCallstack: true })); let importMonitoredStore = createStore(counter, instrument(undefined, { trace: true }));
let importMonitoredLiftedStore = importMonitoredStore.liftedStore; let importMonitoredLiftedStore = importMonitoredStore.liftedStore;
importMonitoredStore.dispatch({ type: 'DECREMENT' }); importMonitoredStore.dispatch({ type: 'DECREMENT' });