From f1ced92a0cad59c803574518d324a0b6bb513e7f Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 24 Jun 2016 16:04:45 -0700 Subject: [PATCH 1/6] Add Cloud Functions Error Reporting sample. --- functions/errorreporting/index.js | 138 +++++++++++++++++++++++++ functions/errorreporting/package.json | 15 +++ functions/errorreporting/report.js | 139 ++++++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 functions/errorreporting/index.js create mode 100644 functions/errorreporting/package.json create mode 100644 functions/errorreporting/report.js diff --git a/functions/errorreporting/index.js b/functions/errorreporting/index.js new file mode 100644 index 0000000000..ab9a524512 --- /dev/null +++ b/functions/errorreporting/index.js @@ -0,0 +1,138 @@ +// Copyright 2016, Google, Inc. +// 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'; + +// [START setup] +var gcloud = require('gcloud'); + +// Get a reference to the StackDriver Logging component +var logging = gcloud.logging(); +// [END setup] + +var reporting = require('./report'); + +// [START helloSimpleErrorReport] +/** + * Report an error to StackDriver Error Reporting. Writes the minimum data + * required for the error to be picked up by StackDriver Error Reporting. + * + * @param {Error} err The Error object to report. + * @param {Function} callback Callback function. + */ +function reportError (err, callback) { + // This is the name of the StackDriver log stream that will receive the log + // entry. This name can be any valid log stream name, but must contain "err" + // in order for the error to be picked up by StackDriver Error Reporting. + var logName = 'error'; + var log = logging.log(logName); + + // https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource + var monitoredResource = { + type: 'cloud_function', + labels: { + function_name: process.env.FUNCTION_NAME + } + }; + + // https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent + var errorEvent = { + message: err.stack, + serviceContext: { + service: 'cloud_function:' + process.env.FUNCTION_NAME, + version: require('./package.json').version || 'unknown' + } + }; + + // Write the error log entry + log.write(log.entry(monitoredResource, errorEvent), callback); +} +// [END helloSimpleErrorReport] + +// [START helloSimpleError] +/** + * HTTP Cloud Function. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.helloSimpleError = function helloSimpleError (req, res) { + try { + if (req.method !== 'GET') { + var error = new Error('Only GET requests are accepted!'); + error.code = 405; + throw error; + } + // All is good, respond to the HTTP request + return res.send('Hello World!').end(); + } catch (err) { + // Report the error + return reportError(err, function () { + // Now respond to the HTTP request + res.status(error.code || 500).send(err.message); + }); + } +}; +// [END helloSimpleError] + +// [START helloHttpError] +/** + * HTTP Cloud Function. + * + * @param {Object} req Cloud Function request context. + * @param {Object} res Cloud Function response context. + */ +exports.helloHttpError = function helloHttpError (req, res) { + try { + if (req.method !== 'POST' && req.method !== 'GET') { + var error = new Error('Only POST and GET requests are accepted!'); + error.code = 405; + throw error; + } + // All is good, respond to the HTTP request + return res.send('Hello ' + (req.body.message || 'World') + '!').end(); + } catch (err) { + res.status(err.code || 500); + // Report the error + return reporting.reportHttpError(err, req, res, function () { + // Now respond to the HTTP request + res.send(err.message); + }); + } +}; +// [END helloHttpError] + +// [START helloBackgroundError] +/** + * Background Cloud Function. + * + * @param {Object} context Cloud Function context. + * @param {Object} data Request data, provided by a trigger. + * @param {string} data.message Message, provided by the trigger. + */ +exports.helloBackgroundError = function helloBackgroundError (context, data) { + try { + if (!data.message) { + throw new Error('"message" is required!'); + } + // All is good, respond with a message + return context.success('Hello World!'); + } catch (err) { + // Report the error + return reporting.reportError(err, function () { + // Now finish mark the execution failure + context.failure(err.message); + }); + } +}; +// [END helloBackgroundError] diff --git a/functions/errorreporting/package.json b/functions/errorreporting/package.json new file mode 100644 index 0000000000..4fc19844cf --- /dev/null +++ b/functions/errorreporting/package.json @@ -0,0 +1,15 @@ +{ + "name": "nodejs-docs-samples-functions-errorreporting", + "description": "Node.js samples found on https://cloud.google.com", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "dependencies": { + "gcloud": "^0.36.0" + } +} diff --git a/functions/errorreporting/report.js b/functions/errorreporting/report.js new file mode 100644 index 0000000000..f536c56eff --- /dev/null +++ b/functions/errorreporting/report.js @@ -0,0 +1,139 @@ +// Copyright 2016, Google, Inc. +// 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 gcloud = require('gcloud'); +var logging = gcloud.logging(); + +function getVersion (options) { + try { + if (options.version === undefined) { + var pkg = require('./package.json'); + options.version = pkg.version; + } + } catch (err) {} + if (options.version === undefined) { + options.version = 'unknown'; + } +} + +function getRequest (options) { + if (options.req && options.req) { + var req = options.req; + return { + method: req.method, + url: req.originalUrl, + userAgent: typeof req.get === 'function' ? req.get('user-agent') : 'unknown', + referrer: '', + responseStatusCode: options.res.statusCode, + remoteIp: req.ip + }; + } +} + +function report (options, callback) { + options || (options = {}); + options.err || (options.err = {}); + options.req || (options.req = {}); + + var FUNCTION_NAME = process.env.FUNCTION_NAME; + var FUNCTION_TRIGGER_TYPE = process.env.FUNCTION_TRIGGER_TYPE; + var ENTRY_POINT = process.env.ENTRY_POINT; + + getVersion(options); + + var log = logging.log('errors'); + + // MonitoredResource + // See https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource + var resource = { + // MonitoredResource.type + type: 'cloud_function', + // MonitoredResource.labels + labels: { + function_name: FUNCTION_NAME + } + }; + if (typeof options.region === 'string') { + resource.labels.region = options.region; + } + if (typeof options.projectId === 'string') { + resource.labels.projectId = options.projectId; + } + + // ErrorEvent + // See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent + var context = {}; + if (typeof options.user === 'string') { + // ErrorEvent.context.user + context.user = options.user; + } + if (FUNCTION_TRIGGER_TYPE === 'HTTP_TRIGGER') { + // ErrorEvent.context.httpRequest + context.httpRequest = getRequest(options); + } + if (!(options.err instanceof Error)) { + // ErrorEvent.context.reportLocation + context.reportLocation = { + filePath: typeof options.filePath === 'string' ? options.filePath : 'unknown', + lineNumber: typeof options.lineNumber === 'number' ? options.lineNumber : 0, + functionName: ENTRY_POINT + }; + } + + var structPayload = { + // ErrorEvent.serviceContext + serviceContext: { + // ErrorEvent.serviceContext.service + service: 'cloud_function:' + FUNCTION_NAME, + // ErrorEvent.serviceContext.version + version: '' + options.version + }, + // ErrorEvent.context + context: context + }; + + // ErrorEvent.message + if (options.err instanceof Error && typeof options.err.stack === 'string') { + structPayload.message = options.err.stack; + } else if (typeof options.err === 'string') { + structPayload.message = options.err; + } else if (typeof options.err.message === 'string') { + structPayload.message = options.err.message; + } + + log.write(log.entry(resource, structPayload), callback); +} + +exports.report = report; + +exports.reportError = function reportError (err, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + options.err = err; + report(options, callback); +}; + +exports.reportHttpError = function reportHttpError (err, req, res, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + options.err = err; + options.req = req; + options.res = res; + report(options, callback); +}; From c0b092a11a5f776b7ec787f7d61fafc15813f164 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 24 Jun 2016 16:22:07 -0700 Subject: [PATCH 2/6] Couple of tweaks. --- functions/errorreporting/index.js | 4 +++- functions/errorreporting/report.js | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/functions/errorreporting/index.js b/functions/errorreporting/index.js index ab9a524512..75f0353946 100644 --- a/functions/errorreporting/index.js +++ b/functions/errorreporting/index.js @@ -20,7 +20,9 @@ var gcloud = require('gcloud'); var logging = gcloud.logging(); // [END setup] +// [START reporting] var reporting = require('./report'); +// [END reporting] // [START helloSimpleErrorReport] /** @@ -34,7 +36,7 @@ function reportError (err, callback) { // This is the name of the StackDriver log stream that will receive the log // entry. This name can be any valid log stream name, but must contain "err" // in order for the error to be picked up by StackDriver Error Reporting. - var logName = 'error'; + var logName = 'errors'; var log = logging.log(logName); // https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource diff --git a/functions/errorreporting/report.js b/functions/errorreporting/report.js index f536c56eff..303abc7399 100644 --- a/functions/errorreporting/report.js +++ b/functions/errorreporting/report.js @@ -72,8 +72,6 @@ function report (options, callback) { resource.labels.projectId = options.projectId; } - // ErrorEvent - // See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent var context = {}; if (typeof options.user === 'string') { // ErrorEvent.context.user @@ -88,10 +86,12 @@ function report (options, callback) { context.reportLocation = { filePath: typeof options.filePath === 'string' ? options.filePath : 'unknown', lineNumber: typeof options.lineNumber === 'number' ? options.lineNumber : 0, - functionName: ENTRY_POINT + functionName: typeof options.functionName === 'string' ? options.functionName : 'unknown' }; } + // ErrorEvent + // See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent var structPayload = { // ErrorEvent.serviceContext serviceContext: { From a3d2f5404068b23e1408b978fce8727dba495d14 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Mon, 27 Jun 2016 10:19:03 -0700 Subject: [PATCH 3/6] Tweak error reporting sample code. --- functions/errorreporting/index.js | 11 +-- functions/errorreporting/report.js | 110 +++++++++++++---------------- functions/log/index.js | 26 ++++++- 3 files changed, 81 insertions(+), 66 deletions(-) diff --git a/functions/errorreporting/index.js b/functions/errorreporting/index.js index 75f0353946..de308701ce 100644 --- a/functions/errorreporting/index.js +++ b/functions/errorreporting/index.js @@ -20,9 +20,9 @@ var gcloud = require('gcloud'); var logging = gcloud.logging(); // [END setup] -// [START reporting] -var reporting = require('./report'); -// [END reporting] +// [START reportDetailedError] +var reportDetailedError = require('./report'); +// [END reportDetailedError] // [START helloSimpleErrorReport] /** @@ -104,9 +104,10 @@ exports.helloHttpError = function helloHttpError (req, res) { // All is good, respond to the HTTP request return res.send('Hello ' + (req.body.message || 'World') + '!').end(); } catch (err) { + // Set the response status code before reporting the error res.status(err.code || 500); // Report the error - return reporting.reportHttpError(err, req, res, function () { + return reportDetailedError(err, req, res, function () { // Now respond to the HTTP request res.send(err.message); }); @@ -131,7 +132,7 @@ exports.helloBackgroundError = function helloBackgroundError (context, data) { return context.success('Hello World!'); } catch (err) { // Report the error - return reporting.reportError(err, function () { + return reportDetailedError(err, function () { // Now finish mark the execution failure context.failure(err.message); }); diff --git a/functions/errorreporting/report.js b/functions/errorreporting/report.js index 303abc7399..eef3e6aae6 100644 --- a/functions/errorreporting/report.js +++ b/functions/errorreporting/report.js @@ -16,43 +16,33 @@ var gcloud = require('gcloud'); var logging = gcloud.logging(); -function getVersion (options) { - try { - if (options.version === undefined) { - var pkg = require('./package.json'); - options.version = pkg.version; - } - } catch (err) {} - if (options.version === undefined) { - options.version = 'unknown'; - } -} - -function getRequest (options) { - if (options.req && options.req) { - var req = options.req; - return { - method: req.method, - url: req.originalUrl, - userAgent: typeof req.get === 'function' ? req.get('user-agent') : 'unknown', - referrer: '', - responseStatusCode: options.res.statusCode, - remoteIp: req.ip - }; +// [START helloHttpError] +/** + * Report an error to StackDriver Error Reporting. Writes up to the maximum data + * accepted by StackDriver Error Reporting. + * + * @param {Error} err The Error object to report. + * @param {Object} [req] Request context, if any. + * @param {Object} [res] Response context, if any. + * @param {Object} [options] Additional context, if any. + * @param {Function} callback Callback function. + */ +function reportDetailedError (err, req, res, options, callback) { + if (typeof req === 'function') { + callback = req; + req = null; + res = null; + options = {}; + } else if (typeof options === 'function') { + callback = options; + options = {}; } -} - -function report (options, callback) { options || (options = {}); - options.err || (options.err = {}); - options.req || (options.req = {}); var FUNCTION_NAME = process.env.FUNCTION_NAME; var FUNCTION_TRIGGER_TYPE = process.env.FUNCTION_TRIGGER_TYPE; var ENTRY_POINT = process.env.ENTRY_POINT; - getVersion(options); - var log = logging.log('errors'); // MonitoredResource @@ -77,11 +67,20 @@ function report (options, callback) { // ErrorEvent.context.user context.user = options.user; } - if (FUNCTION_TRIGGER_TYPE === 'HTTP_TRIGGER') { + if (FUNCTION_TRIGGER_TYPE === 'HTTP_TRIGGER' && req && res) { // ErrorEvent.context.httpRequest - context.httpRequest = getRequest(options); + context.httpRequest = { + method: req.method, + url: req.originalUrl, + userAgent: typeof req.get === 'function' ? req.get('user-agent') : 'unknown', + referrer: '', + remoteIp: req.ip + }; + if (typeof res.statusCode === 'number') { + context.httpRequest.responseStatusCode = res.statusCode; + } } - if (!(options.err instanceof Error)) { + if (!(err instanceof Error) || typeof err.stack !== 'string') { // ErrorEvent.context.reportLocation context.reportLocation = { filePath: typeof options.filePath === 'string' ? options.filePath : 'unknown', @@ -90,6 +89,16 @@ function report (options, callback) { }; } + try { + if (options.version === undefined) { + var pkg = require('./package.json'); + options.version = pkg.version; + } + } catch (err) {} + if (options.version === undefined) { + options.version = 'unknown'; + } + // ErrorEvent // See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent var structPayload = { @@ -105,35 +114,16 @@ function report (options, callback) { }; // ErrorEvent.message - if (options.err instanceof Error && typeof options.err.stack === 'string') { - structPayload.message = options.err.stack; - } else if (typeof options.err === 'string') { - structPayload.message = options.err; - } else if (typeof options.err.message === 'string') { - structPayload.message = options.err.message; + if (err instanceof Error && typeof err.stack === 'string') { + structPayload.message = err.stack; + } else if (typeof err === 'string') { + structPayload.message = err; + } else if (typeof err.message === 'string') { + structPayload.message = err.message; } log.write(log.entry(resource, structPayload), callback); } +// [END helloHttpError] -exports.report = report; - -exports.reportError = function reportError (err, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - options.err = err; - report(options, callback); -}; - -exports.reportHttpError = function reportHttpError (err, req, res, options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - options.err = err; - options.req = req; - options.res = res; - report(options, callback); -}; +module.exports = reportDetailedError; diff --git a/functions/log/index.js b/functions/log/index.js index 8d5cd55a12..bdb7ae8b98 100644 --- a/functions/log/index.js +++ b/functions/log/index.js @@ -14,8 +14,32 @@ 'use strict'; // [START log] -exports.helloWorld = function (context, data) { +exports.helloWorld = function helloWorld (context, data) { console.log('I am a log entry!'); context.success(); }; // [END log] + +exports.retrieve = function retrieve () { + // [START retrieve] + // By default, gcloud will authenticate using the service account file specified + // by the GOOGLE_APPLICATION_CREDENTIALS environment variable and use the + // project specified by the GCLOUD_PROJECT environment variable. See + // https://googlecloudplatform.github.io/gcloud-node/#/docs/guides/authentication + var gcloud = require('gcloud'); + var logging = gcloud.logging(); + + // Retrieve the latest Cloud Function log entries + // See https://googlecloudplatform.github.io/gcloud-node/#/docs/logging + logging.getEntries({ + pageSize: 10, + filter: 'resource.type="cloud_function"' + }, function (err, entries) { + if (err) { + console.error(err); + } else { + console.log(entries); + } + }); + // [END retrieve] +} From 353a6214719e86ff0ea24165d910bcef14a20204 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 28 Jun 2016 10:02:10 -0700 Subject: [PATCH 4/6] Tweak samples. --- functions/errorreporting/index.js | 20 ++++++------ functions/errorreporting/report.js | 5 +-- functions/log/index.js | 50 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/functions/errorreporting/index.js b/functions/errorreporting/index.js index de308701ce..b16330bb2b 100644 --- a/functions/errorreporting/index.js +++ b/functions/errorreporting/index.js @@ -65,8 +65,8 @@ function reportError (err, callback) { /** * HTTP Cloud Function. * - * @param {Object} req Cloud Function request context. - * @param {Object} res Cloud Function response context. + * @param {Object} req Cloud Function request object. + * @param {Object} res Cloud Function response object. */ exports.helloSimpleError = function helloSimpleError (req, res) { try { @@ -76,12 +76,12 @@ exports.helloSimpleError = function helloSimpleError (req, res) { throw error; } // All is good, respond to the HTTP request - return res.send('Hello World!').end(); + return res.send('Hello World!'); } catch (err) { // Report the error return reportError(err, function () { // Now respond to the HTTP request - res.status(error.code || 500).send(err.message); + return res.status(error.code || 500).send(err.message); }); } }; @@ -91,8 +91,8 @@ exports.helloSimpleError = function helloSimpleError (req, res) { /** * HTTP Cloud Function. * - * @param {Object} req Cloud Function request context. - * @param {Object} res Cloud Function response context. + * @param {Object} req Cloud Function request object. + * @param {Object} res Cloud Function response object. */ exports.helloHttpError = function helloHttpError (req, res) { try { @@ -102,14 +102,14 @@ exports.helloHttpError = function helloHttpError (req, res) { throw error; } // All is good, respond to the HTTP request - return res.send('Hello ' + (req.body.message || 'World') + '!').end(); + return res.send('Hello ' + (req.body.message || 'World') + '!'); } catch (err) { // Set the response status code before reporting the error res.status(err.code || 500); // Report the error return reportDetailedError(err, req, res, function () { // Now respond to the HTTP request - res.send(err.message); + return res.send(err.message); }); } }; @@ -119,7 +119,7 @@ exports.helloHttpError = function helloHttpError (req, res) { /** * Background Cloud Function. * - * @param {Object} context Cloud Function context. + * @param {Object} context Cloud Function context object. * @param {Object} data Request data, provided by a trigger. * @param {string} data.message Message, provided by the trigger. */ @@ -134,7 +134,7 @@ exports.helloBackgroundError = function helloBackgroundError (context, data) { // Report the error return reportDetailedError(err, function () { // Now finish mark the execution failure - context.failure(err.message); + return context.failure(err.message); }); } }; diff --git a/functions/errorreporting/report.js b/functions/errorreporting/report.js index eef3e6aae6..ae8557b1c3 100644 --- a/functions/errorreporting/report.js +++ b/functions/errorreporting/report.js @@ -40,9 +40,6 @@ function reportDetailedError (err, req, res, options, callback) { options || (options = {}); var FUNCTION_NAME = process.env.FUNCTION_NAME; - var FUNCTION_TRIGGER_TYPE = process.env.FUNCTION_TRIGGER_TYPE; - var ENTRY_POINT = process.env.ENTRY_POINT; - var log = logging.log('errors'); // MonitoredResource @@ -67,7 +64,7 @@ function reportDetailedError (err, req, res, options, callback) { // ErrorEvent.context.user context.user = options.user; } - if (FUNCTION_TRIGGER_TYPE === 'HTTP_TRIGGER' && req && res) { + if (req && res) { // ErrorEvent.context.httpRequest context.httpRequest = { method: req.method, diff --git a/functions/log/index.js b/functions/log/index.js index bdb7ae8b98..94ada788a3 100644 --- a/functions/log/index.js +++ b/functions/log/index.js @@ -43,3 +43,53 @@ exports.retrieve = function retrieve () { }); // [END retrieve] } + +exports.getMetrics = function getMetrics () { + // [START getMetrics] + var google = require('googleapis'); + var monitoring = google.monitoring('v3'); + + google.auth.getApplicationDefault(function(err, authClient) { + if (err) { + return console.log('Authentication failed because of ', err); + } + if (authClient.createScopedRequired && authClient.createScopedRequired()) { + var scopes = [ + 'https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/monitoring', + 'https://www.googleapis.com/auth/monitoring.read', + 'https://www.googleapis.com/auth/monitoring.write' + ]; + authClient = authClient.createScoped(scopes); + } + + // Format a date according to RFC33339 with milliseconds format + function formatDate (date) { + return JSON.parse(JSON.stringify(date).replace('Z', '000Z')); + } + + // Create two datestrings, a start and end range + var oneWeekAgo = new Date(); + var now = new Date() + oneWeekAgo.setHours(oneWeekAgo.getHours() - (7 * 24)); + oneWeekAgo = formatDate(oneWeekAgo); + now = formatDate(now); + + monitoring.projects.timeSeries.list({ + auth: authClient, + // There is also cloudfunctions.googleapis.com/function/execution_count + filter: 'metric.type="cloudfunctions.googleapis.com/function/execution_times"', + pageSize: 10, + 'interval.startTime': oneWeekAgo, + 'interval.endTime': now, + name: 'projects/' + process.env.GCLOUD_PROJECT + }, function(err, results) { + if (err) { + console.log(err); + } else { + console.log(results.timeSeries); + } + }); + }); + // [END getMetrics] +} From d13809399c8530a5046231b98a0c05b8f1b677df Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 28 Jun 2016 10:33:26 -0700 Subject: [PATCH 5/6] Added tests for latest GCF samples. --- functions/log/index.js | 14 ++--- test/functions/log.test.js | 104 +++++++++++++++++++++++++++++++++++-- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/functions/log/index.js b/functions/log/index.js index 94ada788a3..54c452661c 100644 --- a/functions/log/index.js +++ b/functions/log/index.js @@ -42,16 +42,16 @@ exports.retrieve = function retrieve () { } }); // [END retrieve] -} +}; exports.getMetrics = function getMetrics () { // [START getMetrics] var google = require('googleapis'); var monitoring = google.monitoring('v3'); - google.auth.getApplicationDefault(function(err, authClient) { + google.auth.getApplicationDefault(function (err, authClient) { if (err) { - return console.log('Authentication failed because of ', err); + return console.error('Authentication failed', err); } if (authClient.createScopedRequired && authClient.createScopedRequired()) { var scopes = [ @@ -70,7 +70,7 @@ exports.getMetrics = function getMetrics () { // Create two datestrings, a start and end range var oneWeekAgo = new Date(); - var now = new Date() + var now = new Date(); oneWeekAgo.setHours(oneWeekAgo.getHours() - (7 * 24)); oneWeekAgo = formatDate(oneWeekAgo); now = formatDate(now); @@ -83,13 +83,13 @@ exports.getMetrics = function getMetrics () { 'interval.startTime': oneWeekAgo, 'interval.endTime': now, name: 'projects/' + process.env.GCLOUD_PROJECT - }, function(err, results) { + }, function (err, results) { if (err) { - console.log(err); + console.error(err); } else { console.log(results.timeSeries); } }); }); // [END getMetrics] -} +}; diff --git a/test/functions/log.test.js b/test/functions/log.test.js index 09d127410b..28cd3c5b23 100644 --- a/test/functions/log.test.js +++ b/test/functions/log.test.js @@ -15,20 +15,114 @@ var test = require('ava'); var sinon = require('sinon'); -var logSample = require('../../functions/log'); +var proxyquire = require('proxyquire').noCallThru(); + +var authClient = {}; + +function getSample () { + var auth = { + getApplicationDefault: sinon.stub().callsArgWith(0, null, authClient) + }; + var monitoring = { + projects: { + timeSeries: { + list: sinon.stub().callsArgWith(1, null, { + timeSeries: 'series' + }) + } + } + }; + var logging = { + getEntries: sinon.stub().callsArgWith(1, null, 'entries') + }; + return { + sample: proxyquire('../../functions/log', { + googleapis: { + auth: auth, + monitoring: sinon.stub().returns(monitoring) + }, + gcloud: { + logging: sinon.stub().returns(logging) + } + }), + mocks: { + auth: auth, + monitoring: monitoring, + logging: logging + } + }; +} + +test.before(function () { + sinon.stub(console, 'error'); + sinon.stub(console, 'log'); +}); test('should write to log', function (t) { var expectedMsg = 'I am a log entry!'; - sinon.spy(console, 'log'); - - logSample.helloWorld({ + getSample().sample.helloWorld({ success: function (result) { t.is(result, undefined); - t.is(console.log.calledOnce, true); + t.is(console.log.called, true); t.is(console.log.calledWith(expectedMsg), true); }, failure: t.fail }); +}); + +test('retrieve: should retrieve logs', function (t) { + var logSample = getSample(); + logSample.sample.retrieve(); + t.is(console.log.calledWith('entries'), true); +}); + +test('retrieve: handles error', function (t) { + var expectedMsg = 'entries error'; + var logSample = getSample(); + logSample.mocks.logging.getEntries = sinon.stub().callsArgWith(1, expectedMsg); + logSample.sample.retrieve(); + t.is(console.error.calledWith(expectedMsg), true); +}); + +test('getMetrics: should retrieve metrics', function (t) { + var logSample = getSample(); + logSample.sample.getMetrics(); + t.is(console.log.calledWith('series'), true); +}); + +test('getMetrics: creates with scope', function (t) { + var authClient = { + createScopedRequired: sinon.stub().returns(true), + createScoped: sinon.stub().returns('foo') + }; + var logSample = getSample(); + logSample.mocks.auth.getApplicationDefault = sinon.stub().callsArgWith(0, null, authClient); + logSample.sample.getMetrics(); + t.deepEqual(authClient.createScoped.firstCall.args[0], [ + 'https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/monitoring', + 'https://www.googleapis.com/auth/monitoring.read', + 'https://www.googleapis.com/auth/monitoring.write' + ]); +}); + +test('getMetrics: handles auth error', function (t) { + var expectedMsg = 'auth error'; + var logSample = getSample(); + logSample.mocks.auth.getApplicationDefault = sinon.stub().callsArgWith(0, expectedMsg); + logSample.sample.getMetrics(); + t.is(console.error.calledWith('Authentication failed', expectedMsg), true); +}); + +test('getMetrics: handles time series error', function (t) { + var expectedMsg = 'time series error'; + var logSample = getSample(); + logSample.mocks.monitoring.projects.timeSeries.list = sinon.stub().callsArgWith(1, expectedMsg); + logSample.sample.getMetrics(); + t.is(console.error.calledWith(expectedMsg), true); +}); +test.after(function () { + console.error.restore(); console.log.restore(); }); From abe6d4b55c207977584066edf4df16d7b19a7895 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 28 Jun 2016 10:38:56 -0700 Subject: [PATCH 6/6] Update readme --- functions/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/README.md b/functions/README.md index 66082ff78d..97d5b1cf16 100644 --- a/functions/README.md +++ b/functions/README.md @@ -26,8 +26,9 @@ environment. * [Cloud Datastore](datastore/) * [Cloud Pub/Sub](pubsub/) * [Dependencies](uuid/) +* [Error Reporting](errorreporting/) * [HTTP](http/) -* [Logging](log/) +* [Logging & Monitoring](log/) * [Modules](module/) * [OCR (Optical Character Recognition)](ocr/) * [SendGrid](sendgrid/)