"use strict";
/*!
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const common = require("@google-cloud/common-grpc");
const paginator_1 = require("@google-cloud/paginator");
const projectify_1 = require("@google-cloud/projectify");
const promisify_1 = require("@google-cloud/promisify");
const arrify = require("arrify");
const extend = require("extend");
const google_auth_library_1 = require("google-auth-library");
const pumpify = require('pumpify');
const streamEvents = require("stream-events");
const through = require("through2");
const middleware = require("./middleware");
exports.middleware = middleware;
const metadata_1 = require("./metadata");
exports.detectServiceContext = metadata_1.detectServiceContext;
const PKG = require('../../package.json');
const v2 = require('./v2');
const entry_1 = require("./entry");
exports.Entry = entry_1.Entry;
const log_1 = require("./log");
exports.Log = log_1.Log;
exports.Severity = log_1.Severity;
const sink_1 = require("./sink");
exports.Sink = sink_1.Sink;
/**
* @namespace google
*/
/**
* @namespace google.api
*/
/**
* @namespace google.logging
*/
/**
* @namespace google.logging.type
*/
/**
* @namespace google.logging.v2
*/
/**
* @namespace google.protobuf
*/
/**
* @typedef {object} ClientConfig
* @property {string} [projectId] The project ID from the Google Developer's
* Console, e.g. 'grape-spaceship-123'. We will also check the environment
* variable `GCLOUD_PROJECT` for your project ID. If your app is running in
* an environment which supports {@link
* https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application
* Application Default Credentials}, your project ID will be detected
* automatically.
* @property {string} [keyFilename] Full path to the a .json, .pem, or .p12 key
* downloaded from the Google Developers Console. If you provide a path to a
* JSON file, the `projectId` option above is not necessary. NOTE: .pem and
* .p12 require you to specify the `email` option as well.
* @property {string} [email] Account email address. Required when using a .pem
* or .p12 keyFilename.
* @property {object} [credentials] Credentials object.
* @property {string} [credentials.client_email]
* @property {string} [credentials.private_key]
* @property {boolean} [autoRetry=true] Automatically retry requests if the
* response is related to rate limits or certain intermittent server errors.
* We will exponentially backoff subsequent requests by default.
* @property {number} [maxRetries=3] Maximum number of automatic retries
* attempted before returning the error.
* @property {Constructor} [promise] Custom promise module to use instead of
* native Promises.
*/
/**
* [Stackdriver Logging](https://cloud.google.com/logging/docs) allows you to
* store, search, analyze, monitor, and alert on log data and events from Google
* Cloud Platform and Amazon Web Services (AWS).
*
* @class
*
* @see [What is Stackdriver Logging?](https://cloud.google.com/logging/docs)
* @see [Introduction to the Stackdriver Logging API](https://cloud.google.com/logging/docs/api)
* @see [Logging to Stackdriver from Bunyan](https://www.npmjs.com/package/@google-cloud/logging-bunyan)
* @see [Logging to Stackdriver from Winston](https://www.npmjs.com/package/@google-cloud/logging-winston)
*
* @param {ClientConfig} [options] Configuration options.
*
* @example <caption>Import the client library</caption>
* const {Logging} = require('@google-cloud/logging');
*
* @example <caption>Create a client that uses <a
* href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application
* Default Credentials (ADC)</a>:</caption> const logging = new Logging();
*
* @example <caption>Create a client with <a
* href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit
* credentials</a>:</caption> const logging = new Logging({ projectId:
* 'your-project-id', keyFilename: '/path/to/keyfile.json'
* });
*
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:logging_quickstart
* Full quickstart example:
*/
class Logging {
constructor(options) {
// Determine what scopes are needed.
// It is the union of the scopes on all three clients.
const scopes = [];
const clientClasses = [
v2.ConfigServiceV2Client,
v2.LoggingServiceV2Client,
v2.MetricsServiceV2Client,
];
for (const clientClass of clientClasses) {
for (const scope of clientClass.scopes) {
if (scopes.indexOf(scope) === -1) {
scopes.push(scope);
}
}
}
const options_ = extend({
libName: 'gccl',
libVersion: PKG.version,
scopes,
}, options);
this.api = {};
this.auth = new google_auth_library_1.GoogleAuth(options_);
this.options = options_;
this.projectId = this.options.projectId || '{{projectId}}';
this.configService = new v2.ConfigServiceV2Client(this.options);
this.loggingService = new v2.LoggingServiceV2Client(this.options);
}
async createSink(name, config) {
if (typeof name !== 'string') {
throw new Error('A sink name must be provided.');
}
if (typeof config !== 'object') {
throw new Error('A sink configuration object must be provided.');
}
if (common.util.isCustomType(config.destination, 'bigquery/dataset')) {
await this.setAclForDataset_(config);
}
if (common.util.isCustomType(config.destination, 'pubsub/topic')) {
await this.setAclForTopic_(config);
}
if (common.util.isCustomType(config.destination, 'storage/bucket')) {
await this.setAclForBucket_(config);
}
const reqOpts = {
parent: 'projects/' + this.projectId,
sink: extend({}, config, { name }),
};
delete reqOpts.sink.gaxOptions;
await this.setProjectId(reqOpts);
const [resp] = await this.configService.createSink(reqOpts, config.gaxOptions);
const sink = this.sink(resp.name);
sink.metadata = resp;
return [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
* {@link Log#write}.
*
* @see [LogEntry JSON representation]{@link https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry}
*
* @param {?object|?string} [resource] See a
* [Monitored
* Resource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource).
* @param {object|string} data The data to use as the value for this log
* entry.
* @returns {Entry}
*
* @example
* const {Logging} = require('@google-cloud/logging');
* const logging = new Logging();
*
* const resource = {
* type: 'gce_instance',
* labels: {
* zone: 'global',
* instance_id: '3'
* }
* };
*
* const entry = logging.entry(resource, {
* delegate: 'my_username'
* });
*
* entry.toJSON();
* // {
* // resource: {
* // type: 'gce_instance',
* // labels: {
* // zone: 'global',
* // instance_id: '3'
* // }
* // },
* // jsonPayload: {
* // delegate: 'my_username'
* // }
* // }
*/
entry(resource, data) {
return new entry_1.Entry(resource, data);
}
async getEntries(opts) {
const options = opts ? opts : {};
const reqOpts = extend({
orderBy: 'timestamp desc',
}, options);
reqOpts.resourceNames = arrify(reqOpts.resourceNames);
this.projectId = await this.auth.getProjectId();
const resourceName = 'projects/' + this.projectId;
if (reqOpts.resourceNames.indexOf(resourceName) === -1) {
reqOpts.resourceNames.push(resourceName);
}
delete reqOpts.autoPaginate;
delete reqOpts.gaxOptions;
const gaxOptions = extend({
autoPaginate: options.autoPaginate,
}, options.gaxOptions);
const resp = await this.loggingService.listLogEntries(reqOpts, gaxOptions);
const [entries] = resp;
if (entries) {
resp[0] = entries.map(entry_1.Entry.fromApiResponse_);
}
return resp;
}
/**
* List the {@link Entry} objects in your logs as a readable object
* stream.
*
* @method Logging#getEntriesStream
* @param {GetEntriesRequest} [query] Query object for listing entries.
* @returns {ReadableStream} A readable stream that emits {@link Entry}
* instances.
*
* @example
* const {Logging} = require('@google-cloud/logging');
* const logging = new Logging();
*
* logging.getEntriesStream()
* .on('error', console.error)
* .on('data', entry => {
* // `entry` is a Stackdriver 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.getEntriesStream()
* .on('data', function(entry) {
* this.end();
* });
*/
getEntriesStream(options = {}) {
let requestStream;
const userStream = streamEvents(pumpify.obj());
userStream.abort = () => {
if (requestStream) {
requestStream.abort();
}
};
const toEntryStream = through.obj((entry, _, next) => {
next(null, entry_1.Entry.fromApiResponse_(entry));
});
userStream.once('reading', () => {
this.auth.getProjectId().then(projectId => {
this.projectId = projectId;
if (options.log) {
if (options.filter) {
options.filter = `(${options.filter}) AND logName="${log_1.Log.formatName_(this.projectId, options.log)}"`;
}
else {
options.filter = `logName="${log_1.Log.formatName_(this.projectId, options.log)}"`;
}
delete options.log;
}
const reqOpts = extend({
orderBy: 'timestamp desc',
}, options);
reqOpts.resourceNames = arrify(reqOpts.resourceNames);
reqOpts.resourceNames.push(`projects/${this.projectId}`);
delete reqOpts.autoPaginate;
delete reqOpts.gaxOptions;
const gaxOptions = extend({
autoPaginate: options.autoPaginate,
}, options.gaxOptions);
let gaxStream;
requestStream = streamEvents(through.obj());
requestStream.abort = () => {
if (gaxStream && gaxStream.cancel) {
gaxStream.cancel();
}
};
// tslint:disable-next-line no-any
if (!global.GCLOUD_SANDBOX_ENV) {
requestStream.once('reading', () => {
try {
gaxStream = this.loggingService.listLogEntriesStream(reqOpts, gaxOptions);
}
catch (error) {
requestStream.destroy(error);
return;
}
gaxStream
.on('error', err => {
requestStream.destroy(err);
})
.pipe(requestStream);
return;
});
}
// tslint:disable-next-line no-any
userStream.setPipeline(requestStream, toEntryStream);
});
});
return userStream;
}
async getSinks(opts) {
const options = opts ? opts : {};
this.projectId = await this.auth.getProjectId();
const reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId,
});
delete reqOpts.autoPaginate;
delete reqOpts.gaxOptions;
const gaxOptions = extend({
autoPaginate: options.autoPaginate,
}, options.gaxOptions);
const resp = await this.configService.listSinks(reqOpts, gaxOptions);
const [sinks] = resp;
if (sinks) {
resp[0] = sinks.map((sink) => {
const sinkInstance = this.sink(sink.name);
sinkInstance.metadata = sink;
return sinkInstance;
});
}
return resp;
}
/**
* Get the {@link Sink} objects associated with this project as a
* readable object stream.
*
* @method Logging#getSinksStream
* @param {GetSinksRequest} [query] Query object for listing sinks.
* @returns {ReadableStream} A readable stream that emits {@link Sink}
* instances.
*
* @example
* const {Logging} = require('@google-cloud/logging');
* const logging = new Logging();
*
* logging.getSinksStream()
* .on('error', console.error)
* .on('data', 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.getSinksStream()
* .on('data', function(sink) {
* this.end();
* });
*/
getSinksStream(options) {
const self = this;
options = options || {};
let requestStream;
const userStream = streamEvents(pumpify.obj());
userStream.abort = () => {
if (requestStream) {
requestStream.abort();
}
};
const toSinkStream = through.obj((sink, _, next) => {
const sinkInstance = self.sink(sink.name);
sinkInstance.metadata = sink;
next(null, sinkInstance);
});
userStream.once('reading', () => {
this.auth.getProjectId().then(projectId => {
this.projectId = projectId;
const reqOpts = extend({}, options, {
parent: 'projects/' + self.projectId,
});
delete reqOpts.gaxOptions;
const gaxOptions = extend({
autoPaginate: options.autoPaginate,
}, options.gaxOptions);
let gaxStream;
requestStream = streamEvents(through.obj());
requestStream.abort = () => {
if (gaxStream && gaxStream.cancel) {
gaxStream.cancel();
}
};
// tslint:disable-next-line no-any
if (!global.GCLOUD_SANDBOX_ENV) {
requestStream.once('reading', () => {
try {
gaxStream = this.configService.listSinksStream(reqOpts, gaxOptions);
}
catch (error) {
requestStream.destroy(error);
return;
}
gaxStream
.on('error', err => {
requestStream.destroy(err);
})
.pipe(requestStream);
return;
});
}
// tslint:disable-next-line no-any
userStream.setPipeline(requestStream, toSinkStream);
});
});
return userStream;
}
/**
* Get a reference to a Stackdriver Logging log.
*
* @see [Log Overview]{@link https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.logs}
*
* @param {string} name Name of the existing log.
* @param {object} [options] Configuration object.
* @param {boolean} [options.removeCircular] Replace circular references in
* logged objects with a string value, `[Circular]`. (Default: false)
* @returns {Log}
*
* @example
* const {Logging} = require('@google-cloud/logging');
* const logging = new Logging();
* const log = logging.log('my-log');
*/
log(name, options) {
return new log_1.Log(this, name, options);
}
/**
* Get a reference to a Stackdriver Logging sink.
*
* @see [Sink Overview]{@link https://cloud.google.com/logging/docs/reference/v2/rest/v2/projects.sinks}
*
* @param {string} name Name of the existing sink.
* @returns {Sink}
*
* @example
* const {Logging} = require('@google-cloud/logging');
* const logging = new Logging();
* const sink = logging.sink('my-sink');
*/
sink(name) {
return new sink_1.Sink(this, name);
}
/**
* Funnel all API requests through this method, to be sure we have a project
* ID.
*
* @param {object} config Configuration object.
* @param {object} config.gaxOpts GAX options.
* @param {function} config.method The gax method to call.
* @param {object} config.reqOpts Request options.
* @param {function} [callback] Callback function.
*/
// tslint:disable-next-line no-any
request(config, callback) {
const self = this;
const isStreamMode = !callback;
let gaxStream;
let stream;
if (isStreamMode) {
stream = streamEvents(through.obj());
stream.abort = () => {
if (gaxStream && gaxStream.cancel) {
gaxStream.cancel();
}
};
stream.once('reading', makeRequestStream);
}
else {
makeRequestCallback();
}
function prepareGaxRequest(callback) {
self.auth.getProjectId((err, projectId) => {
if (err) {
callback(err);
return;
}
self.projectId = projectId;
let gaxClient = self.api[config.client];
if (!gaxClient) {
// Lazily instantiate client.
gaxClient = new v2[config.client](self.options);
self.api[config.client] = gaxClient;
}
let reqOpts = extend(true, {}, config.reqOpts);
reqOpts = projectify_1.replaceProjectIdToken(reqOpts, projectId);
const requestFn = gaxClient[config.method].bind(gaxClient, reqOpts, config.gaxOpts);
callback(null, requestFn);
});
}
function makeRequestCallback() {
// tslint:disable-next-line no-any
if (global.GCLOUD_SANDBOX_ENV) {
return;
}
prepareGaxRequest((err, requestFn) => {
if (err) {
callback(err);
return;
}
requestFn(callback);
});
}
function makeRequestStream() {
// tslint:disable-next-line no-any
if (global.GCLOUD_SANDBOX_ENV) {
return through.obj();
}
prepareGaxRequest((err, requestFn) => {
if (err) {
stream.destroy(err);
return;
}
gaxStream = requestFn();
gaxStream
.on('error', err => {
stream.destroy(err);
})
.pipe(stream);
});
return;
}
return stream;
}
/**
* This method is called when creating a sink with a Bucket destination. The
* bucket must first grant proper ACL access to the Stackdriver Logging
* account.
*
* The parameters are the same as what {@link Logging#createSink} accepts.
*
* @private
*/
async setAclForBucket_(config) {
const bucket = config.destination;
// tslint:disable-next-line no-any
await bucket.acl.owners.addGroup('cloud-logs@google.com');
config.destination = 'storage.googleapis.com/' + bucket.name;
}
/**
* This method is called when creating a sink with a Dataset destination. The
* dataset must first grant proper ACL access to the Stackdriver Logging
* account.
*
* The parameters are the same as what {@link Logging#createSink} accepts.
*
* @private
*/
async setAclForDataset_(config) {
const dataset = config.destination;
const [metadata] = await dataset.getMetadata();
// tslint:disable-next-line no-any
const access = [].slice.call(arrify(metadata.access));
access.push({
role: 'WRITER',
groupByEmail: 'cloud-logs@google.com',
});
await dataset.setMetadata({
access,
});
const baseUrl = 'bigquery.googleapis.com';
const pId = dataset.parent.projectId;
const dId = dataset.id;
config.destination = `${baseUrl}/projects/${pId}/datasets/${dId}`;
}
/**
* This method is called when creating a sink with a Topic destination. The
* topic must first grant proper ACL access to the Stackdriver Logging
* account.
*
* The parameters are the same as what {@link Logging#createSink} accepts.
*
* @private
*/
async setAclForTopic_(config) {
const topic = config.destination;
const [policy] = await topic.iam.getPolicy();
policy.bindings = arrify(policy.bindings);
policy.bindings.push({
role: 'roles/pubsub.publisher',
members: ['serviceAccount:cloud-logs@system.gserviceaccount.com'],
});
await topic.iam.setPolicy(policy);
const baseUrl = 'pubsub.googleapis.com';
const topicName = topic.name;
config.destination = `${baseUrl}/${topicName}`;
}
async setProjectId(reqOpts) {
if (this.projectId === '{{projectId}}') {
this.projectId = await this.auth.getProjectId();
}
reqOpts = projectify_1.replaceProjectIdToken(reqOpts, this.projectId);
}
}
exports.Logging = Logging;
/*! Developer Documentation
* All async methods (except for streams) will execute a callback in the event
* that a callback is provided.
*/
promisify_1.callbackifyAll(Logging, {
exclude: ['request'],
});
/*! Developer Documentation
*
* These methods can be auto-paginated.
*/
paginator_1.paginator.extend(Logging, ['getEntries', 'getSinks']);
/**
* Reference to the low-level auto-generated clients for the V2 Logging service.
*
* @type {object}
* @property {constructor} LoggingServiceV2Client
* Reference to {@link v2.LoggingServiceV2Client}
* @property {constructor} ConfigServiceV2Client
* Reference to {@link v2.ConfigServiceV2Client}
* @property {constructor} MetricsServiceV2Client
* Reference to {@link v2.MetricsServiceV2Client}
*/
module.exports.v2 = v2;
//# sourceMappingURL=index.js.map