diff --git a/packages/error-reporting/src/index.js b/packages/error-reporting/src/index.js index 3116dd19694..8935482feab 100644 --- a/packages/error-reporting/src/index.js +++ b/packages/error-reporting/src/index.js @@ -79,9 +79,9 @@ function Errors(initConfiguration) { return new Errors(initConfiguration); } - var logger = createLogger(initConfiguration); - var config = new Configuration(initConfiguration, logger); - var client = new AuthClient(config, logger); + this._logger = createLogger(initConfiguration); + this._config = new Configuration(initConfiguration, this._logger); + this._client = new AuthClient(this._config, this._logger); // Build the application interfaces for use by the hosting application /** @@ -91,7 +91,7 @@ function Errors(initConfiguration) { * console.log('done!'); * }); */ - this.report = manual(client, config); + this.report = manual(this._client, this._config); /** * @example * // Use to create and report errors manually with a high-degree @@ -103,7 +103,7 @@ function Errors(initConfiguration) { * console.log('done!'); * }); */ - this.event = messageBuilder(config); + this.event = messageBuilder(this._config); /** * @example * var hapi = require('hapi'); @@ -113,7 +113,7 @@ function Errors(initConfiguration) { * // AFTER ALL OTHER ROUTE HANDLERS * server.register({register: errors.hapi}); */ - this.hapi = hapi(client, config); + this.hapi = hapi(this._client, this._config); /** * @example * var express = require('express'); @@ -122,7 +122,7 @@ function Errors(initConfiguration) { * app.use(errors.express); * app.listen(3000); */ - this.express = express(client, config); + this.express = express(this._client, this._config); /** * @example * var restify = require('restify'); @@ -130,7 +130,7 @@ function Errors(initConfiguration) { * // BEFORE ALL OTHER ROUTE HANDLERS * server.use(errors.restify(server)); */ - this.restify = restify(client, config); + this.restify = restify(this._client, this._config); /** * @example * var koa = require('koa'); @@ -138,7 +138,7 @@ function Errors(initConfiguration) { * // BEFORE ALL OTHER ROUTE HANDLERS HANDLERS * app.use(errors.koa); */ - this.koa = koa(client, config); + this.koa = koa(this._client, this._config); } module.exports = Errors; diff --git a/packages/error-reporting/system-test/testAuthClient.js b/packages/error-reporting/system-test/testAuthClient.js index 7e01b258ce5..4b3074cd2d8 100644 --- a/packages/error-reporting/system-test/testAuthClient.js +++ b/packages/error-reporting/system-test/testAuthClient.js @@ -19,6 +19,7 @@ var assert = require('assert'); var nock = require('nock'); var RequestHandler = require('../src/google-apis/auth-client.js'); +var ErrorsApiTransport = require('../utils/errors-api-transport.js'); var ErrorMessage = require('../src/classes/error-message.js'); var Configuration = require('../test/fixtures/configuration.js'); var createLogger = require('../src/logger.js'); @@ -32,6 +33,7 @@ var pick = require('lodash.pick'); var omitBy = require('lodash.omitby'); const ERR_TOKEN = '_@google_STACKDRIVER_INTEGRATION_TEST_ERROR__'; +const TIMEOUT = 20000; const envKeys = ['GOOGLE_APPLICATION_CREDENTIALS', 'GCLOUD_PROJECT', 'NODE_ENV']; @@ -357,3 +359,74 @@ describe('Expected Behavior', function() { }); }); }); + +describe('error-reporting', function() { + const TIMESTAMP = Date.now(); + const BASE_NAME = 'error-reporting-system-test'; + function buildName(suffix) { + return [TIMESTAMP, BASE_NAME, suffix].join('_'); + } + + const SERVICE_NAME = buildName('service-name'); + const SERVICE_VERSION = buildName('service-version'); + + var errors; + var transport; + before(function() { + errors = require('../src/index.js')({ + ignoreEnvironmentCheck: true, + serviceContext: { + service: SERVICE_NAME, + version: SERVICE_VERSION + } + }); + transport = new ErrorsApiTransport(errors._config, errors._logger); + }); + + after(function(done) { + transport.deleteAllEvents(function(err) { + assert.ifError(err); + done(); + }); + }); + + it('Should correctly publish errors', function(done) { + // After an error is reported, this test waits TIMEOUT ms before + // verifying the error has been reported to ensure the system had + // enough time to receive the error report and process it. + // As such, this test is set to fail due to a timeout only if sufficiently + // more than TIMEOUT ms has elapsed to avoid test fragility. + this.timeout(TIMEOUT * 2); + var errorId = buildName('message'); + errors.report(new Error(errorId), function(err, response, body) { + assert.ifError(err); + assert(isObject(response)); + assert.deepEqual(body, {}); + + setTimeout(function() { + transport.getAllGroups(function(err, groups) { + assert.ifError(err); + assert.ok(groups); + + var matchedErrors = groups.filter(function(errItem) { + return errItem && errItem.representative && + errItem.representative.message.startsWith('Error: ' + errorId); + }); + + // The error should have been reported exactly once + assert.strictEqual(matchedErrors.length, 1); + var errItem = matchedErrors[0]; + assert.ok(errItem); + assert.equal(errItem.count, 1); + var rep = errItem.representative; + assert.ok(rep); + var context = rep.serviceContext; + assert.ok(context); + assert.strictEqual(context.service, SERVICE_NAME); + assert.strictEqual(context.version, SERVICE_VERSION); + done(); + }); + }, TIMEOUT); + }); + }); +}); diff --git a/packages/error-reporting/utils/errors-api-transport.js b/packages/error-reporting/utils/errors-api-transport.js new file mode 100644 index 00000000000..963df8a2971 --- /dev/null +++ b/packages/error-reporting/utils/errors-api-transport.js @@ -0,0 +1,75 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var AuthClient = require('../src/google-apis/auth-client.js'); + +/* @const {String} Base Error Reporting API */ +var API = 'https://clouderrorreporting.googleapis.com/v1beta1/projects'; + +var ONE_HOUR_API = 'timeRange.period=PERIOD_1_HOUR'; + +class ErrorsApiTransport extends AuthClient { + constructor(config, logger) { + super(config, logger); + } + + deleteAllEvents(cb) { + var self = this; + self.getProjectId(function(err, id) { + if (err) { + return cb(err); + } + + var options = { + uri: [API, id, 'events'].join('/'), + method: 'DELETE' + }; + self.request_(options, function(err, /* jshint unused:false */ response, + /* jshint unused:false */ body) { + if (err) { + return cb(err); + } + + cb(null); + }); + }); + } + + getAllGroups(cb) { + var self = this; + self.getProjectId(function(err, id) { + if (err) { + return cb(err); + } + + var options = { + uri: [API, id, 'groupStats?' + ONE_HOUR_API].join('/'), + method: 'GET' + }; + self.request_(options, function(err, response, body) { + if (err) { + return cb(err); + } + + cb(null, JSON.parse(body.body).errorGroupStats || []); + }); + }); + } +} + +module.exports = ErrorsApiTransport;