Skip to content

Commit 5f9353c

Browse files
authored
Merge pull request #220 from niftymonkey/master
Added in support for custom debug logging
2 parents 7b87d92 + 46b65ee commit 5f9353c

File tree

5 files changed

+165
-43
lines changed

5 files changed

+165
-43
lines changed

src/logging.js

+73-38
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,83 @@ import { getOption } from './reactor/fns'
33
/* eslint-disable no-console */
44
/**
55
* Wraps a Reactor.react invocation in a console.group
6-
* @param {ReactorState} reactorState
7-
* @param {String} type
8-
* @param {*} payload
9-
*/
10-
exports.dispatchStart = function(reactorState, type, payload) {
11-
if (!getOption(reactorState, 'logDispatches')) {
12-
return
13-
}
14-
15-
if (console.group) {
16-
console.groupCollapsed('Dispatch: %s', type)
17-
console.group('payload')
18-
console.debug(payload)
19-
console.groupEnd()
20-
}
21-
}
22-
23-
exports.dispatchError = function(reactorState, error) {
24-
if (!getOption(reactorState, 'logDispatches')) {
25-
return
26-
}
27-
28-
if (console.group) {
29-
console.debug('Dispatch error: ' + error)
30-
console.groupEnd()
31-
}
32-
}
6+
*/
7+
export const ConsoleGroupLogger = {
8+
/**
9+
* @param {ReactorState} reactorState
10+
* @param {String} type
11+
* @param {*} payload
12+
*/
13+
dispatchStart: function(reactorState, type, payload) {
14+
if (!getOption(reactorState, 'logDispatches')) {
15+
return
16+
}
3317

34-
exports.dispatchEnd = function(reactorState, state, dirtyStores) {
35-
if (!getOption(reactorState, 'logDispatches')) {
36-
return
37-
}
18+
if (console.group) {
19+
console.groupCollapsed('Dispatch: %s', type)
20+
console.group('payload')
21+
console.debug(payload)
22+
console.groupEnd()
23+
}
24+
},
25+
/**
26+
* @param {ReactorState} reactorState
27+
* @param {Error} error
28+
*/
29+
dispatchError: function(reactorState, error) {
30+
if (!getOption(reactorState, 'logDispatches')) {
31+
return
32+
}
3833

39-
if (console.group) {
40-
if (getOption(reactorState, 'logDirtyStores')) {
41-
console.log('Stores updated:', dirtyStores.toList().toJS())
34+
if (console.group) {
35+
console.debug('Dispatch error: ' + error)
36+
console.groupEnd()
37+
}
38+
},
39+
/**
40+
* @param {ReactorState} reactorState
41+
* @param {Map} state
42+
* @param {Set} dirtyStores
43+
*/
44+
dispatchEnd: function(reactorState, state, dirtyStores) {
45+
if (!getOption(reactorState, 'logDispatches')) {
46+
return
4247
}
4348

44-
if (getOption(reactorState, 'logAppState')) {
45-
console.debug('Dispatch done, new state: ', state.toJS())
49+
if (console.group) {
50+
if (getOption(reactorState, 'logDirtyStores')) {
51+
console.log('Stores updated:', dirtyStores.toList().toJS())
52+
}
53+
54+
if (getOption(reactorState, 'logAppState')) {
55+
console.debug('Dispatch done, new state: ', state.toJS())
56+
}
57+
console.groupEnd()
4658
}
47-
console.groupEnd()
48-
}
59+
},
4960
}
61+
5062
/* eslint-enable no-console */
63+
64+
export const NoopLogger = {
65+
/**
66+
* @param {ReactorState} reactorState
67+
* @param {String} type
68+
* @param {*} payload
69+
*/
70+
dispatchStart: function(reactorState, type, payload) {
71+
},
72+
/**
73+
* @param {ReactorState} reactorState
74+
* @param {Error} error
75+
*/
76+
dispatchError: function(reactorState, error) {
77+
},
78+
/**
79+
* @param {ReactorState} reactorState
80+
* @param {Map} state
81+
* @param {Set} dirtyStores
82+
*/
83+
dispatchEnd: function(reactorState, state, dirtyStores) {
84+
},
85+
}

src/reactor.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import Immutable from 'immutable'
22
import createReactMixin from './create-react-mixin'
33
import * as fns from './reactor/fns'
44
import { DefaultCache } from './reactor/cache'
5+
import { NoopLogger, ConsoleGroupLogger } from './logging'
56
import { isKeyPath } from './key-path'
67
import { isGetter } from './getter'
78
import { toJS } from './immutable-helpers'
8-
import { toFactory } from './utils'
9+
import { extend, toFactory } from './utils'
910
import {
1011
ReactorState,
1112
ObserverState,
@@ -26,9 +27,16 @@ class Reactor {
2627
constructor(config = {}) {
2728
const debug = !!config.debug
2829
const baseOptions = debug ? DEBUG_OPTIONS : PROD_OPTIONS
30+
// if defined, merge the custom implementation over the noop logger to avoid undefined lookups,
31+
// otherwise, just use the built-in console group logger
32+
let logger = config.logger ? extend({}, NoopLogger, config.logger) : NoopLogger
33+
if (!config.logger && debug) {
34+
logger = ConsoleGroupLogger
35+
}
2936
const initialReactorState = new ReactorState({
3037
debug: debug,
3138
cache: config.cache || DefaultCache(),
39+
logger: logger,
3240
// merge config options with the defaults
3341
options: baseOptions.merge(config.options || {}),
3442
})

src/reactor/fns.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import Immutable from 'immutable'
2-
import logging from '../logging'
32
import { CacheEntry } from './cache'
43
import { isImmutableValue } from '../immutable-helpers'
54
import { toImmutable } from '../immutable-helpers'
@@ -74,6 +73,8 @@ export function replaceStores(reactorState, stores) {
7473
* @return {ReactorState}
7574
*/
7675
export function dispatch(reactorState, actionType, payload) {
76+
let logging = reactorState.get('logger')
77+
7778
if (actionType === undefined && getOption(reactorState, 'throwOnUndefinedActionType')) {
7879
throw new Error('`dispatch` cannot be called with an `undefined` action type.')
7980
}

src/reactor/records.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Map, Set, Record } from 'immutable'
22
import { DefaultCache } from './cache'
3+
import { NoopLogger } from '../logging'
34

45
export const PROD_OPTIONS = Map({
56
// logs information for each dispatch
@@ -40,6 +41,7 @@ export const ReactorState = Record({
4041
state: Map(),
4142
stores: Map(),
4243
cache: DefaultCache(),
44+
logger: NoopLogger,
4345
// maintains a mapping of storeId => state id (monotomically increasing integer whenever store state changes)
4446
storeStates: Map(),
4547
dirtyStores: Set(),

tests/reactor-tests.js

+79-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Reactor, Store } from '../src/main'
33
import { getOption } from '../src/reactor/fns'
44
import { toImmutable } from '../src/immutable-helpers'
55
import { PROD_OPTIONS, DEBUG_OPTIONS } from '../src/reactor/records'
6-
import logging from '../src/logging'
6+
import { NoopLogger, ConsoleGroupLogger } from '../src/logging'
77

88
describe('Reactor', () => {
99
it('should construct without \'new\'', () => {
@@ -54,6 +54,82 @@ describe('Reactor', () => {
5454
expect(getOption(reactor.reactorState, 'throwOnNonImmutableStore')).toBe(true)
5555
expect(getOption(reactor.reactorState, 'throwOnDispatchInDispatch')).toBe(false)
5656
})
57+
58+
describe('custom logging', () => {
59+
var handler
60+
61+
beforeEach(() => {
62+
handler = {
63+
dispatchStart() {},
64+
dispatchError() {},
65+
dispatchEnd() {},
66+
}
67+
spyOn(handler, 'dispatchStart')
68+
spyOn(handler, 'dispatchError')
69+
spyOn(handler, 'dispatchEnd')
70+
71+
spyOn(NoopLogger, 'dispatchError')
72+
})
73+
74+
afterEach(() => {
75+
handler = null
76+
})
77+
78+
it('should use dispatchStart on the provided logging handler if defined', () => {
79+
var reactor = new Reactor({
80+
debug: true,
81+
logger: handler,
82+
})
83+
84+
reactor.dispatch('setTax', 5)
85+
86+
expect(handler.dispatchStart).toHaveBeenCalled()
87+
})
88+
it('should use dispatchEnd on the provided logging handler if defined', () => {
89+
var reactor = new Reactor({
90+
debug: true,
91+
logger: handler,
92+
})
93+
94+
reactor.dispatch('setTax', 5)
95+
96+
expect(handler.dispatchEnd).toHaveBeenCalled()
97+
})
98+
it('should use dispatchError on the provided logging handler if defined', () => {
99+
var reactor = new Reactor({
100+
debug: true,
101+
logger: handler,
102+
options: {
103+
throwOnUndefinedActionType: false,
104+
},
105+
})
106+
107+
try {
108+
reactor.dispatch(undefined)
109+
} catch (e) {
110+
expect(handler.dispatchError).toHaveBeenCalled()
111+
}
112+
})
113+
it('should use the NoopLogger implementation when a logging function is not defined on the custom implementation', () => {
114+
var reactor = new Reactor({
115+
debug: true,
116+
logger: {
117+
dispatchStart() {},
118+
dispatchEnd() {},
119+
},
120+
options: {
121+
throwOnUndefinedActionType: false,
122+
},
123+
})
124+
125+
try {
126+
reactor.dispatch(undefined)
127+
} catch (e) {
128+
expect(NoopLogger.dispatchError).toHaveBeenCalled()
129+
}
130+
})
131+
132+
})
57133
})
58134

59135
describe('options', () => {
@@ -1098,7 +1174,7 @@ describe('Reactor', () => {
10981174
var reactor
10991175

11001176
beforeEach(() => {
1101-
spyOn(logging, 'dispatchError')
1177+
spyOn(ConsoleGroupLogger, 'dispatchError')
11021178
var throwingStore = new Store({
11031179
getInitialState() {
11041180
return 1
@@ -1124,7 +1200,7 @@ describe('Reactor', () => {
11241200
expect(function() {
11251201
reactor.dispatch('set', 'foo')
11261202
}).toThrow(new Error('Error during action handling'))
1127-
expect(logging.dispatchError).toHaveBeenCalledWith(reactor.reactorState, 'Error during action handling')
1203+
expect(ConsoleGroupLogger.dispatchError).toHaveBeenCalledWith(reactor.reactorState, 'Error during action handling')
11281204
})
11291205
})
11301206

0 commit comments

Comments
 (0)