Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output stack trace for errors in debug mode #137

Merged
merged 5 commits into from
Sep 12, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
[PR #131](https://github.com/apollostack/apollo-server/pull/131)
* Improve logging function. Issue #79. ([@nnance](https://github.com/nnance)) in
[PR #136](https://github.com/apollostack/apollo-server/pull/136)
* Output stack trace for errors in debug mode. Issue #111. ([@nnance](https://github.com/nnance)) in
[PR #137](https://github.com/apollostack/apollo-server/pull/137)

### v0.2.6
* Expose the OperationStore as part of the public API. ([@nnance](https://github.com/nnance))
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"mocha": "^3.0.0",
"multer": "^1.1.0",
"remap-istanbul": "^0.6.4",
"sinon": "^1.17.5",
"supertest": "^2.0.0",
"supertest-as-promised": "^4.0.0",
"tslint": "^3.13.0",
Expand Down
63 changes: 48 additions & 15 deletions src/core/runQuery.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
expect,
} from 'chai';
import { expect } from 'chai';
import { stub } from 'sinon';

import {
GraphQLSchema,
Expand Down Expand Up @@ -62,6 +61,12 @@ const QueryType = new GraphQLObjectType({
return 'it ' + (<any>Promise).await('works');
},
},
testError: {
type: GraphQLString,
resolve() {
throw new Error('Secret error message');
},
},
},
});

Expand All @@ -88,18 +93,46 @@ describe('runQuery', () => {
});
});

it('returns a syntax error if the query string contains one', () => {
const query = `query { test`;
const expected = /Syntax Error GraphQL/;
return runQuery({
schema: Schema,
query: query,
variables: { base: 1 },
}).then((res) => {
expect(res.data).to.be.undefined;
expect(res.errors.length).to.equal(1);
return expect(res.errors[0].message).to.match(expected);
});
it('returns a syntax error if the query string contains one', () => {
const query = `query { test `;
const expected = /Syntax Error GraphQL/;
return runQuery({
schema: Schema,
query: query,
variables: { base: 1 },
}).then((res) => {
expect(res.data).to.be.undefined;
expect(res.errors.length).to.equal(1);
return expect(res.errors[0].message).to.match(expected);
});
});

it('sends stack trace to error if in an error occurs and debug mode is set', () => {
const query = `query { testError }`;
const expected = /at resolveOrError/;
const logStub = stub(console, 'error');
return runQuery({
schema: Schema,
query: query,
debug: true,
}).then((res) => {
logStub.restore();
expect(logStub.callCount).to.equal(1);
return expect(logStub.getCall(0).args[0]).to.match(expected);
});
});

it('does not send stack trace if in an error occurs and not in debug mode', () => {
const query = `query { testError }`;
const logStub = stub(console, 'error');
return runQuery({
schema: Schema,
query: query,
debug: false,
}).then((res) => {
logStub.restore();
return expect(logStub.callCount).to.equal(0);
});
});

it('returns a validation error if the query string does not pass validation', () => {
Expand Down
10 changes: 10 additions & 0 deletions src/core/runQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface QueryOptions {

formatError?: Function;
formatResponse?: Function;
debug?: boolean;
}

const resolvedPromise = Promise.resolve();
Expand All @@ -62,6 +63,8 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
let documentAST: Document;

const logFunction = options.logFunction || function(){ return null; };
const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
const debug = typeof options.debug !== 'undefined' ? options.debug : debugDefault;

logFunction({action: LogAction.request, step: LogStep.start});

Expand All @@ -73,6 +76,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
return errors.map(options.formatError || formatError as any) as Array<Error>;
}

function printStackTrace(error: Error) {
console.error(error.stack);
}

const qry = typeof options.query === 'string' ? options.query : print(options.query);
logFunction({action: LogAction.request, step: LogStep.status, key: 'query', data: qry});
logFunction({action: LogAction.request, step: LogStep.status, key: 'variables', data: options.variables});
Expand Down Expand Up @@ -123,6 +130,9 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResult> {
};
if (gqlResponse.errors) {
response['errors'] = format(gqlResponse.errors);
if (debug) {
gqlResponse.errors.map(printStackTrace);
}
}
if (options.formatResponse) {
response = options.formatResponse(response, options);
Expand Down
2 changes: 2 additions & 0 deletions src/integrations/apolloOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { LogFunction } from '../core/runQuery';
* - (optional) formatParams: a function applied to the parameters of every invocation of runQuery
* - (optional) validationRules: extra validation rules applied to requests
* - (optional) formatResponse: a function applied to each graphQL execution result
* - (optional) debug: a boolean that will print additional debug logging if execution errors occur
*
*/
interface ApolloOptions {
Expand All @@ -23,6 +24,7 @@ interface ApolloOptions {
formatParams?: Function;
validationRules?: Array<ValidationRule>;
formatResponse?: Function;
debug?: boolean;
}

export default ApolloOptions;
1 change: 1 addition & 0 deletions src/integrations/expressApollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function apolloExpress(options: ApolloOptions | ExpressApolloOptionsFunct
validationRules: optionsObject.validationRules,
formatError: formatErrorFn,
formatResponse: optionsObject.formatResponse,
debug: optionsObject.debug,
};

if (optionsObject.formatParams) {
Expand Down
1 change: 1 addition & 0 deletions src/integrations/hapiApollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ async function processQuery(graphqlParams, optionsObject: ApolloOptions, reply)
validationRules: optionsObject.validationRules,
formatError: formatErrorFn,
formatResponse: optionsObject.formatResponse,
debug: optionsObject.debug,
};

if (optionsObject.formatParams) {
Expand Down
44 changes: 41 additions & 3 deletions src/integrations/integrations.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
expect,
} from 'chai';
import { expect } from 'chai';
import { stub } from 'sinon';

import {
GraphQLSchema,
Expand Down Expand Up @@ -425,6 +424,45 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
});
});

it('sends stack trace to error if debug mode is set', () => {
const expected = /at resolveOrError/;
const stackTrace = [];
const origError = console.error;
console.error = (...args) => stackTrace.push(args);
app = createApp({apolloOptions: {
schema: Schema,
debug: true,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
console.error = origError;
return expect(stackTrace[0][0]).to.match(expected);
});
});

it('sends stack trace to error log if debug mode is set', () => {
const logStub = stub(console, 'error');
const expected = /at resolveOrError/;
app = createApp({apolloOptions: {
schema: Schema,
debug: true,
}});
const req = request(app)
.post('/graphql')
.send({
query: 'query test{ testError }',
});
return req.then((res) => {
logStub.restore();
expect(logStub.callCount).to.equal(1);
return expect(logStub.getCall(0).args[0]).to.match(expected);
});
});

it('applies additional validationRules', () => {
const expected = 'AlwaysInvalidRule was really invalid!';
const AlwaysInvalidRule = function (context) {
Expand Down
1 change: 1 addition & 0 deletions src/integrations/koaApollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function apolloKoa(options: ApolloOptions | KoaApolloOptionsFunction): Ko
validationRules: optionsObject.validationRules,
formatError: formatErrorFn,
formatResponse: optionsObject.formatResponse,
debug: optionsObject.debug,
};

if (optionsObject.formatParams) {
Expand Down
3 changes: 2 additions & 1 deletion typings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"dependencies": {
"chai": "registry:npm/chai#3.5.0+20160723033700",
"graphql": "github:nitintutlani/typed-graphql#ffe7e46e2249cc8f3824a5d15a44938f4354afe9",
"http-errors": "registry:npm/http-errors#1.4.0+20160723033700"
"http-errors": "registry:npm/http-errors#1.4.0+20160723033700",
"sinon": "registry:npm/sinon#1.16.0+20160723033700"
},
"globalDependencies": {
"body-parser": "registry:dt/body-parser#0.0.0+20160619023215",
Expand Down