Skip to content

Commit

Permalink
Added Cloud Functions Error Reporting sample (#140)
Browse files Browse the repository at this point in the history
* Add Cloud Functions Error Reporting sample.

* Couple of tweaks.

* Tweak error reporting sample code.

* Tweak samples.

* Added tests for latest GCF samples.

* Update readme
  • Loading branch information
jmdobry committed Jun 28, 2016
1 parent 00d918c commit 8152610
Show file tree
Hide file tree
Showing 6 changed files with 458 additions and 7 deletions.
3 changes: 2 additions & 1 deletion functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
141 changes: 141 additions & 0 deletions functions/errorreporting/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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]

// [START reportDetailedError]
var reportDetailedError = require('./report');
// [END reportDetailedError]

// [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 = 'errors';
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 object.
* @param {Object} res Cloud Function response object.
*/
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!');
} catch (err) {
// Report the error
return reportError(err, function () {
// Now respond to the HTTP request
return res.status(error.code || 500).send(err.message);
});
}
};
// [END helloSimpleError]

// [START helloHttpError]
/**
* HTTP Cloud Function.
*
* @param {Object} req Cloud Function request object.
* @param {Object} res Cloud Function response object.
*/
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') + '!');
} 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
return res.send(err.message);
});
}
};
// [END helloHttpError]

// [START helloBackgroundError]
/**
* Background Cloud Function.
*
* @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.
*/
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 reportDetailedError(err, function () {
// Now finish mark the execution failure
return context.failure(err.message);
});
}
};
// [END helloBackgroundError]
15 changes: 15 additions & 0 deletions functions/errorreporting/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
126 changes: 126 additions & 0 deletions functions/errorreporting/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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();

// [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 = {};
}
options || (options = {});

var FUNCTION_NAME = process.env.FUNCTION_NAME;
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;
}

var context = {};
if (typeof options.user === 'string') {
// ErrorEvent.context.user
context.user = options.user;
}
if (req && res) {
// ErrorEvent.context.httpRequest
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 (!(err instanceof Error) || typeof err.stack !== 'string') {
// ErrorEvent.context.reportLocation
context.reportLocation = {
filePath: typeof options.filePath === 'string' ? options.filePath : 'unknown',
lineNumber: typeof options.lineNumber === 'number' ? options.lineNumber : 0,
functionName: typeof options.functionName === 'string' ? options.functionName : 'unknown'
};
}

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 = {
// ErrorEvent.serviceContext
serviceContext: {
// ErrorEvent.serviceContext.service
service: 'cloud_function:' + FUNCTION_NAME,
// ErrorEvent.serviceContext.version
version: '' + options.version
},
// ErrorEvent.context
context: context
};

// ErrorEvent.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]

module.exports = reportDetailedError;
76 changes: 75 additions & 1 deletion functions/log/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,82 @@
'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]
};

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.error('Authentication failed', 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.error(err);
} else {
console.log(results.timeSeries);
}
});
});
// [END getMetrics]
};
Loading

0 comments on commit 8152610

Please sign in to comment.