Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion spec/helpers.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai';
import { formatTime, pad, repeat } from '../src/helpers';
import { directlyApplied, formatTime, pad, repeat } from '../src/helpers';

context('Helpers', () => {
describe('repeat', () => {
Expand All @@ -20,4 +20,17 @@ context('Helpers', () => {
expect(formatTime(time)).to.equal('23:15:30.000');
});
});

describe('Directly Applied', () => {
it('return true if getState and dispatch are available', () => {
const options = {
dispatch: () => {
},
getState: () => {
},
};
expect(directlyApplied(options)).to.be.true;
});
});

});
47 changes: 37 additions & 10 deletions spec/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,54 @@ context('default logger', () => {
});

context('createLogger', () => {
describe('init', () => {
beforeEach(() => {
sinon.spy(console, 'error');
sinon.spy(console, 'log');
});

afterEach(() => {
console.error.restore();
console.log.restore();
});

let store;

context('mistakenly passed directly to applyMiddleware', () => {
beforeEach(() => {
sinon.spy(console, 'error');
store = createStore(() => ({}), applyMiddleware(createLogger));
});

afterEach(() => {
console.error.restore();
it('should log error', () => {
sinon.assert.calledOnce(console.error);
});

it('should create an empty middleware', () => {
store.dispatch({ type: 'foo' });
sinon.assert.notCalled(console.log);
});
});

it('should throw error if passed direct to applyMiddleware', () => {
const store = createStore(() => ({}), applyMiddleware(createLogger));
context('options.logger undefined or null', () => {
beforeEach(() => {
const logger = createLogger({ logger: null });
store = createStore(() => ({}), applyMiddleware(logger));
});

it('should create an empty middleware', () => {
store.dispatch({ type: 'foo' });
sinon.assert.calledOnce(console.error);
sinon.assert.notCalled(console.log);
});
});

it('should be ok', () => {
const store = createStore(() => ({}), applyMiddleware(createLogger()));
context('options.predicate returns false', () => {
beforeEach(() => {
const logger = createLogger({ predicate: () => false });
store = createStore(() => ({}), applyMiddleware(logger));
});

it('should not log', () => {
store.dispatch({ type: 'foo' });
sinon.assert.notCalled(console.error);
sinon.assert.notCalled(console.log);
});
});
});
33 changes: 33 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,36 @@ export const timer =
(typeof performance !== 'undefined' && performance !== null) && typeof performance.now === 'function' ?
performance :
Date;

export const directlyApplied = ({ getState, dispatch }) => !!(getState && dispatch);

export const hasLogger = ({ logger }) => logger;

export const shouldNotLog = ({ predicate }, getState, action) =>
(typeof predicate === 'function' && !predicate(getState, action));

export const shouldDiff = ({ diff, diffPredicate }, getState, action) =>
!!(diff && typeof diffPredicate === 'function' && diffPredicate(getState, action));

export const emptyLogger = () => () => next => action => next(action);

export function emptyLoggerWarning() {
// eslint-disable-next-line no-console
console.error(`[redux-logger] redux-logger not installed. Make sure to pass logger instance as middleware:
// Logger with default options
import { logger } from 'redux-logger'
const store = createStore(
reducer,
applyMiddleware(logger)
)
// Or you can create your own logger with custom options http://bit.ly/redux-logger-options
import createLogger from 'redux-logger'
const logger = createLogger({
// ...options
});
const store = createStore(
reducer,
applyMiddleware(logger)
)
`);
}
106 changes: 45 additions & 61 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import printBuffer from './core';
import defaults from './defaults';
import { timer } from './helpers';
import {
directlyApplied,
emptyLogger,
emptyLoggerWarning,
hasLogger,
shouldDiff,
shouldNotLog,
timer,
} from './helpers';

/* eslint max-len: ["error", 110, { "ignoreComments": true }] */
/**
Expand All @@ -23,79 +31,55 @@ import { timer } from './helpers';
* @returns {function} logger middleware
*/
function createLogger(options = {}) {
const loggerOptions = Object.assign({}, defaults, options);
const {
logger,
stateTransformer,
errorTransformer,
predicate,
logErrors,
diffPredicate,
} = loggerOptions;
const { getState: getStateOption, dispatch } = options;
// Return if 'console' object is not defined
if (typeof logger === 'undefined') {
return () => next => action => next(action);
}

// Detect if 'createLogger' was passed directly to 'applyMiddleware'.
if (getStateOption && dispatch) {
// eslint-disable-next-line no-console
console.error(`[redux-logger] redux-logger not installed. Make sure to pass logger instance as middleware:
// Logger with default options
import { logger } from 'redux-logger'
const store = createStore(
reducer,
applyMiddleware(logger)
)
// Or you can create your own logger with custom options http://bit.ly/redux-logger-options
import { createLogger } from 'redux-logger'
const logger = createLogger({
// ...options
});
const store = createStore(
reducer,
applyMiddleware(logger)
)
`);
return () => next => action => next(action);
if (directlyApplied(options)) {
emptyLoggerWarning();
return emptyLogger();
}

const logBuffer = [];
const loggerOptions = Object.assign({}, defaults, options);

// Return if 'console' object is not defined
if (!hasLogger(loggerOptions)) return emptyLogger();

return ({ getState }) => next => (action) => {
// Exit early if predicate function returns 'false'
if (typeof predicate === 'function' && !predicate(getState, action)) {
return next(action);
}
if (shouldNotLog(options, getState, action)) return next(action);

const logEntry = {};
logBuffer.push(logEntry);
logEntry.started = timer.now();
logEntry.startedTime = new Date();
logEntry.prevState = stateTransformer(getState());
logEntry.action = action;
const started = timer.now();
const startedTime = new Date();
const prevState = loggerOptions.stateTransformer(getState());
let returnedValue;
if (logErrors) {
try {
returnedValue = next(action);
} catch (e) {
logEntry.error = errorTransformer(e);
}
} else {
let error;

try {
returnedValue = next(action);
} catch (e) {
if (loggerOptions.logErrors) {
error = loggerOptions.errorTransformer(e);
} else {
throw e;
}
}
logEntry.took = timer.now() - logEntry.started;
logEntry.nextState = stateTransformer(getState());

const diff = loggerOptions.diff && typeof diffPredicate === 'function'
? diffPredicate(getState, action)
: loggerOptions.diff;
const took = timer.now() - started;
const nextState = loggerOptions.stateTransformer(getState());

loggerOptions.diff = shouldDiff(loggerOptions, getState, action);

printBuffer(logBuffer, Object.assign({}, loggerOptions, { diff }));
logBuffer.length = 0;
printBuffer(
[{
started,
startedTime,
prevState,
action,
error,
took,
nextState,
}],
loggerOptions);

if (logEntry.error) throw logEntry.error;
if (error) throw error;
return returnedValue;
};
}
Expand Down