{"action":"created","comment":{"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/comments/72857634","id":72857634,"diff_hunk":"@@ -15,150 +15,615 @@\n  */\n \n 'use strict';\n+var env = process.env;\n+var inherits = require('util').inherits;\n+var EventEmitter = require('events').EventEmitter;\n+var commonDiag = require('@google/cloud-diagnostics-common');\n+var utils = commonDiag.utils;\n var lodash = require('lodash');\n var isPlainObject = lodash.isPlainObject;\n-var isString = lodash.isString;\n-var isNumber = lodash.isNumber;\n+var isObject = lodash.isObject;\n var isUndefined = lodash.isUndefined;\n-var has = lodash.has;\n-var env = process.env;\n+var isFunction = lodash.isFunction;\n+var isBoolean = lodash.isBoolean;\n+var isString = lodash.isString;\n+var isEmpty = lodash.isEmpty;\n+var isNull = lodash.isNull;\n var version = require('../package.json').version;\n \n /**\n- * @typedef ServiceContextConfiguration\n- * @type Object\n- * @property {String} service - the hosting applications underlying service\n- *  platform\n- * @property {String} version - the hosting applications version\n+ * The Configuration constructor function initializes several internal\n+ * properties on the Configuration instance and accepts a runtime-given\n+ * configuration object which may be used by the Configuration instance\n+ * depending on the initialization transaction that occurs with the meta-data\n+ * service.\n+ * @class Configuration\n+ * @extends EventEmitter\n+ * @classdesc The Configuration class represents the runtime configuration of\n+ *  the Stackdriver error handling library. This Configuration class accepts the\n+ *  configuration options potentially given through the application interface\n+ *  but it also preferences values received from the metadata service over\n+ *  values given through the application interface. Becuase the Configuration\n+ *  class must handle async network I/O it exposes an EventEmitter interface\n+ *  wherein components interested in waiting for the instances configuration\n+ *  resolution may listen to the `ready` event. The `error` event may be emitted\n+ *  place of the `ready` event in which case the Configuration instance has\n+ *  failed to gather necessary information to interact with the Stackdriver\n+ *  service and all listening components must proceed by operating in `offline`\n+ *  mode.\n+ * @param {ConfigurationOptions} givenConfig - The config given by the\n+ *  hosting application at runtime. Configuration values will only be observed\n+ *  if they are given as a plain JS object; all other values will be ignored.\n+ * @param {commonDiag.logger} logger - an inited logger instance\n  */\n-\n+var Configuration = function(givenConfig, logger) {\n+  /**\n+   * The _initState property denotes whether or not the Configuration instance\n+   * has fired the `ready` event once. This boolean flag is used on the emission\n+   * invocation to stop the `ready` event firing more than once.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Boolean}\n+   * @defaultvalue false\n+   */\n+  this._initState = false;\n+  /**\n+   * The _startedInit property denotes whether or not the Configuration instance\n+   * has started the init process.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Boolean}\n+   * @defaultvalue false\n+   */\n+  this._startedInit = false;\n+  /**\n+   * The _initError property denotes whether or not the Configuration instance\n+   * has initialized and encountered an error during that initialization. Until\n+   * the _initState property is true and initialization complete the _initError\n+   * property will remain at its default value: null. If the _initState property\n+   * is true and the _initError property is null then this denotes that the\n+   * Configuration instance initialized without error. If the _initState\n+   * property is true and the _initError propert is an instance of the Error\n+   * class then this denotes the Configuration instance initialized to an\n+   * irrecoverable error. \n+   * @memberof Configuration\n+   * @private\n+   * @type {Error|Null}\n+   * @defaultvalue null\n+   */\n+  this._initError = null;\n+  /**\n+   * The _reportUncaughtExceptions property is meant to contain the optional\n+   * runtime configuration property reportUncaughtExceptions. This property will\n+   * default to true if not given false through the runtime configuration\n+   * meaning that the default behavior is to catch uncaught exceptions, report\n+   * them to the Stackdriver Errors API and then exit. If given false uncaught\n+   * exceptions will not be listened for and not be caught or reported.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Boolean}\n+   * @defaultvalue true\n+   */\n+  this._reportUncaughtExceptions = true;\n+  /**\n+   * The _shouldReportErrorsToAPI property is meant to denote whether or not\n+   * the Stackdriver error reporting library will actually try to report Errors\n+   * to the Stackdriver Error API. The value of this property is derived from\n+   * the `NODE_ENV` environmental variable. If the `NODE_ENV` variable is set to\n+   * 'production' then the _shouldReportErrorToAPI property will be set to true\n+   * and error reporting library will attempt to send errors to the Error API.\n+   * Otherwise the value will remain false and errors will not be reported to\n+   * the API.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Boolean}\n+   */\n+  this._shouldReportErrorsToAPI = env.NODE_ENV === 'production';\n+  /**\n+   * The _projectId property is meant to contain the string project id that the\n+   * hosting application is running under. The project id is a unique string\n+   * identifier for the project. If the Configuration instance is not able to\n+   * retrieve a project id from the metadata service or the runtime-given\n+   * configuration then the property will remain null. If given both a project\n+   * id through the metadata service and the runtime configuration then the\n+   * instance will assign the value given by the metadata service over the\n+   * runtime configuration. If the instance is unable to retrieve a valid\n+   * project id or number from runtime configuration and the metadata service\n+   * then this will trigger the `error` event in which listening components must\n+   * operate in 'offline' mode.\n+   * {@link https://cloud.google.com/compute/docs/storing-retrieving-metadata}\n+   * @memberof Configuration\n+   * @private\n+   * @type {String|Null}\n+   * @defaultvalue null\n+   */\n+  this._projectId = null;\n+  /**\n+   * The _projectNumber property is meant to contain the number project id that\n+   * the hosting application is running under. The project number is a unique\n+   * number identifier for the project. If the Configuration instance is unable\n+   * to retrieve a project number from the metadata service or the runtime\n+   * configuration then the property will remain null. If given both a project\n+   * number through the metadata service and the runtime configuration then the\n+   * instance will assign the value given by the metadata service over the\n+   * runtime configuration. If the Configuration instance is not able to \n+   * retrieve a project number or a project id from runtime configuration or\n+   * from the metadata service then this will trigger the `error` event in which\n+   * listening components must operate in 'offline' mode.\n+   * {@link https://cloud.google.com/compute/docs/storing-retrieving-metadata}\n+   * @memberof Configuration\n+   * @private\n+   * @type {String|Null}\n+   * @defaultvalue null\n+   */\n+  this._projectNumber = null;\n+  /**\n+   * The _key property is meant to contain the optional Stackdriver API key that\n+   * may be used in place of default application credentials to authenticate\n+   * with the Stackdriver Error API. This property will remain null if a key\n+   * is not given in the runtime configuration or an invalid type is given as\n+   * the runtime configuration.\n+   * {@link https://support.google.com/cloud/answer/6158862?hl=en}\n+   * @memberof Configuration\n+   * @private\n+   * @type {String|Null}\n+   * @defaultvalue null\n+   */\n+  this._key = null;\n+  /**\n+   * The _serviceContext property is meant to contain the optional service\n+   * context information which may be given in the runtime configuration. If\n+   * not given in the runtime configuration then the property value will remain\n+   * null.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Object}\n+   * @default\n+   */\n+  this._serviceContext = {service: '', version: ''};\n+  /**\n+   * The _version of the Error reporting library that is currently being run.\n+   * This information will be logged in errors communicated to the Stackdriver\n+   * Error API.\n+   * @memberof Configuration\n+   * @private\n+   * @type {String}\n+   */\n+  this._version = version;\n+  /**\n+   * The _givenConfiguration property holds a ConfigurationOptions object\n+   * which, if valid, will be merged against by the values taken from the meta-\n+   * data service. If the _givenConfiguration property is not valid then only\n+   * metadata values will be used in the Configuration instance.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Object|Null}\n+   * @defaultvalue null\n+   */\n+  this._givenConfiguration = isPlainObject(givenConfig) ? givenConfig : null;\n+  /**\n+   * The _logger property holds a logger instance which, if valid, will be used\n+   * to log any debug or error information generated by the instance.\n+   * @memberof Configuration\n+   * @private\n+   * @type {Object|Null}\n+   * @defaultvalue null\n+   */\n+  this._logger = isObject(logger) ? logger : null;\n+}\n+// Extend the Configuration constructor by augmenting it with EventEmitter\n+inherits(Configuration, EventEmitter);\n /**\n- * Attempts to determine whether or not a service context parameter was given\n- * by the Object configuration. If it is and it is of the correct type then\n- * it will be merged with the default service context where the given\n- * configuration will overwrite all shared properties on the default return\n- * value.\n- * @function extractServiceContext\n- * @param {ConfigurationOptions} initConfiguration - the init configuration\n- * @returns {ServiceContextConfiguration} - The service context of the hosting\n- *  application\n- */\n-function extractServiceContext(initConfiguration) {\n-\n-  var gaeModuleName = env.GAE_MODULE_NAME;\n-  var gaeModuleVersion = env.GAE_MODULE_VERSION;\n-\n-  var defaultReturnObject = {\n-    service : isString(gaeModuleName) ? gaeModuleName : '',\n-    version : isString(gaeModuleVersion) ? gaeModuleVersion : ''\n-  };\n-\n-  if (has(initConfiguration, 'serviceContext') &&\n-      isPlainObject(initConfiguration.serviceContext)) {\n-\n-    if (has(initConfiguration.serviceContext, 'service')) {\n-\n-      defaultReturnObject.service = initConfiguration.serviceContext.service;\n-    }\n-\n-    if (has(initConfiguration.serviceContext, 'version')) {\n-\n-      defaultReturnObject.version = initConfiguration.serviceContext.version;\n+ * The callback for `utils.getProjectNumber` this function is responsible for\n+ * determining whether or not an error occured in the metadata transaction and,\n+ * if one did not, assigning the value of the transaction (the project number)\n+ * to the instance property `_projectNumber`.\n+ * @memberof Configuration\n+ * @private\n+ * @function _assimilateProjectNumber\n+ * @param {Error|Undefined} err - If applicable, the error that occured during \n+ *  the transaction\n+ * @param {Null|String} projectNum - The project number if successfully \n+ *  retrieved, null otherwise.\n+ * @returns {Undefined} - does not return anything\n+ */\n+Configuration.prototype._assimilateProjectNumber = function(err, projectNum) {\n+  if (isNull(err) && isString(projectNum)) {\n+    this._projectNumber = projectNum;\n+  } else {\n+    if (this._logger) {\n+      this._logger.warn(\n+        'Unable to retrieve project number\\n',\n+        '\\t', 'Project Number:', projectNum, '\\n',\n+        '\\t', 'Error:', err\n+      );\n     }\n   }\n-\n-  return defaultReturnObject;\n-}\n-\n+  this._gatherLocalConfiguration();\n+  this._checkConfigurationIntegrity();\n+};\n /**\n- * @typedef NormalizedConfigurationVariables\n- * @type Object\n- * @property {String} projectId - the project id of the hosting application\n- * @property {Boolean} reportUncaughtExceptions=true - the flag denoting\n- *  whether or not to catch and report uncaught errors to the API\n- * @property {Object} serviceContext - the hosting applications service\n- *  information\n- * @property {String} serviceContext.service - the hosting applications service\n- *  that it is running atop of\n- * @property {String} serviceContext.version - the hosting applications version\n- * @property {String} key - an API key used to communicate with the service\n- * @property {String} version - the version of the module\n- * @property {Boolean} shouldReportErrorsToAPI - a flag indicating whether or\n- *  not the Middleware should send errors to the Stackdriver Error Reporting\n- *  API. This flag is derived from the `NODE_ENV` environmental varaible being\n- *  set to production: if the flag is set to 'production' then the\n- *  `shouldReportErrorsToAPI` property will be set to true and the API client\n- *  will attempt to send errors to the Stackdriver Error Reporting API.\n+ * The _checkConfigurationIntegrity function is meant to be called at the end\n+ * or metadata service and local initialization. This function checks to see \n+ * whether the `_projectId` or `_projectNumber` properties have been set. If one\n+ * or both of these properties has been set the initialization process is deemed\n+ * to be a success and the `ready` event will emitted provided this is the\n+ * first time this function has been called which is determined by the \n+ * `_initState` property being at its default value of false. If both the\n+ * `_projectId` and `_projectNumber` properties are set at thier default values\n+ * of null and this is the first time the function is run then the\n+ * initialization process is deemed to be a failure and the `error` event will\n+ * be emitted.\n+ * @memberof Configuration\n+ * @private\n+ * @function _checkConfigurationIntegrity\n+ * @fires Configuration#error\n+ * @fires Configuration#ready\n+ * @returns {Undefined} - does not return anything\n  */\n-\n-/**\n- * Orchestrates the gathering of configuration based on submission of the\n- * initConfiguration parameter. If all optional properties are given in the\n- * initConfiguration parameter then the only environmental variable that will\n- * be read is the NODE_ENV variable. If initConfiguration is not supplied or\n- * only partially supplied then gatherConfiguration will attempt to read\n- * environmental variables to supplement information as necessary.\n- * @function gatherConfiguration\n- * @param {ConfigurationOptions} initConfiguration - the options object\n- * @returns {NormalizedConfigurationVariables} - a configuration object\n- *  supplemented with environmental information as necessary\n- */\n-function gatherConfiguration(initConfiguration) {\n-\n-  var projectId = null;\n-  var reportUncaughtExceptions = true;\n-  var serviceContext = null;\n-  var key = null;\n-  var shouldReportErrorsToAPI = false;\n-\n-  if (env.NODE_ENV === 'production') {\n-\n-    shouldReportErrorsToAPI = true;\n+Configuration.prototype._checkConfigurationIntegrity = function() {\n+  this._initState = true;\n+  if (isNull(this._projectId) && isNull(this._projectNumber)) {\n+    // Irrecoverable configuration error, emit error event\n+    this._initError = new Error(\"Unable to gather project id or number\");\n+    /**\n+     * The error event is emitted when a configuration error has occurred or\n+     * a configuration is incomplete. If the error event is fired on the\n+     * instance then the ready event will not be fired on this same instance.\n+     * In this way the two events execution on one instance should be viewed\n+     * as mutually exclusive and if an error event is fired all concerned\n+     * components should have a default operating mode in which access to\n+     * configuration variables may be non-existent.\n+     * @event Configuration#error\n+     * @type {Error} - the error that occured during initialization\n+     */\n+    this.emit('error', this._initError);\n+    return;\n   } else {\n-\n-    console.log([\n-      'Warning: the NODE_ENV environmental variable is not set to',\n-      '\\'production\\'. To send errors to the Stackdriver Error Reporting API',\n-      'please set the NODE_ENV to \\'production\\'.'\n-    ].join(' '));\n+    /**\n+     * The ready event is emitted when a configuration instance has finished\n+     * gathering information from both local and remote sources and did not\n+     * encounter an irrecoverable error in the process. If the ready is fired\n+     * on the instance then the error event will not be fired on the same\n+     * instance. If the ready event is emitted listening components can\n+     * resume normal operation and safely access finalized configuration\n+     * properties on the instance.\n+     * @event Configuration#ready\n+     * @type {this} - the Configuration instance ready to be addressed\n+     */\n+    this.emit('ready', this);\n   }\n-\n-  if (isPlainObject(initConfiguration)) {\n-\n-    if (has(initConfiguration, 'projectId')) {\n-\n-      projectId = initConfiguration.projectId;\n+};\n+/**\n+ * The _gatherLocalConfiguration function is responsible for determining\n+ * directly determing whether the properties `reportUncaughtExceptions` and\n+ * `key`, which can be optionally supplied in the runtime configuration, should\n+ * be merged into the instance. This function also calls several specialized\n+ * environmental variable checkers which not only check for the optional runtime\n+ * configuration supplied values but also the processes environmental values.\n+ * @memberof Configuration\n+ * @private\n+ * @function _gatherLocalConfiguration\n+ * @returns {Undefined} - does not return anything\n+ */\n+Configuration.prototype._gatherLocalConfiguration = function() {\n+  this._checkLocalProjectId();\n+  this._checkLocalProjectNumber();\n+  if (isPlainObject(this._givenConfiguration)) {\n+    if (isBoolean(this._givenConfiguration.reportUncaughtExceptions)) {\n+      this._reportUncaughtExceptions = this._givenConfiguration\n+        .reportUncaughtExceptions;\n     }\n-\n-    if (has(initConfiguration, 'reportUncaughtExceptions')) {\n-\n-      reportUncaughtExceptions = !!initConfiguration.reportUncaughtExceptions;\n+    if (isString(this._givenConfiguration.key)) {\n+      this._key = this._givenConfiguration.key;\n     }\n-\n-    if (has(initConfiguration, 'key')) {\n-\n-      key = initConfiguration.key;\n+  }\n+};\n+/**\n+ * The _checkLocalProjectId function is responsible for determing whether the\n+ * _projectId property was set by the metadata service and whether or not the\n+ * _projectId property should/can be set with a environmental or runtime\n+ * configuration variable. If, upon execution of the _checkLocalProjectId\n+ * function, the _projectId property has already been set to a string then it is\n+ * assumed that this property has been set with the metadata services response.\n+ * The metadata value for the project id always take precedence over any other\n+ * locally configured project id value. Given that the metadata service did not\n+ * set the project id this function will defer next to the value set in the\n+ * environment named `GCLOUD_PROJECT` if it is set and of type string. If this\n+ * environmental variable is not set the function will defer to the\n+ * _givenConfiguration property if it is of type object and has a string\n+ * property named projectId. If none of these conditions are met then the\n+ * _projectId property will be left at its default value.\n+ * @memberof Configuration\n+ * @private\n+ * @function _checkLocalProjectId\n+ * @returns {Undefined} - does not return anything\n+ */\n+Configuration.prototype._checkLocalProjectId = function() {\n+  if (isString(env.GCLOUD_PROJECT) && !isEmpty(env.GCLOUD_PROJECT)\n+    && isNaN(env.GCLOUD_PROJECT)) {\n+    // GCLOUD_PROJECT is set and it is not the project number, set on instance\n+    this._projectId = env.GCLOUD_PROJECT;\n+    return;\n+  } else if (isPlainObject(this._givenConfiguration)\n+    && !isEmpty(this._givenConfiguration.projectId)\n+    && isString(this._givenConfiguration.projectId)\n+    && isNaN(this._givenConfiguration.projectId)) {\n+      // projectId was set in the runtime configuration and it is not the\n+      // project number, set on instance\n+      this._projectId = this._givenConfiguration.projectId;\n+      return;\n+  }\n+};\n+/**\n+ * The _checkLocalProjectNumber function is responsible for determing whether\n+ * the _projectNumber property was set by the metadata service and whether or \n+ * not the _projectNumber property should/can be set with a environmental or \n+ * runtime configuration variable. If, upon execution of the \n+ * _checkLocalProjectNumber function, the _projectNumber property has already\n+ * been set to a number then it is assumed that this property has been set with\n+ * the metadata services response. The metadata value for the project number\n+ * always take precedence over any other locally configured project number\n+ * value. Given that the metadata service did not set the project number this\n+ * function will defer next to the value set in the environment named\n+ * `GCLOUD_PROJECT` if it is set and of type string and this string represents a\n+ * number (e.g. '123'). If this environmental variable is not set the function\n+ * will defer to the _givenConfiguration property if it is of type object and\n+ * has a string property named projectId. If none of these conditions are met\n+ * then the _projectNumber property will be left at its default value.\n+ * @memberof Configuration\n+ * @private\n+ * @function _checkLocalProjectNumber\n+ * @returns {Undefined} - does not return anything\n+ */\n+Configuration.prototype._checkLocalProjectNumber = function() {\n+  if (isString(this._projectNumber)) {\n+    // already has been set by the metadata service\n+    return;\n+  } else if (isString(env.GCLOUD_PROJECT) && !isEmpty(env.GCLOUD_PROJECT)\n+    && !isNaN(env.GCLOUD_PROJECT)) {\n+      // GCLOUD_PROJECT is set and it is the project number, set on instance\n+      this._projectNumber = env.GCLOUD_PROJECT;\n+      return;\n+  } else if (isPlainObject(this._givenConfiguration)\n+    && !isEmpty(this._givenConfiguration.projectId)\n+    && !isNaN(this._givenConfiguration.projectId)) {\n+      // projectId was set in the runtime configuration and is the project\n+      // number, set on instance\n+      this._projectNumber = this._givenConfiguration.projectId;\n+      return;\n+  }\n+};\n+/**\n+ * The _checkLocalServiceContext function is responsible for attempting to\n+ * source the _serviceContext objects values from runtime configuration and the\n+ * environment. First the function will check the env for known service context\n+ * names, if these are not set then it will defer to the _givenConfiguration\n+ * property if it is set on the instance. The function will check env variables\n+ * `GAE_MODULE_NAME` and `GAE_MODULE_VERSION` for `_serviceContext.service` and\n+ * `_serviceContext.version` respectively. If these are not set the \n+ * `_serviceContext` properties will be left at default unless the given runtime\n+ * configuration supplies any values as substitutes.\n+ * @memberof Configuration\n+ * @private\n+ * @function _checkLocalServiceContext\n+ * @returns {Undefined} - does not return anything\n+ */\n+Configuration.prototype._checkLocalServiceContext = function() {\n+  var gaeModuleName = env.GAE_MODULE_NAME;\n+  var gaeModuleVer = env.GAE_MODULE_VERSION;\n+  this._serviceContext.service = isString(gaeModuleName) ? gaeModuleName : '';\n+  this._serviceContext.version = isString(gaeModuleVer) ? gaeModuleVer : '';\n+  if (isPlainObject(this._givenConfiguration)\n+    && isPlainObject(this._givenConfiguration.serviceContext)) {\n+    if (isString(this._givenConfiguration.serviceContext.service)) {\n+      this._serviceContext.service = \n+        this._givenConfiguration.serviceContext.service;\n+    }\n+    if (isString(this._givenConfiguration.serviceContext.version)) {\n+      this._serviceContext.version =\n+        this._givenConfiguration.serviceContext.version;\n     }\n-  } else if (!isUndefined(initConfiguration)) {\n-\n-    throw new Error(\n-        'An invalid configuration was given to the Stackdriver Error Library');\n   }\n-\n-  if (isString(env.GCLOUD_PROJECT) || isNumber(env.GCLOUD_PROJECT)) {\n-    projectId = env.GCLOUD_PROJECT;\n+};\n+/**\n+ * Returns the _reportUncaughtExceptions property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getReportUncaughtExceptions\n+ * @returns {Boolean} - returns the _reportUncaughtExceptions property\n+ */\n+Configuration.prototype.getReportUncaughtExceptions = function() {\n+  return this._reportUncaughtExceptions;\n+};\n+/**\n+ * Returns the _shouldReportErrorsToAPI property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getShouldReportErrorsToAPI\n+ * @returns {Boolean} - returns the _shouldReportErrorsToAPI property\n+ */\n+Configuration.prototype.getShouldReportErrorsToAPI = function() {\n+  return this._shouldReportErrorsToAPI;\n+};\n+/**\n+ * Returns the _projectId property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getProjectId\n+ * @returns {String|Null} - returns the _projectId property\n+ */\n+Configuration.prototype.getProjectId = function() {\n+  return this._projectId;\n+};\n+/**\n+ * Returns the _projectNumber property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getProjectNumber\n+ * @returns {Number|Null} - returns the _projectNumber property\n+ */\n+Configuration.prototype.getProjectNumber = function() {\n+  return this._projectNumber;\n+};\n+/**\n+ * Returns the _key property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getKey\n+ * @returns {String|Null} - returns the _key property\n+ */\n+Configuration.prototype.getKey = function() {\n+  return this._key;\n+};\n+/**\n+ * Returns the _serviceContext property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getKey\n+ * @returns {Object|Null} - returns the _serviceContext property\n+ */\n+Configuration.prototype.getServiceContext = function() {\n+  return this._serviceContext;\n+};\n+/**\n+ * Returns the _version property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getVersion\n+ * @returns {String} - returns the _version property\n+ */\n+Configuration.prototype.getVersion = function() {\n+  return this._version;\n+};\n+/**\n+ * Returns the _initError property on the instance.\n+ * @memberof Configuration\n+ * @public\n+ * @function getError\n+ * @returns {Error|Null} - returns the _initError property\n+ */\n+Configuration.prototype.getError = function() {\n+  return this._initError;\n+};\n+/**\n+ * Returns a Boolean based upon whethe the Configuration instance communicated\n+ * with the metadata service and has a sufficient configuration.\n+ * @memberof Configuration\n+ * @public\n+ * @function isReady\n+ * @returns {Boolean} - returns true if instance initialized without error,\n+ *  otherwise false\n+ */\n+Configuration.prototype.isReady = function() {\n+  return this._initState && !this._initError;\n+};\n+/**\n+ * Returns a Boolean indicating whether or not the Configuration instance went\n+ * through the initialization process and encountered an error during that\n+ * process.\n+ * @memberof Configuration\n+ * @public\n+ * @function hasErrored\n+ * @returns {Boolean} - returns true if _initError is set as an Error, otherwise\n+ *  false\n+ */\n+Configuration.prototype.hasErrored = function() {\n+  return this._initError instanceof Error;\n+};\n+/**\n+ * The init function causes the Configuration instance to being the\n+ * initialization process of gathering configuration from both the metadata\n+ * service, runtime configuration and environment. This function will begin the\n+ * init process only once. The function will defer starting the initialization\n+ * process on any successive calls.\n+ * @memberof Configuration\n+ * @public\n+ * @function init\n+ * @chainable\n+ * @returns {this} - returns the instance for chaining\n+ */\n+Configuration.prototype.init = function () {\n+  if (this._startedInit === false) {\n+    /**\n+     * Attempt to get the project number from the metadata service\n+     * and kick-off the init process. This would be where we kick off the\n+     * `getProjectId` invocation but since that version is not live yet we use\n+     * project number for now.\n+     * @todo (cristiancavalli) - turn this into the getProjectId call when it\n+     *  goes live and remove the getProjectNumber function and call.\n+     * @example\n+     * utils.getProjectId(this._assimilateProjectId.bind(this));\n+     */\n+    this._checkLocalServiceContext();\n+    utils.getProjectNumber(this._assimilateProjectNumber.bind(this));\n+    this._startedInit = true;\n   }\n+  return this;\n+};\n+/**\n+ * The addReadyListener provides a defined and documented way to attach to the \n+ * `ready` event on a Configuration instance. This interface enforces event\n+ * `ready` event registration by using the `once` register handle since the \n+ * `ready` event is only ever supposed to emitted one time. This interface, at\n+ * invocation, will also check to see if the `ready` event has already been\n+ * emitted at which point it will immediately callback to listener.\n+ * @memberof Configuration\n+ * @public\n+ * @function addReadyListener\n+ * @chainable\n+ * @param {Configuration~readyCallback} callback - the listener to callback on\n+ * @returns {this} - returns the instance for chaining\n+ */\n+Configuration.prototype.addReadyListener = function(callback) {\n+  if (this._initState === false && isFunction(callback)) {\n+    // Enforce the one-time fire paradigm\n+    this.once('ready', callback);\n+  } else if (this._initState === true && isNull(this._initError)) {\n+    // Callback immediately as the event already happened\n+    callback(this);","path":"lib/configuration.js","position":null,"original_position":684,"commit_id":"363fc723dfadecbcb0435ea1d1a4ef856b4404d1","original_commit_id":"c77f686bbf0e26636bba6fd96480ee84840d2ed4","user":{"login":"cristiancavalli","id":4976075,"avatar_url":"https://avatars.githubusercontent.com/u/4976075?v=3","gravatar_id":"","url":"https://api.github.com/users/cristiancavalli","html_url":"https://github.com/cristiancavalli","followers_url":"https://api.github.com/users/cristiancavalli/followers","following_url":"https://api.github.com/users/cristiancavalli/following{/other_user}","gists_url":"https://api.github.com/users/cristiancavalli/gists{/gist_id}","starred_url":"https://api.github.com/users/cristiancavalli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cristiancavalli/subscriptions","organizations_url":"https://api.github.com/users/cristiancavalli/orgs","repos_url":"https://api.github.com/users/cristiancavalli/repos","events_url":"https://api.github.com/users/cristiancavalli/events{/privacy}","received_events_url":"https://api.github.com/users/cristiancavalli/received_events","type":"User","site_admin":false},"body":"✔️ ","created_at":"2016-07-29T20:44:52Z","updated_at":"2016-07-29T20:44:52Z","html_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36#discussion_r72857634","pull_request_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36","_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/comments/72857634"},"html":{"href":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36#discussion_r72857634"},"pull_request":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36"}}},"pull_request":{"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36","id":78450499,"html_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36","diff_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36.diff","patch_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36.patch","issue_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/36","number":36,"state":"open","locked":false,"title":"New configuration flow","user":{"login":"cristiancavalli","id":4976075,"avatar_url":"https://avatars.githubusercontent.com/u/4976075?v=3","gravatar_id":"","url":"https://api.github.com/users/cristiancavalli","html_url":"https://github.com/cristiancavalli","followers_url":"https://api.github.com/users/cristiancavalli/followers","following_url":"https://api.github.com/users/cristiancavalli/following{/other_user}","gists_url":"https://api.github.com/users/cristiancavalli/gists{/gist_id}","starred_url":"https://api.github.com/users/cristiancavalli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cristiancavalli/subscriptions","organizations_url":"https://api.github.com/users/cristiancavalli/orgs","repos_url":"https://api.github.com/users/cristiancavalli/repos","events_url":"https://api.github.com/users/cristiancavalli/events{/privacy}","received_events_url":"https://api.github.com/users/cristiancavalli/received_events","type":"User","site_admin":false},"body":"Add the new Configuration data-sink and add associated\r\ntests and documentation. Update linked interfaces and\r\nupdate tests for those interfaces.","created_at":"2016-07-22T09:41:32Z","updated_at":"2016-07-29T20:44:52Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2b8806cdb71c6801245949ad7785229f09d6fb93","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36/commits","review_comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36/comments","review_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/36/comments","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/statuses/363fc723dfadecbcb0435ea1d1a4ef856b4404d1","head":{"label":"cristiancavalli:new_configuration_flow","ref":"new_configuration_flow","sha":"363fc723dfadecbcb0435ea1d1a4ef856b4404d1","user":{"login":"cristiancavalli","id":4976075,"avatar_url":"https://avatars.githubusercontent.com/u/4976075?v=3","gravatar_id":"","url":"https://api.github.com/users/cristiancavalli","html_url":"https://github.com/cristiancavalli","followers_url":"https://api.github.com/users/cristiancavalli/followers","following_url":"https://api.github.com/users/cristiancavalli/following{/other_user}","gists_url":"https://api.github.com/users/cristiancavalli/gists{/gist_id}","starred_url":"https://api.github.com/users/cristiancavalli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cristiancavalli/subscriptions","organizations_url":"https://api.github.com/users/cristiancavalli/orgs","repos_url":"https://api.github.com/users/cristiancavalli/repos","events_url":"https://api.github.com/users/cristiancavalli/events{/privacy}","received_events_url":"https://api.github.com/users/cristiancavalli/received_events","type":"User","site_admin":false},"repo":{"id":63174673,"name":"cloud-errors-nodejs","full_name":"cristiancavalli/cloud-errors-nodejs","owner":{"login":"cristiancavalli","id":4976075,"avatar_url":"https://avatars.githubusercontent.com/u/4976075?v=3","gravatar_id":"","url":"https://api.github.com/users/cristiancavalli","html_url":"https://github.com/cristiancavalli","followers_url":"https://api.github.com/users/cristiancavalli/followers","following_url":"https://api.github.com/users/cristiancavalli/following{/other_user}","gists_url":"https://api.github.com/users/cristiancavalli/gists{/gist_id}","starred_url":"https://api.github.com/users/cristiancavalli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cristiancavalli/subscriptions","organizations_url":"https://api.github.com/users/cristiancavalli/orgs","repos_url":"https://api.github.com/users/cristiancavalli/repos","events_url":"https://api.github.com/users/cristiancavalli/events{/privacy}","received_events_url":"https://api.github.com/users/cristiancavalli/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/cristiancavalli/cloud-errors-nodejs","description":null,"fork":true,"url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs","forks_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/forks","keys_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/teams","hooks_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/hooks","issue_events_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/issues/events{/number}","events_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/events","assignees_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/assignees{/user}","branches_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/branches{/branch}","tags_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/tags","blobs_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/git/refs{/sha}","trees_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/statuses/{sha}","languages_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/languages","stargazers_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/stargazers","contributors_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/contributors","subscribers_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/subscribers","subscription_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/subscription","commits_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/commits{/sha}","git_commits_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/git/commits{/sha}","comments_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/comments{/number}","issue_comment_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/issues/comments{/number}","contents_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/contents/{+path}","compare_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/merges","archive_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/downloads","issues_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/issues{/number}","pulls_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/pulls{/number}","milestones_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/milestones{/number}","notifications_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/labels{/name}","releases_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/releases{/id}","deployments_url":"https://api.github.com/repos/cristiancavalli/cloud-errors-nodejs/deployments","created_at":"2016-07-12T16:33:47Z","updated_at":"2016-07-12T16:33:48Z","pushed_at":"2016-07-29T20:42:19Z","git_url":"git://github.com/cristiancavalli/cloud-errors-nodejs.git","ssh_url":"git@github.com:cristiancavalli/cloud-errors-nodejs.git","clone_url":"https://github.com/cristiancavalli/cloud-errors-nodejs.git","svn_url":"https://github.com/cristiancavalli/cloud-errors-nodejs","homepage":null,"size":249,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"GoogleCloudPlatform:master","ref":"master","sha":"cba91ddea91b34194455cefd250756dd37d75e23","user":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=3","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"repo":{"id":63088322,"name":"cloud-errors-nodejs","full_name":"GoogleCloudPlatform/cloud-errors-nodejs","owner":{"login":"GoogleCloudPlatform","id":2810941,"avatar_url":"https://avatars.githubusercontent.com/u/2810941?v=3","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs","description":"Node.js module for Google Stackdriver Error Reporting","fork":false,"url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs","forks_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/forks","keys_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/teams","hooks_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/hooks","issue_events_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/events{/number}","events_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/events","assignees_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/assignees{/user}","branches_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/branches{/branch}","tags_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/tags","blobs_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/git/refs{/sha}","trees_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/statuses/{sha}","languages_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/languages","stargazers_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/stargazers","contributors_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/contributors","subscribers_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/subscribers","subscription_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/subscription","commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/commits{/sha}","git_commits_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/git/commits{/sha}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/comments{/number}","issue_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/comments{/number}","contents_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/contents/{+path}","compare_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/merges","archive_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/downloads","issues_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues{/number}","pulls_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls{/number}","milestones_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/milestones{/number}","notifications_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/labels{/name}","releases_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/releases{/id}","deployments_url":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/deployments","created_at":"2016-07-11T17:32:41Z","updated_at":"2016-07-12T19:08:04Z","pushed_at":"2016-07-29T20:42:19Z","git_url":"git://github.com/GoogleCloudPlatform/cloud-errors-nodejs.git","ssh_url":"git@github.com:GoogleCloudPlatform/cloud-errors-nodejs.git","clone_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs.git","svn_url":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs","homepage":"https://cloud.google.com/error-reporting","size":267,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":5,"mirror_url":null,"open_issues_count":9,"forks":5,"open_issues":9,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36"},"html":{"href":"https://github.com/GoogleCloudPlatform/cloud-errors-nodejs/pull/36"},"issue":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/36"},"comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/issues/36/comments"},"review_comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36/comments"},"review_comment":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/pulls/36/commits"},"statuses":{"href":"https://api.github.com/repos/GoogleCloudPlatform/cloud-errors-nodejs/statuses/363fc723dfadecbcb0435ea1d1a4ef856b4404d1"}}}}
{"actor":{"display_login":"cristiancavalli"}}