Skip to content

Commit 8152610

Browse files
authored
Added Cloud Functions Error Reporting sample (#140)
* Add Cloud Functions Error Reporting sample. * Couple of tweaks. * Tweak error reporting sample code. * Tweak samples. * Added tests for latest GCF samples. * Update readme
1 parent 00d918c commit 8152610

File tree

6 files changed

+458
-7
lines changed

6 files changed

+458
-7
lines changed

functions/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ environment.
2626
* [Cloud Datastore](datastore/)
2727
* [Cloud Pub/Sub](pubsub/)
2828
* [Dependencies](uuid/)
29+
* [Error Reporting](errorreporting/)
2930
* [HTTP](http/)
30-
* [Logging](log/)
31+
* [Logging & Monitoring](log/)
3132
* [Modules](module/)
3233
* [OCR (Optical Character Recognition)](ocr/)
3334
* [SendGrid](sendgrid/)

functions/errorreporting/index.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright 2016, Google, Inc.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
'use strict';
15+
16+
// [START setup]
17+
var gcloud = require('gcloud');
18+
19+
// Get a reference to the StackDriver Logging component
20+
var logging = gcloud.logging();
21+
// [END setup]
22+
23+
// [START reportDetailedError]
24+
var reportDetailedError = require('./report');
25+
// [END reportDetailedError]
26+
27+
// [START helloSimpleErrorReport]
28+
/**
29+
* Report an error to StackDriver Error Reporting. Writes the minimum data
30+
* required for the error to be picked up by StackDriver Error Reporting.
31+
*
32+
* @param {Error} err The Error object to report.
33+
* @param {Function} callback Callback function.
34+
*/
35+
function reportError (err, callback) {
36+
// This is the name of the StackDriver log stream that will receive the log
37+
// entry. This name can be any valid log stream name, but must contain "err"
38+
// in order for the error to be picked up by StackDriver Error Reporting.
39+
var logName = 'errors';
40+
var log = logging.log(logName);
41+
42+
// https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
43+
var monitoredResource = {
44+
type: 'cloud_function',
45+
labels: {
46+
function_name: process.env.FUNCTION_NAME
47+
}
48+
};
49+
50+
// https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
51+
var errorEvent = {
52+
message: err.stack,
53+
serviceContext: {
54+
service: 'cloud_function:' + process.env.FUNCTION_NAME,
55+
version: require('./package.json').version || 'unknown'
56+
}
57+
};
58+
59+
// Write the error log entry
60+
log.write(log.entry(monitoredResource, errorEvent), callback);
61+
}
62+
// [END helloSimpleErrorReport]
63+
64+
// [START helloSimpleError]
65+
/**
66+
* HTTP Cloud Function.
67+
*
68+
* @param {Object} req Cloud Function request object.
69+
* @param {Object} res Cloud Function response object.
70+
*/
71+
exports.helloSimpleError = function helloSimpleError (req, res) {
72+
try {
73+
if (req.method !== 'GET') {
74+
var error = new Error('Only GET requests are accepted!');
75+
error.code = 405;
76+
throw error;
77+
}
78+
// All is good, respond to the HTTP request
79+
return res.send('Hello World!');
80+
} catch (err) {
81+
// Report the error
82+
return reportError(err, function () {
83+
// Now respond to the HTTP request
84+
return res.status(error.code || 500).send(err.message);
85+
});
86+
}
87+
};
88+
// [END helloSimpleError]
89+
90+
// [START helloHttpError]
91+
/**
92+
* HTTP Cloud Function.
93+
*
94+
* @param {Object} req Cloud Function request object.
95+
* @param {Object} res Cloud Function response object.
96+
*/
97+
exports.helloHttpError = function helloHttpError (req, res) {
98+
try {
99+
if (req.method !== 'POST' && req.method !== 'GET') {
100+
var error = new Error('Only POST and GET requests are accepted!');
101+
error.code = 405;
102+
throw error;
103+
}
104+
// All is good, respond to the HTTP request
105+
return res.send('Hello ' + (req.body.message || 'World') + '!');
106+
} catch (err) {
107+
// Set the response status code before reporting the error
108+
res.status(err.code || 500);
109+
// Report the error
110+
return reportDetailedError(err, req, res, function () {
111+
// Now respond to the HTTP request
112+
return res.send(err.message);
113+
});
114+
}
115+
};
116+
// [END helloHttpError]
117+
118+
// [START helloBackgroundError]
119+
/**
120+
* Background Cloud Function.
121+
*
122+
* @param {Object} context Cloud Function context object.
123+
* @param {Object} data Request data, provided by a trigger.
124+
* @param {string} data.message Message, provided by the trigger.
125+
*/
126+
exports.helloBackgroundError = function helloBackgroundError (context, data) {
127+
try {
128+
if (!data.message) {
129+
throw new Error('"message" is required!');
130+
}
131+
// All is good, respond with a message
132+
return context.success('Hello World!');
133+
} catch (err) {
134+
// Report the error
135+
return reportDetailedError(err, function () {
136+
// Now finish mark the execution failure
137+
return context.failure(err.message);
138+
});
139+
}
140+
};
141+
// [END helloBackgroundError]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "nodejs-docs-samples-functions-errorreporting",
3+
"description": "Node.js samples found on https://cloud.google.com",
4+
"version": "0.0.1",
5+
"private": true,
6+
"license": "Apache Version 2.0",
7+
"author": "Google Inc.",
8+
"repository": {
9+
"type": "git",
10+
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
11+
},
12+
"dependencies": {
13+
"gcloud": "^0.36.0"
14+
}
15+
}

functions/errorreporting/report.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2016, Google, Inc.
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
'use strict';
15+
16+
var gcloud = require('gcloud');
17+
var logging = gcloud.logging();
18+
19+
// [START helloHttpError]
20+
/**
21+
* Report an error to StackDriver Error Reporting. Writes up to the maximum data
22+
* accepted by StackDriver Error Reporting.
23+
*
24+
* @param {Error} err The Error object to report.
25+
* @param {Object} [req] Request context, if any.
26+
* @param {Object} [res] Response context, if any.
27+
* @param {Object} [options] Additional context, if any.
28+
* @param {Function} callback Callback function.
29+
*/
30+
function reportDetailedError (err, req, res, options, callback) {
31+
if (typeof req === 'function') {
32+
callback = req;
33+
req = null;
34+
res = null;
35+
options = {};
36+
} else if (typeof options === 'function') {
37+
callback = options;
38+
options = {};
39+
}
40+
options || (options = {});
41+
42+
var FUNCTION_NAME = process.env.FUNCTION_NAME;
43+
var log = logging.log('errors');
44+
45+
// MonitoredResource
46+
// See https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
47+
var resource = {
48+
// MonitoredResource.type
49+
type: 'cloud_function',
50+
// MonitoredResource.labels
51+
labels: {
52+
function_name: FUNCTION_NAME
53+
}
54+
};
55+
if (typeof options.region === 'string') {
56+
resource.labels.region = options.region;
57+
}
58+
if (typeof options.projectId === 'string') {
59+
resource.labels.projectId = options.projectId;
60+
}
61+
62+
var context = {};
63+
if (typeof options.user === 'string') {
64+
// ErrorEvent.context.user
65+
context.user = options.user;
66+
}
67+
if (req && res) {
68+
// ErrorEvent.context.httpRequest
69+
context.httpRequest = {
70+
method: req.method,
71+
url: req.originalUrl,
72+
userAgent: typeof req.get === 'function' ? req.get('user-agent') : 'unknown',
73+
referrer: '',
74+
remoteIp: req.ip
75+
};
76+
if (typeof res.statusCode === 'number') {
77+
context.httpRequest.responseStatusCode = res.statusCode;
78+
}
79+
}
80+
if (!(err instanceof Error) || typeof err.stack !== 'string') {
81+
// ErrorEvent.context.reportLocation
82+
context.reportLocation = {
83+
filePath: typeof options.filePath === 'string' ? options.filePath : 'unknown',
84+
lineNumber: typeof options.lineNumber === 'number' ? options.lineNumber : 0,
85+
functionName: typeof options.functionName === 'string' ? options.functionName : 'unknown'
86+
};
87+
}
88+
89+
try {
90+
if (options.version === undefined) {
91+
var pkg = require('./package.json');
92+
options.version = pkg.version;
93+
}
94+
} catch (err) {}
95+
if (options.version === undefined) {
96+
options.version = 'unknown';
97+
}
98+
99+
// ErrorEvent
100+
// See https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
101+
var structPayload = {
102+
// ErrorEvent.serviceContext
103+
serviceContext: {
104+
// ErrorEvent.serviceContext.service
105+
service: 'cloud_function:' + FUNCTION_NAME,
106+
// ErrorEvent.serviceContext.version
107+
version: '' + options.version
108+
},
109+
// ErrorEvent.context
110+
context: context
111+
};
112+
113+
// ErrorEvent.message
114+
if (err instanceof Error && typeof err.stack === 'string') {
115+
structPayload.message = err.stack;
116+
} else if (typeof err === 'string') {
117+
structPayload.message = err;
118+
} else if (typeof err.message === 'string') {
119+
structPayload.message = err.message;
120+
}
121+
122+
log.write(log.entry(resource, structPayload), callback);
123+
}
124+
// [END helloHttpError]
125+
126+
module.exports = reportDetailedError;

functions/log/index.js

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,82 @@
1414
'use strict';
1515

1616
// [START log]
17-
exports.helloWorld = function (context, data) {
17+
exports.helloWorld = function helloWorld (context, data) {
1818
console.log('I am a log entry!');
1919
context.success();
2020
};
2121
// [END log]
22+
23+
exports.retrieve = function retrieve () {
24+
// [START retrieve]
25+
// By default, gcloud will authenticate using the service account file specified
26+
// by the GOOGLE_APPLICATION_CREDENTIALS environment variable and use the
27+
// project specified by the GCLOUD_PROJECT environment variable. See
28+
// https://googlecloudplatform.github.io/gcloud-node/#/docs/guides/authentication
29+
var gcloud = require('gcloud');
30+
var logging = gcloud.logging();
31+
32+
// Retrieve the latest Cloud Function log entries
33+
// See https://googlecloudplatform.github.io/gcloud-node/#/docs/logging
34+
logging.getEntries({
35+
pageSize: 10,
36+
filter: 'resource.type="cloud_function"'
37+
}, function (err, entries) {
38+
if (err) {
39+
console.error(err);
40+
} else {
41+
console.log(entries);
42+
}
43+
});
44+
// [END retrieve]
45+
};
46+
47+
exports.getMetrics = function getMetrics () {
48+
// [START getMetrics]
49+
var google = require('googleapis');
50+
var monitoring = google.monitoring('v3');
51+
52+
google.auth.getApplicationDefault(function (err, authClient) {
53+
if (err) {
54+
return console.error('Authentication failed', err);
55+
}
56+
if (authClient.createScopedRequired && authClient.createScopedRequired()) {
57+
var scopes = [
58+
'https://www.googleapis.com/auth/cloud-platform',
59+
'https://www.googleapis.com/auth/monitoring',
60+
'https://www.googleapis.com/auth/monitoring.read',
61+
'https://www.googleapis.com/auth/monitoring.write'
62+
];
63+
authClient = authClient.createScoped(scopes);
64+
}
65+
66+
// Format a date according to RFC33339 with milliseconds format
67+
function formatDate (date) {
68+
return JSON.parse(JSON.stringify(date).replace('Z', '000Z'));
69+
}
70+
71+
// Create two datestrings, a start and end range
72+
var oneWeekAgo = new Date();
73+
var now = new Date();
74+
oneWeekAgo.setHours(oneWeekAgo.getHours() - (7 * 24));
75+
oneWeekAgo = formatDate(oneWeekAgo);
76+
now = formatDate(now);
77+
78+
monitoring.projects.timeSeries.list({
79+
auth: authClient,
80+
// There is also cloudfunctions.googleapis.com/function/execution_count
81+
filter: 'metric.type="cloudfunctions.googleapis.com/function/execution_times"',
82+
pageSize: 10,
83+
'interval.startTime': oneWeekAgo,
84+
'interval.endTime': now,
85+
name: 'projects/' + process.env.GCLOUD_PROJECT
86+
}, function (err, results) {
87+
if (err) {
88+
console.error(err);
89+
} else {
90+
console.log(results.timeSeries);
91+
}
92+
});
93+
});
94+
// [END getMetrics]
95+
};

0 commit comments

Comments
 (0)