vulcan-errors package

This commit is contained in:
SachaG 2018-11-04 16:32:34 +09:00
parent 47bd12eb80
commit 983c9ed08a
10 changed files with 422 additions and 0 deletions

View file

@ -0,0 +1 @@
Vulcan error tracking package.

View file

@ -0,0 +1 @@
export * from '../modules/index.js';

View file

@ -0,0 +1,45 @@
/*
ErrorCatcher
Usage:
<Components.ErrorCatcher>
<YourComponentTree />
</Components.ErrorCatcher>
*/
import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core';
import React, { Component } from 'react';
import { Errors } from '../modules/errors.js';
class ErrorCatcher extends Component {
state = {
error: null,
};
componentDidCatch = (error, errorInfo) => {
const { currentUser } = this.props;
this.setState({ error });
Errors.log({
message: error.message,
error,
details: errorInfo,
currentUser,
});
};
render() {
const { error } = this.state;
return error ? (
<div className="error-catcher">
<Components.Flash message={{ id: 'errors.generic_report', properties: { errorMessage: error.message } }} />
</div>
) : (
this.props.children
);
}
}
registerComponent('ErrorCatcher', ErrorCatcher, withCurrentUser);

View file

@ -0,0 +1,59 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Components, registerComponent, withCurrentUser } from 'meteor/vulcan:core';
import classNames from 'classnames';
import { Errors } from 'meteor/vulcan:errors';
class ErrorsUserMonitor extends PureComponent {
constructor(props) {
super(props);
}
componentDidMount() {
this.checkCurrentUser();
}
componentDidUpdate() {
this.checkCurrentUser();
}
checkCurrentUser(prevProps, prevState, snapshot) {
const currentUser = this.props.currentUser;
const currentUserId = currentUser && currentUser._id;
const errorsUserId = Errors.currentUser && Errors.currentUser._id;
if (currentUserId !== errorsUserId) {
const currentUserEmail = currentUser && currentUser.email;
const errorsUserEmail = Errors.currentUser && Errors.currentUser.email;
console.log(`User changed from ${errorsUserEmail} (${errorsUserId}) to ${currentUserEmail} (${currentUserId})`);
Errors.setCurrentUser(currentUser);
}
}
render() {
const { className, currentUser } = this.props;
return (
<div
className={classNames(
'errors-user-monitor',
(currentUser && currentUser._id) || 'no-user',
currentUser && currentUser.email,
className
)}
/>
);
}
}
ErrorsUserMonitor.propTypes = {
className: PropTypes.string,
currentUser: PropTypes.object,
};
ErrorsUserMonitor.displayName = 'ErrorsUserMonitor';
registerComponent('ErrorsUserMonitor', ErrorsUserMonitor, withCurrentUser);

View file

@ -0,0 +1,197 @@
import Users from 'meteor/vulcan:users';
import { getSetting } from 'meteor/vulcan:core';
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import { formatMessage } from 'meteor/vulcan:i18n';
import _isEmpty from 'lodash/isEmpty';
import { inspect } from 'util';
export const initFunctions = [];
export const logFunctions = [];
export const userFunctions = [];
export const scrubFields = new Set();
export const userFields = {
id: '_id',
email: 'email',
username: 'profile.username',
isAdmin: 'isAdmin',
};
export const addInitFunction = fn => {
initFunctions.push(fn);
// execute init function as soon as possible
fn();
};
export const addLogFunction = fn => {
logFunctions.push(fn);
};
export const addUserFunction = fn => {
userFunctions.push(fn);
};
export const addUserFields = fields => {
Object.assign(userFields, fields);
};
export const addScrubFields = fields => {
fields = Array.isArray(fields) ? fields : [fields];
for (const field of fields) {
scrubFields.add(field);
}
};
// export const getUserPayload = function(userOrUserId) {
// try {
// const user = Users.getUser(userOrUserId);
// if (!user) return null;
// const userPayload = {};
// for (const field in userFields) {
// const path = userFields[field];
// userPayload[field] = get(user, path);
// }
// return userPayload;
// } catch (error) {
// return null;
// }
// };
// export const getServerHost = function() {
// return process.env.GALAXY_CONTAINER_ID
// ? process.env.GALAXY_CONTAINER_ID.split('-')[1]
// : getSetting('public.environment');
// };
// export const processApolloErrors = function(err) {
// if (!err) return;
// const apolloErrors =
// err.original && err.original.data && err.original.data.errors
// ? formatApolloError(err.original, formatMessage, '\n', ' ApolloError: ')
// : err.data && err.data.errors
// ? formatApolloError(err, formatMessage, '\n', ' ApolloError: ')
// : '';
// err.message = err.message + '\n' + apolloErrors;
// };
// export const formatApolloError = (err, formatMessage, separator = ', ', prefix = '') => {
// let formatted = '';
// const formatProperties = properties => {
// return _isEmpty(properties) ? '' : ' ' + inspect(properties);
// };
// const addError = error => {
// let message = '';
// if (error.id) {
// try {
// message = formatMessage({ id: error.id }, error.properties);
// } catch (err) {
// message = error.id + formatProperties(error.properties);
// }
// } else if (error.message) {
// message = error.message + formatProperties(error.properties);
// }
// formatted += formatted ? separator : '';
// formatted += prefix + message;
// };
// const graphQLErrors = err.data && err.data.errors ? [err] : err.graphQLErrors;
// if (graphQLErrors) {
// for (let graphQLError of graphQLErrors) {
// if (graphQLError.data && graphQLError.data.errors) {
// for (let innerError of graphQLError.data.errors) {
// if (innerError.data) {
// addError(innerError.data);
// } else {
// addError(innerError);
// }
// }
// } else if (graphQLError.data) {
// addError(graphQLError.data);
// } else {
// addError(graphQLError);
// }
// }
// } else {
// let message = err.message;
// const graphqlPrefixIsPresent = message.match(/GraphQL error: (.*)/);
// addError({ message: graphqlPrefixIsPresent ? graphqlPrefixIsPresent[1] : message });
// }
// return formatted;
// };
export const Errors = {
currentUser: null,
setCurrentUser: function(user) {
// avoid setting current user multiple times
if (isEqual(this.currentUser, user)) return;
for (const fn of userFunctions) {
try {
fn(user);
} catch (error) {
// eslint-disable-next-line no-console
console.log(`// ${fn.name} with ${user && user.email}`);
// eslint-disable-next-line no-console
console.log(error);
}
}
this.currentUser = user;
},
/*rethrow: function (message, err, details, level = 'error') {
err = new RethrownError(message, err, { stack: true, remove: 1 });
Errors.log({ err, details, level });
},*/
log: function(params) {
const { message, err, level = 'error' } = params;
// processApolloErrors(err);
for (const fn of logFunctions) {
try {
fn(params);
} catch (error) {
// eslint-disable-next-line no-console
console.log(`// ${fn.name} ${level} error for '${(err && err.message) || message}'`);
// eslint-disable-next-line no-console
console.log(error);
}
}
},
/*
Shortcuts
*/
debug: params => Errors.log({ level: 'debug', ...params }),
info: params => Errors.log({ level: 'info', ...params }),
warning: params => Errors.log({ level: 'warning', ...params }),
error: params => Errors.log({ level: 'error', ...params }),
critical: params => Errors.log({ level: 'info', ...params }),
// getMethodDetails: function(method) {
// return {
// userId: method.userId,
// headers: method.connection && method.connection.httpHeaders,
// };
// },
};

View file

@ -0,0 +1,50 @@
// This is experimental and not actually used by vulcan:errors
// ### ExtendedError
// From https://github.com/deployable/deployable-errors
// Custom errors can extend this
export default class ExtendedError extends Error {
constructor(message, options = {}) {
// Make it an error
super(message);
// Standard Error things
this.name = this.constructor.name;
this.message = message;
// Get a stack trace where we can
/* istanbul ignore else */
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = new Error(message).stack;
}
// A standard place to store a more human readable error message
if (options.simple) this.simple = options.simple;
}
// Support `.statusCode` for express
get statusCode() {
return this.status;
}
set statusCode(val) {
this.status = val;
}
// Fix Errors `.toJSON` for our errors
toJSON() {
let o = {};
Object.getOwnPropertyNames(this).forEach(key => (o[key] = this[key]), this);
return o;
}
toResponse() {
let o = this.toJSON();
if (process && process.env && process.env.NODE_ENV !== 'development') delete o.stack;
return o;
}
}

View file

@ -0,0 +1,3 @@
import '../components/ErrorsUserMonitor';
import '../components/ErrorCatcher';
export * from './errors';

View file

@ -0,0 +1,45 @@
import ExtendedError from './extended';
// This is experimental and not actually used by vulcan:errors
/**
* Rethrow an error that you caught in your code, adding an additional message,
* and preserving the stack trace
*
* Based on https://github.com/deployable/deployable-errors
* See https://stackoverflow.com/questions/42754270/re-throwing-exception-in-nodejs-and-not-losing-stack-trace
*
* @example
* try {
* ... some code
* } catch (error) {
* new RethrownError('new error message', error, { stack: true });
* }
*/
export default class RethrownError extends ExtendedError {
/**
* @param {string} message - An error message
* @param {Error} error - An Error caught in a catch block
* @param {Object} [options] - The employee who is responsible for the project.
* @param {boolean|number} [options.stack] - Enable, disable or set the number of lines of stack output
* @param {number} [options.remove] - The number of lines to remove from the beginning of the stack trace
*/
constructor(message, error, options = {}) {
super(message);
if (!error) throw new Error(`new ${this.name} requires a message and error`);
let message_lines = (this.message.match(/\n/g) || []).length + 1;
let stack_array = this.stack.split('\n');
if (options.remove) {
stack_array.splice(message_lines, options.remove);
}
if (options.stack !== true) {
stack_array = stack_array.slice(0, message_lines + (options.stack || 0));
}
//this.original = error;
this.stack = stack_array.join('\n') + '\n' + error.stack;
}
}

View file

@ -0,0 +1 @@
export * from '../modules/index.js';

View file

@ -0,0 +1,20 @@
Package.describe({
name: "vulcan:errors",
summary: "Vulcan error tracking package",
version: '1.12.8',
git: "https://github.com/VulcanJS/Vulcan.git"
});
Package.onUse(function(api) {
api.versionsFrom('1.6.1');
api.use([
'ecmascript',
'vulcan:core',
]);
api.mainModule("lib/server/main.js", "server");
api.mainModule('lib/client/main.js', 'client');
});