diff --git a/docs/json/master/logging/.gitkeep b/docs/json/master/logging/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lib/index.js b/lib/index.js index 71644c2918b0..c9df5d7d0ce2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -108,6 +108,34 @@ var apis = { */ dns: require('./dns'), + /** + *

+ * **This is a *Beta* release of Google Cloud Logging for Google Compute + * Engine.** This feature is not covered by any SLA or deprecation policy + * and may be subject to backward-incompatible changes. + *

+ * + * [Google Cloud Logging](https://cloud.google.com/logging/docs) collects and + * stores logs from applications and services on the Google Cloud Platform: + * + * - Export your logs to Google Cloud Storage, Google BigQuery, or Google + * Cloud Pub/Sub. + * - Integrate third-party logs from your virtual machine instances by + * installing the logging agent, `google-fluentd`. + * + * @type {module:logging} + * + * @return {module:logging} + * + * @example + * var gcloud = require('gcloud'); + * var logging = gcloud.logging({ + * projectId: 'grape-spaceship-123', + * keyFilename: '/path/to/keyfile.json' + * }); + */ + logging: require('./logging'), + /** * [Google Cloud Pub/Sub](https://developers.google.com/pubsub/overview) is a * reliable, many-to-many, asynchronous messaging service from Google Cloud diff --git a/lib/logging/index.js b/lib/logging/index.js new file mode 100644 index 000000000000..aa1df98d1a5d --- /dev/null +++ b/lib/logging/index.js @@ -0,0 +1,532 @@ +/*! + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module logging + */ + +'use strict'; + +var arrify = require('arrify'); +var extend = require('extend'); +var is = require('is'); + +/** + * @type {module:storage/bucket} + * @private + */ +var Bucket = require('../storage/bucket.js'); + +/** + * @type {module:logging/log} + * @private + */ +var Log = require('./log.js'); + +/** + * @type {module:logging/service} + * @private + */ +var Service = require('./service.js'); + +/** + * @type {module:logging/sink} + * @private + */ +var Sink = require('./sink.js'); + +/** + * @type {module:common/streamrouter} + * @private + */ +var streamRouter = require('../common/stream-router.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * @const {string} + * @private + */ +var LOGGING_BASE_URL = 'https://logging.googleapis.com/v1beta3/projects/'; + +/** + * Required scopes for Google Cloud Logging API. + * @const {array} + * @private + */ +var SCOPES = [ + 'https://www.googleapis.com/auth/logging.admin' +]; + +/** + * [Google Cloud Logging](https://cloud.google.com/logging/docs) collects and + * stores logs from applications and services on the Google Cloud Platform: + * + * - Export your logs to Google Cloud Storage, Google BigQuery, or Google + * Cloud Pub/Sub. + * - Integrate third-party logs from your virtual machine instances by + * installing the logging agent, `google-fluentd`. + * + * @alias module:logging + * @constructor + * + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var logging = gcloud.logging(); + */ +function Logging(options) { + if (!(this instanceof Logging)) { + options = util.normalizeArguments(this, options); + return new Logging(options); + } + + this.projectId = options.projectId; + + this.makeAuthenticatedRequest_ = util.makeAuthenticatedRequestFactory({ + credentials: options.credentials, + keyFile: options.keyFilename, + scopes: SCOPES, + email: options.email + }); +} + +/** + * Create a sink. + * + * @resource [Sink Overview]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks} + * @resource [projects.sinks.create API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks/create} + * + * @throws {Error} if a name is not provided. + * @throws {Error} if a config object is not provided. + * + * @param {string} name - Name of the firewall. + * @param {object} config - See a + * [Sink resource](https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logServices.sinks#LogSink). + * @param {module:storage/bucket|string} config.destination - The destination. + * If a GCS Bucket object is provided, the proper ACL scope will be granted + * to the bucket. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:loggin/sink} callback.sink - The created Sink object. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var gcs = gcloud.storage(); + * + * var config = { + * destination: gcs.bucket('logging-bucket') + * }; + * + * function callback(err, sink, apiResponse) { + * // `sink` is a Sink object. + * } + * + * logging.createSink('new-sink-name', config, callback); + */ +Logging.prototype.createSink = function(name, config, callback) { + var self = this; + + if (!is.string(name)) { + throw new Error('A sink name must be provided.'); + } + + if (!is.object(config)) { + throw new Error('A sink configuration object must be provided.'); + } + + if (config.destination instanceof Bucket) { + var bucket = config.destination; + + // Set the proper permissions on the bucket. + bucket.acl.owners.addGroup('cloud-logs@google.com', function(err) { + if (err) { + callback(err); + return; + } + + config.destination = 'storage.googleapis.com/' + bucket.name; + self.createSink(name, config, callback); + }); + return; + } + + var body = extend({}, config, { + name: name + }); + + this.makeReq_('POST', '/sinks', null, body, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + var sink = self.sink(resp.name); + sink.metadata = resp; + + callback(null, sink, resp); + }); +}; + +/** + * List the logs in this project. **Only logs that have entries are listed.** + * + * @resource [projects.logs.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs/list} + * + * @param {object=} options - Services filtering options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number} options.pageSize - Maximum number of logs to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:logging/log[]} callback.logs - Log objects from your project. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * logging.getLogs(function(err, logs) { + * // logs is an array of `Log` objects. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, logs, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * logging.getLogs(nextQuery, callback); + * } + * } + * + * logging.getLogs({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the logs from your project as a readable object stream. + * //- + * logging.getLogs() + * .on('error', console.error) + * .on('data', function(log) { + * // `log` is an `Log` object. + * }) + * .on('end', function() { + * // All logs retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * logging.getLogs() + * .on('data', function(service) { + * this.end(); + * }); + */ +Logging.prototype.getLogs = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + var path = '/logs'; + + this.makeReq_('GET', path, options, null, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + var logs = arrify(resp.logs).map(function(log) { + var logInstance = self.log(log.name); + logInstance.metadata = log; + return logInstance; + }); + + callback(null, logs, nextQuery, resp); + }); +}; + +/** + * List the log services that have entries in this project. + * + * @resource [projects.logServices.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logServices/list} + * + * @param {object=} options - Services filtering options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number} options.pageSize - Maximum number of services to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:logging/service[]} callback.services - Service objects from + * your project. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * logging.getServices(function(err, services) { + * // services is an array of `Service` objects. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, services, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * logging.getServices(nextQuery, callback); + * } + * } + * + * logging.getServices({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the services from your project as a readable object stream. + * //- + * logging.getServices() + * .on('error', console.error) + * .on('data', function(service) { + * // `service` is an `Service` object. + * }) + * .on('end', function() { + * // All services retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * logging.getServices() + * .on('data', function(service) { + * this.end(); + * }); + */ +Logging.prototype.getServices = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + var path = '/logServices'; + + this.makeReq_('GET', path, options, null, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var services = arrify(resp.logServices).map(function(service) { + var serviceInstance = self.service(service.name); + serviceInstance.metadata = service; + return serviceInstance; + }); + + callback(null, services, nextQuery, resp); + }); +}; + +/*! Developer Documentation + * + * The JSON API doesn't support any query options. We still hold a place for an + * `options` argument so StreamRouter still works... and in case the API ever + * does support it, like all of the other `list` operations do! + * + * @param {object=} options - Query object. + */ +/** + * Get the sinks associated with this project. + * + * @resource [projects.sinks.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks/list} + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:logging/sink[]} callback.sinks - Sink objects. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * logging.getSinks(function(err, sinks) { + * // sinks is an array of Sink objects. + * }); + * + * //- + * // Get the sinks from your project as a readable object stream. + * //- + * logging.getSinks() + * .on('error', console.error) + * .on('data', function(sink) { + * // `sink` is a Sink object. + * }) + * .on('end', function() { + * // All sinks retrieved. + * }); + */ +Logging.prototype.getSinks = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + var path = '/sinks'; + + this.makeReq_('GET', path, null, null, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var sinks = arrify(resp.sinks).map(function(sink) { + var sinkInstance = self.sink(sink.name); + sinkInstance.metadata = sink; + return sinkInstance; + }); + + callback(null, sinks, nextQuery, resp); + }); +}; + +/** + * Get a reference to a Cloud Logging log. + * + * @resource [Log Overview]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs} + * + * @param {string} name - Name of the existing log. + * @return {module:logging/log} + * + * @example + * var log = logging.log('my-log'); + */ +Logging.prototype.log = function(name) { + return new Log(this, name); +}; + +/** + * Get a reference to a Cloud Logging service. + * + * @resource [Service Overview]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logServices} + * + * @param {string} name - Name of the existing service. + * @return {module:logging/service} + * + * @example + * var service = logging.service('compute.googleapis.com'); + */ +Logging.prototype.service = function(name) { + return new Service(this, name); +}; + +/** + * Get a reference to a Cloud Logging sink. + * + * @resource [Sink Overview]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks} + * + * @param {string} name - Name of the existing sink. + * @return {module:logging/sink} + * + * @example + * var sink = logging.sink('my-sink'); + */ +Logging.prototype.sink = function(name) { + return new Sink(this, name); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Logging.prototype.makeReq_ = function(method, path, query, body, callback) { + var reqOpts = { + method: method, + qs: query, + uri: LOGGING_BASE_URL + this.projectId + path + }; + + if (body) { + reqOpts.json = body; + } + + return this.makeAuthenticatedRequest_(reqOpts, callback); +}; + +/*! Developer Documentation + * + * These methods can be used with either a callback or as a readable object + * stream. `streamRouter` is used to add this dual behavior. + */ +streamRouter.extend(Logging, ['getLogs', 'getServices', 'getSinks']); + +module.exports = Logging; diff --git a/lib/logging/log.js b/lib/logging/log.js new file mode 100644 index 000000000000..cba87a29b36b --- /dev/null +++ b/lib/logging/log.js @@ -0,0 +1,231 @@ +/*! + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module logging/log + */ + +'use strict'; + +var arrify = require('arrify'); +var extend = require('extend'); +var is = require('is'); +var JSONStream = require('JSONStream'); +var pumpify = require('pumpify'); +var streamEvents = require('stream-events'); +var through = require('through2'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * + * + * @alias module:logging/log + * @constructor + * + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var logging = gcloud.logging(); + * var log = logging.log('my-log'); + */ +function Log(logging, name) { + this.logging = logging; + this.name = name.replace(/^.*logs/, ''); + this.unformattedName = name; +} + +/** + * Write log entries from a streaming object input. + * + * @todo implement. + * + * @example + * var logStream = log.createWriteStream(); + * + * logStream.on('finish', function(err) { + * // All entries have been written. + * }); + * + * logStream.end({ + * // Log entry. + * }); + */ +Log.prototype.createWriteStream = function() { + var self = this; + var writeStream = streamEvents(pumpify.obj()); + + writeStream.once('writing', function() { + // get request stream + var requestStream = self.makeReq_('POST', '/entries:write'); + + requestStream.on('response', function(response) { + writeStream.emit('response', response); + }); + + function formatEntry_(entry, enc, next) { + next(null, self.formatEntry_(entry)); + } + + writeStream.setPipeline([ + through.obj(formatEntry_), + JSONStream.stringify('{\n "entries":\n [', '\n,\n', '\n ]\n }'), + requestStream + ]); + }); + + return writeStream; +}; + +/** + * Delete the log. + * + * @resource [projects.logs.delete API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs/delete} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * log.delete(function(err, apiResponse) { + * if (!err) { + * // The log was deleted. + * } + * }); + */ +Log.prototype.delete = function(callback) { + callback = callback || util.noop; + + this.makeReq_('DELETE', '', null, null, function(err, resp) { + callback(err, resp); + }); +}; + +/** + * Write log entries to Cloud Logging. + * + * @resource [projects.logs.entries.write API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs.entries/write} + * + * @param {object|object[]} entry - A log entry, or array of entries, to write. + * See a [LogEntry resource](https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs.entries/write#LogEntry). + * @param {object|string} entry.data - The payload for this log entry. (Alias + * for `structPayload` and `textPayload` as described by the API) + * @param {object=} entry.metadata - See a + * [LogEntryMetadata resource](https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logs.entries/write#LogEntryMetadata). + * @param {object[]=} labels - Labels to set on the log. (Alias for + * `commonLabels` as described by the API) + * @param {function} callback - The callback function. + * + * @example + * var logEntry = { + * metadata: { + * severity: 'INFO', + * timestamp: new Date().toJSON() + * }, + * data: { + * delegate: process.env.USER + * } + * }; + * + * log.insert(logEntry, function(err, apiResponse) { + * if (!err) { + * // The log entry was written. + * } + * }); + * + * //- + * // You may also pass multiple log entries to write. + * //- + * var secondLogEntry = { + * metadata: { + * severity: 'DEBUG', + * timestamp: new Date().toJSON() + * }, + * data: { + * delegate: process.env.USER + * } + * }; + * + * log.insert([ + * logEntry, + * secondLogEntry + * ], function(err, apiResponse) { + * if (!err) { + * // The log entries were written. + * } + * }); + */ +Log.prototype.insert = function(entry, labels, callback) { + if (is.fn(labels)) { + callback = labels; + labels = {}; + } + + var body = { + commonLabels: labels, + entries: arrify(entry).map(this.formatEntry_.bind(this)) + }; + + this.makeReq_('POST', '/entries:write', null, body, function(err, resp) { + callback(err, resp); + }); +}; + +Log.prototype.formatEntry_ = function(entry) { + entry = extend({}, entry); + + entry.log = this.name; + + if (is.object(entry.data)) { + entry.structPayload = entry.data; + } + + if (is.string(entry.data)) { + entry.textPayload = entry.data; + } + + delete entry.data; + + return entry; +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Log.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/logs/' + this.name + path; + return this.logging.makeReq_(method, path, query, body, callback); +}; + +module.exports = Log; diff --git a/lib/logging/service.js b/lib/logging/service.js new file mode 100644 index 000000000000..66004a53da83 --- /dev/null +++ b/lib/logging/service.js @@ -0,0 +1,171 @@ +/*! + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module logging/service + */ + +'use strict'; + +var arrify = require('arrify'); +var extend = require('extend'); +var is = require('is'); + +/** + * @type {module:common/streamrouter} + * @private + */ +var streamRouter = require('../common/stream-router.js'); + +/** + * Log services are Google Cloud Platform services that create log entries for + * projects. Log entries must be associated with a service at the time of + * ingestion. Log entries associated with a Google Cloud Platform service are + * indexed by labels specific to that service. The log services available to a + * project are named `projects/{...}/logServices/{...}`. + * + * @alias module:logging/service + * @constructor + * + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var logging = gcloud.logging(); + * var service = logging.service('compute.googleapis.com'); + */ +function Service(logging, name) { + this.logging = logging; + this.name = name; + this.metadata = {}; +} + +/** + * List the current index values for this service. + * + * @resource [projects.logServices.indexes.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.logServices.indexes/list} + * + * @param {object=} options - Service query options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number} options.pageSize - Maximum number of indexes to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.indexes - The current index values. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * service.getIndexes(function(err, indexes) { + * // indexes is an array of results from the API. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, indexes, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * service.getIndexes(nextQuery, callback); + * } + * } + * + * service.getIndexes({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the indexes from your service as a readable object stream. + * //- + * service.getIndexes() + * .on('error', console.error) + * .on('data', function(index) { + * // `index` is a result from the API. + * }) + * .on('end', function() { + * // All indexes retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * service.getIndexes() + * .on('data', function(index) { + * this.end(); + * }); + */ +Service.prototype.getIndexes = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + this.makeReq_('GET', '/indexes', options, null, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var indexes = arrify(resp.serviceIndexPrefixes); + + callback(null, indexes, nextQuery, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Service.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/logServices/' + this.name + path; + this.logging.makeReq_(method, path, query, body, callback); +}; + +/*! Developer Documentation + * + * These methods can be used with either a callback or as a readable object + * stream. `streamRouter` is used to add this dual behavior. + */ +streamRouter.extend(Service, ['getIndexes']); + +module.exports = Service; diff --git a/lib/logging/sink.js b/lib/logging/sink.js new file mode 100644 index 000000000000..dec500cd5188 --- /dev/null +++ b/lib/logging/sink.js @@ -0,0 +1,165 @@ +/*! + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module logging/sink + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/*! Developer Documentation + * + * @param {module:logging} logging - The Logging instance. + */ +/** + * + * + * @alias module:logging/sink + * @constructor + * + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var logging = gcloud.logging(); + * var sink = logging.sink('my-sink'); + */ +function Sink(logging, name) { + this.logging = logging; + this.name = name; + this.metadata = {}; +} + +/** + * Delete the sink. + * + * @resource [projects.sink.delete API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks/delete} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * sink.delete(function(err, apiResponse) { + * if (!err) { + * // The log was deleted. + * } + * }); + */ +Sink.prototype.delete = function(callback) { + callback = callback || util.noop; + + this.makeReq_('DELETE', '', null, null, function(err, resp) { + callback(err, resp); + }); +}; + +/** + * Get the sink's metadata. + * + * @resource [Sink Resource]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks#LogSink} + * @resource [projects.sink.get API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request + * @param {object} callback.metadata - The sink's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * sink.getMetadata(function(err, metadata, apiResponse) {}); + */ +Sink.prototype.getMetadata = function(callback) { + var self = this; + + callback = callback || util.noop; + + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + self.metadata = resp; + + callback(null, self.metadata, resp); + }); +}; + +/** + * Set the sink's metadata. + * + * @resource [Sink Resource]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks#LogSink} + * @resource [projects.sink.update API Documentation]{@link https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks/update} + * + * @param {object} metadata - See a + * [Sink resource](https://cloud.google.com/logging/docs/api/ref/rest/v1beta3/projects.sinks#LogSink). + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var metadata = { + * description: 'New description' + * }; + * + * sink.setMetadata(metadata, function(err, apiResponse) {}); + */ +Sink.prototype.setMetadata = function(metadata, callback) { + var self = this; + + callback = callback || util.noop; + + this.makeReq_('PUT', '', null, metadata, function(err, resp) { + if (err) { + callback(err, resp); + return; + } + + self.metadata = resp; + + callback(null, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Sink.prototype.makeReq_ = function(method, path, query, body, callback) { + // The parent `scope` can be Logging, Log, or Service. + path = '/sinks/' + this.name + path; + this.logging.makeReq_(method, path, query, body, callback); +}; + +module.exports = Sink; diff --git a/package.json b/package.json index 424d9586573b..4f603a431d19 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "search" ], "dependencies": { + "JSONStream": "^1.0.4", "arrify": "^1.0.0", "async": "^1.4.2", "concat-stream": "^1.5.0", diff --git a/scripts/docs.sh b/scripts/docs.sh index b88506605d37..f18a267dbea3 100755 --- a/scripts/docs.sh +++ b/scripts/docs.sh @@ -43,6 +43,11 @@ ./node_modules/.bin/dox < lib/datastore/request.js > docs/json/master/datastore/request.json & ./node_modules/.bin/dox < lib/datastore/transaction.js > docs/json/master/datastore/transaction.json & +./node_modules/.bin/dox < lib/logging/index.js > docs/json/master/logging/index.json & +./node_modules/.bin/dox < lib/logging/log.js > docs/json/master/logging/log.json & +./node_modules/.bin/dox < lib/logging/service.js > docs/json/master/logging/service.json & +./node_modules/.bin/dox < lib/logging/sink.js > docs/json/master/logging/sink.json & + ./node_modules/.bin/dox < lib/pubsub/index.js > docs/json/master/pubsub/index.json & ./node_modules/.bin/dox < lib/pubsub/subscription.js > docs/json/master/pubsub/subscription.json & ./node_modules/.bin/dox < lib/pubsub/topic.js > docs/json/master/pubsub/topic.json & diff --git a/system-test/logging.js b/system-test/logging.js new file mode 100644 index 000000000000..fc406800f994 --- /dev/null +++ b/system-test/logging.js @@ -0,0 +1,141 @@ +/*! + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var assert = require('assert'); +var uuid = require('node-uuid'); + +var env = require('./env.js'); +var Logging = require('../lib/logging/index.js'); +var Storage = require('../lib/storage/index.js'); +var util = require('../lib/common/util.js'); + +describe('Logging', function() { + var logging = new Logging(env); + var storage = new Storage(env); + + // Create a bucket to use as a destination for the created sinks. + var BUCKET_NAME = generateUniqueName(); + var bucket; + + before(function(done) { + storage.createBucket(BUCKET_NAME, function(err, bucket_) { + if (err) { + done(err); + return; + } + + bucket = bucket_; + done(); + }); + }); + + after(function(done) { + bucket.delete(done); + }); + + it('should list services', function(done) { + logging.getServices({ pageSize: 1 }, function(err, services) { + assert.ifError(err); + assert.strictEqual(services.length, 1); + done(); + }); + }); + + it('should list services as a stream', function(done) { + logging.getServices({ pageSize: 1 }) + .on('error', done) + .once('data', function() { + this.end(); + done(); + }); + }); + + it('should create a sink', function(done) { + var name = generateUniqueName(); + + // To create a Sink, auth through the gcloud SDK is required. + var logging = new Logging({ + projectId: env.projectId + }); + + logging.createSink(name, { + destination: bucket + }, function(err, sink) { + assert.ifError(err); + assert.strictEqual(sink.name, name); + sink.delete(done); + }); + }); + + describe('services', function() { + var service; + + before(function(done) { + logging.getServices({ pageSize: 1 }, function(err, services) { + assert.ifError(err); + service = services[0]; + done(); + }); + }); + + it('should get indexes', function(done) { + service.getIndexes({ pageSize: 1 }, function(err, indexes) { + assert.ifError(err); + assert.strictEqual(indexes.length, 1); + done(); + }); + }); + + it('should list indexes as a stream', function(done) { + service.getIndexes({ pageSize: 1 }) + .on('error', done) + .on('data', util.noop) + .on('end', done); + }); + }); + + describe('logs', function() { + var log = logging.log(generateUniqueName()); + + var logEntry = { + metadata: { + severity: 'INFO', + timestamp: new Date().toJSON(), + serviceName: 'compute.googleapis.com' + }, + data: { + delegate: process.env.USER + } + }; + + it('should write to a log', function(done) { + log.insert(logEntry, done); + }); + + it('should write from a stream', function(done) { + log.createWriteStream() + .on('error', done) + .on('finish', done) + .end(logEntry); + }); + }); +}); + +function generateUniqueName() { + return 'gcloud-test-logging-' + uuid.v1(); +} diff --git a/test/logging/index.js b/test/logging/index.js new file mode 100644 index 000000000000..4a317cc1d068 --- /dev/null +++ b/test/logging/index.js @@ -0,0 +1,38 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var mockery = require('mockery'); + +describe('Logging', function() { + before(function() { + // If we don't stub see4_crc32 and use mockery, we get "Module did not self- + // register". + var crc32c = require('hash-stream-validation/node_modules/sse4_crc32'); + mockery.registerMock('sse4_crc32', crc32c); + + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); +}); diff --git a/test/logging/log.js b/test/logging/log.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/logging/service.js b/test/logging/service.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/logging/sink.js b/test/logging/sink.js new file mode 100644 index 000000000000..e69de29bb2d1