gcloud
Version:
Google Cloud APIs Client Library for Node.js
618 lines (550 loc) • 17.1 kB
JavaScript
/*!
* 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
*/
;
var arrify = require('arrify');
var extend = require('extend');
var format = require('string-format-obj');
var googleProtoFiles = require('google-proto-files');
var is = require('is');
var nodeutil = require('util');
/**
* @type {module:storage/bucket}
* @private
*/
var Bucket = require('../storage/bucket.js');
/**
* @type {module:bigquery/dataset}
* @private
*/
var Dataset = require('../bigquery/dataset.js');
/**
* @type {module:logging/entry}
* @private
*/
var Entry = require('./entry.js');
/**
* @type {module:common/grpc-service}
* @private
*/
var GrpcService = require('../common/grpc-service.js');
/**
* @type {module:logging/log}
* @private
*/
var Log = require('./log.js');
/**
* @type {module:logging/sink}
* @private
*/
var Sink = require('./sink.js');
/**
* @type {module:common/stream-router}
* @private
*/
var streamRouter = require('../common/stream-router.js');
/**
* @type {module:pubsub/topic}
* @private
*/
var Topic = require('../pubsub/topic.js');
/**
* @type {module:common/util}
* @private
*/
var util = require('../common/util.js');
/**
* [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
*
* @classdesc
* <p class="notice">
* **This is a Beta release of Google Cloud Logging.** This API is not covered
* by any SLA or deprecation policy and may be subject to
* backward-incompatible changes.
* </p>
*
* The `gcloud.logging` method will return a `logging` object, allowing you to
* create sinks, write log entries, and more.
*
* To learn more about Logging, see the
* [What is Google Cloud Logging?](https://cloud.google.com/logging/docs)
*
* @resource [What is Google Cloud Logging?]{@link https://cloud.google.com/logging/docs}
* @resource [Introduction to the Cloud Logging API]{@link https://cloud.google.com/logging/docs/api}
*
* @param {object} options - [Configuration object](#/docs).
*
* @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);
}
var config = {
baseUrl: 'logging.googleapis.com',
service: 'logging',
apiVersion: 'v2',
protoServices: {
ConfigServiceV2:
googleProtoFiles('logging', 'v2', 'logging_config.proto'),
LoggingServiceV2: googleProtoFiles.logging.v2
},
scopes: [
'https://www.googleapis.com/auth/cloud-platform'
]
};
GrpcService.call(this, config, options);
}
nodeutil.inherits(Logging, GrpcService);
// jscs:disable maximumLineLength
/**
* Create a sink.
*
* @resource [Sink Overview]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/projects.sinks}
* @resource [Advanced Logs Filters]{@link https://cloud.google.com/logging/docs/view/advanced_filters}
* @resource [projects.sinks.create API Documentation]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/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 sink.
* @param {object} config - See a
* [Sink resource](https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/projects.sinks#LogSink).
* @param {module:storage/bucket|module:bigquery/dataset|module:pubsub/topic} config.destination -
* The destination. The proper ACL scopes will be granted to the provided
* destination.
* @param {string=} config.filter - An advanced logs filter. Only log entries
* matching the filter are written.
* @param {function} callback - The callback function.
* @param {?error} callback.err - An error returned while making this request.
* @param {module:logging/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'),
* filter: 'severity = ALERT'
* };
*
* function callback(err, sink, apiResponse) {
* // `sink` is a Sink object.
* }
*
* logging.createSink('new-sink-name', config, callback);
*/
Logging.prototype.createSink = function(name, config, callback) {
// jscs:enable maximumLineLength
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) {
this.setAclForBucket_(name, config, callback);
return;
}
if (config.destination instanceof Dataset) {
this.setAclForDataset_(name, config, callback);
return;
}
if (config.destination instanceof Topic) {
this.setAclForTopic_(name, config, callback);
return;
}
var protoOpts = {
service: 'ConfigServiceV2',
method: 'createSink'
};
var reqOpts = {
parent: 'projects/' + this.projectId,
sink: extend({}, config, { name: name })
};
this.request(protoOpts, reqOpts, function(err, resp) {
if (err) {
callback(err, null, resp);
return;
}
var sink = self.sink(resp.name);
sink.metadata = resp;
callback(null, sink, resp);
});
};
/**
* Create an entry object.
*
* Note that using this method will not itself make any API requests. You will
* use the object returned in other API calls, such as
* {module:logging/log#write}.
*
* @resource [LogEntry JSON representation]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/LogEntry}
*
* @param {object=|string=} resource - See a
* [Monitored Resource](https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource).
* @param {object|string} data - The data to use as the value for this log
* entry.
* @return {module:logging/entry}
*
* @example
* var resource = {
* type: 'gce_instance',
* labels: {
* zone: 'global',
* instance_id: '3'
* }
* };
*
* var entry = logging.entry(resource, {
* delegate: 'my_username'
* });
*
* entry.toJSON();
* // {
* // resource: {
* // type: 'gce_instance',
* // labels: {
* // zone: 'global',
* // instance_id: '3'
* // }
* // },
* // jsonPayload: {
* // delegate: 'my_username'
* // }
* // }
*/
Logging.prototype.entry = function(resource, data) {
return new Entry(resource, data);
};
/**
* List the entries in your logs.
*
* @resource [entries.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/entries/list}
*
* @param {object=} options - Filtering options.
* @param {boolean} options.autoPaginate - Have pagination handled
* automatically. Default: true.
* @param {string} options.filter - An
* [advanced logs filter](https://cloud.google.com/logging/docs/view/advanced_filters).
* An empty filter matches all log entries.
* @param {number} options.maxApiCalls - Maximum number of API calls to make.
* @param {number} options.maxResults - Maximum number of results to return.
* @param {string} options.orderBy - How the results should be sorted,
* `timestamp` (oldest first) and `timestamp desc` (newest first,
* **default**).
* @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/entry[]} callback.entries - Entries from your logs.
* @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.getEntries(function(err, entries) {
* // `entries` is an array of Cloud Logging entry objects.
* // See the `data` property to read the data from the entry.
* });
*
* //-
* // To control how many API requests are made and page through the results
* // manually, set `autoPaginate` to `false`.
* //-
* function callback(err, entries, nextQuery, apiResponse) {
* if (nextQuery) {
* // More results exist.
* logging.getEntries(nextQuery, callback);
* }
* }
*
* logging.getEntries({
* autoPaginate: false
* }, callback);
*
* //-
* // Get the entries from your project as a readable object stream.
* //-
* logging.getEntries()
* .on('error', console.error)
* .on('data', function(entry) {
* // `entry` is a Cloud Logging entry object.
* // See the `data` property to read the data from the entry.
* })
* .on('end', function() {
* // All entries retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* logging.getEntries()
* .on('data', function(entry) {
* this.end();
* });
*/
Logging.prototype.getEntries = function(options, callback) {
if (is.fn(options)) {
callback = options;
options = {};
}
var protoOpts = {
service: 'LoggingServiceV2',
method: 'listLogEntries'
};
var reqOpts = extend({
orderBy: 'timestamp desc'
}, options);
reqOpts.projectIds = arrify(reqOpts.projectIds);
reqOpts.projectIds.push(this.projectId);
this.request(protoOpts, reqOpts, function(err, resp) {
if (err) {
callback(err, null, null, resp);
return;
}
var nextQuery = null;
if (resp.nextPageToken) {
nextQuery = extend({}, reqOpts, {
pageToken: resp.nextPageToken
});
}
var entries = arrify(resp.entries).map(Entry.fromApiResponse_);
callback(null, entries, nextQuery, resp);
});
};
/**
* Get the sinks associated with this project.
*
* @resource [projects.sinks.list API Documentation]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/projects.sinks/list}
*
* @param {object=} options - Configuration object.
* @param {boolean} options.autoPaginate - Have pagination handled
* automatically. Default: true.
* @param {number} options.maxApiCalls - Maximum number of API calls to make.
* @param {number} options.maxResults - Maximum number of results to return.
* @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.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* logging.getSinks()
* .on('data', function(sink) {
* this.end();
* });
*/
Logging.prototype.getSinks = function(options, callback) {
var self = this;
if (is.fn(options)) {
callback = options;
options = {};
}
var protoOpts = {
service: 'ConfigServiceV2',
method: 'listSinks'
};
var reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId
});
this.request(protoOpts, reqOpts, 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_v2beta1/rest/v2beta1/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 sink.
*
* @resource [Sink Overview]{@link https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/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);
};
/**
* This method is called when creating a sink with a Bucket destination. The
* bucket must first grant proper ACL access to the Cloud Logging account.
*
* The parameters are the same as what {module:logging#createSink} accepts.
*
* @private
*/
Logging.prototype.setAclForBucket_ = function(name, config, callback) {
var self = this;
var bucket = config.destination;
bucket.acl.owners.addGroup('cloud-logs@google.com', function(err, apiResp) {
if (err) {
callback(err, null, apiResp);
return;
}
config.destination = 'storage.googleapis.com/' + bucket.name;
self.createSink(name, config, callback);
});
};
/**
* This method is called when creating a sink with a Dataset destination. The
* dataset must first grant proper ACL access to the Cloud Logging account.
*
* The parameters are the same as what {module:logging#createSink} accepts.
*
* @private
*/
Logging.prototype.setAclForDataset_ = function(name, config, callback) {
var self = this;
var dataset = config.destination;
dataset.getMetadata(function(err, metadata, apiResp) {
if (err) {
callback(err, null, apiResp);
return;
}
var access = [].slice.call(arrify(metadata.access));
access.push({
role: 'WRITER',
groupByEmail: 'cloud-logs@google.com'
});
dataset.setMetadata({
access: access
}, function(err, apiResp) {
if (err) {
callback(err, null, apiResp);
return;
}
config.destination = format('{baseUrl}/projects/{pId}/datasets/{dId}', {
baseUrl: 'bigquery.googleapis.com',
pId: dataset.parent.projectId,
dId: dataset.id
});
self.createSink(name, config, callback);
});
});
};
/**
* This method is called when creating a sink with a Topic destination. The
* topic must first grant proper ACL access to the Cloud Logging account.
*
* The parameters are the same as what {module:logging#createSink} accepts.
*
* @private
*/
Logging.prototype.setAclForTopic_ = function(name, config, callback) {
var self = this;
var topic = config.destination;
topic.iam.getPolicy(function(err, policy, apiResp) {
if (err) {
callback(err, null, apiResp);
return;
}
policy.bindings = arrify(policy.bindings);
policy.bindings.push({
role: 'roles/pubsub.publisher',
members: [
'serviceAccount:cloud-logs@system.gserviceaccount.com'
]
});
topic.iam.setPolicy(policy, function(err, policy, apiResp) {
if (err) {
callback(err, null, apiResp);
return;
}
config.destination = format('{baseUrl}/{topicName}', {
baseUrl: 'pubsub.googleapis.com',
topicName: topic.name
});
self.createSink(name, config, 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, ['getEntries', 'getSinks']);
module.exports = Logging;