index.js

"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