UNPKG

@google-cloud/logging

Version:
405 lines 17.2 kB
"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 }); exports.Log = void 0; const promisify_1 = require("@google-cloud/promisify"); const dotProp = require("dot-prop"); const extend = require("extend"); const entry_1 = require("./entry"); const instrumentation_1 = require("./utils/instrumentation"); const log_common_1 = require("./utils/log-common"); /** * A log is a named collection of entries, each entry representing a timestamped * event. Logs can be produced by Google Cloud Platform services, by third-party * services, or by your applications. For example, the log `apache-access` is * produced by the Apache Web Server, but the log * `compute.googleapis.com/activity_log` is produced by Google Compute Engine. * * See {@link https://cloud.google.com/logging/docs/basic-concepts#logs|Introduction to Logs} * * @class * * @param {Logging} logging {@link Logging} instance. * @param {string} name Name of the log. * @param {object} [options] Configuration object. * @param {boolean} [options.removeCircular] Replace circular references in * logged objects with a string value, `[Circular]`. (Default: false) * @param {number} [options.maxEntrySize] A max entry size * @param {string[]} [options.jsonFieldsToTruncate] A list of JSON properties at the given full path to be truncated. * Received values will be prepended to predefined list in the order received and duplicates discarded. * @param {ApiResponseCallback} [options.defaultWriteDeleteCallback] A default global callback to be used for {@link Log#write} * and {@link Log#delete} APIs when {@link ApiResponseCallback} callback was not supplied by caller in function parameters. * Note that {@link LogOptions#defaultWriteDeleteCallback} is useful when {@link Log#write} and {@link Log#delete} APIs are called * without `await` and without callback added explicitly to every call - this way {@link LogOptions#defaultWriteDeleteCallback} * can serve as global callback handler, which for example could be used to catch all errors and eliminate crashes. * @param {boolean} [options.partialSuccess] Global flag indicating Whether a batch's valid entries should be written even if * some other entry failed due to errors. Default is true. * See {@link https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write#body.request_body.FIELDS.partial_success|partialSuccess} for more info. * @example * ``` * import {Logging} from '@google-cloud/logging'; * import {LogOptions} from '@google-cloud/logging/build/src/log'; * const options: LogOptions = { * maxEntrySize: 256, * jsonFieldsToTruncate: [ * 'jsonPayload.fields.metadata.structValue.fields.custom.stringValue', * ], * defaultWriteDeleteCallback: (err: any) => { * if (err) { * console.log('Error: ' + err); * } * }, * }; * const logging = new Logging(); * const log = logging.log('syslog', options); * ``` */ class Log { constructor(logging, name, options) { var _a; options = options || {}; this.formattedName_ = (0, log_common_1.formatLogName)(logging.projectId, name); this.removeCircular_ = options.removeCircular === true; this.maxEntrySize = options.maxEntrySize; this.logging = logging; /** * @name Log#name * @type {string} */ this.name = this.formattedName_.split('/').pop(); this.jsonFieldsToTruncate = [ // Winston: 'jsonPayload.fields.metadata.structValue.fields.stack.stringValue', // Bunyan: 'jsonPayload.fields.msg.stringValue', 'jsonPayload.fields.err.structValue.fields.stack.stringValue', 'jsonPayload.fields.err.structValue.fields.message.stringValue', // All: 'jsonPayload.fields.message.stringValue', ]; // Prepend all custom fields to be truncated to a list with defaults, thus // custom fields will be truncated first. Make sure to filter out fields // which are not in EntryJson.jsonPayload if (options.jsonFieldsToTruncate !== null && options.jsonFieldsToTruncate !== undefined) { const filteredList = options.jsonFieldsToTruncate.filter(str => str !== null && !this.jsonFieldsToTruncate.includes(str) && str.startsWith('jsonPayload')); const uniqueSet = new Set(filteredList); this.jsonFieldsToTruncate = Array.from(uniqueSet).concat(this.jsonFieldsToTruncate); } /** * The default callback for {@link Log#write} and {@link Log#delete} APIs * is going to be used only when {@link LogOptions#defaultWriteDeleteCallback} * was set by user and only for APIs which does not accept a callback as parameter */ this.defaultWriteDeleteCallback = options.defaultWriteDeleteCallback; /** Turning partialSuccess by default to be true if not provided in options. This should improve overall logging reliability since only oversized entries will be dropped from request. See {@link https://cloud.google.com/logging/quotas#log-limits} for more info */ this.partialSuccess = (_a = options.partialSuccess) !== null && _a !== void 0 ? _a : true; } alert(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'ALERT'), options); } critical(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'CRITICAL'), options); } debug(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'DEBUG'), options); } async delete(gaxOptions) { const projectId = await this.logging.auth.getProjectId(); this.formattedName_ = (0, log_common_1.formatLogName)(projectId, this.name); const reqOpts = { logName: this.formattedName_, }; return this.logging.loggingService.deleteLog(reqOpts, gaxOptions, this.defaultWriteDeleteCallback); } emergency(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'EMERGENCY'), options); } entry(metadataOrData, data) { let metadata; if (!data && metadataOrData !== null && Object.prototype.hasOwnProperty.call(metadataOrData, 'httpRequest')) { // If user logs entry(metadata.httpRequest) metadata = metadataOrData; data = {}; } else if (!data) { // If user logs entry(message) data = metadataOrData; metadata = {}; } else { // If user logs entry(metadata, message) metadata = metadataOrData; } return this.logging.entry(metadata, data); } error(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'ERROR'), options); } async getEntries(opts) { const options = extend({}, opts); const projectId = await this.logging.auth.getProjectId(); this.formattedName_ = (0, log_common_1.formatLogName)(projectId, this.name); if (options.filter && !options.filter.includes('logName=')) { options.filter = `(${options.filter}) AND logName="${this.formattedName_}"`; } else if (!options.filter) { options.filter = `logName="${this.formattedName_}"`; } return this.logging.getEntries(options); } /** * This method is a wrapper around {module:logging#getEntriesStream}, but with * a filter specified to only return {module:logging/entry} objects from this * log. * * @method Log#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(); * const log = logging.log('my-log'); * * log.getEntriesStream() * .on('error', console.error) * .on('data', 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. * //- * log.getEntriesStream() * .on('data', function(entry) { * this.end(); * }); * ``` */ getEntriesStream(options) { options = extend({ log: this.name, }, options); return this.logging.getEntriesStream(options); } /** * This method is a wrapper around {module:logging#tailEntries}, but with * a filter specified to only return {module:logging/entry} objects from this * log. * * @method Log#tailEntries * @param {TailEntriesRequest} [query] Query object for tailing entries. * @returns {DuplexStream} A duplex stream that emits TailEntriesResponses * containing an array of {@link Entry} instances. * * @example * ``` * const {Logging} = require('@google-cloud/logging'); * const logging = new Logging(); * const log = logging.log('my-log'); * * log.tailEntries() * .on('error', console.error) * .on('data', resp => { * console.log(resp.entries); * console.log(resp.suppressionInfo); * }) * .on('end', function() { * // All entries retrieved. * }); * * //- * // If you anticipate many results, you can end a stream early to prevent * // unnecessary processing and API requests. * //- * log.tailEntries() * .on('data', function(entry) { * this.end(); * }); * ``` */ tailEntries(options) { options = extend({ log: this.name, }, options); return this.logging.tailEntries(options); } info(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'INFO'), options); } notice(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'NOTICE'), options); } warning(entry, options) { return this.write((0, log_common_1.assignSeverityToEntries)(entry, 'WARNING'), options); } async write(entry, opts) { var _a, _b, _c; const options = opts ? opts : {}; // Extract projectId & resource from Logging - inject & memoize if not. await this.logging.setProjectId(); this.formattedName_ = (0, log_common_1.formatLogName)(this.logging.projectId, this.name); const resource = await this.getOrSetResource(options); // Extract & format additional context from individual entries. Make sure to add instrumentation info const info = (0, instrumentation_1.populateInstrumentationInfo)(entry); const decoratedEntries = this.decorateEntries(info[0]); // If instrumentation info was added or this.partialSuccess was set, make sure we set // partialSuccess in outgoing write request, so entire request will make it through and // only oversized entries will be dropped if any if (info[1] || ((_a = options.partialSuccess) !== null && _a !== void 0 ? _a : this.partialSuccess)) { options.partialSuccess = true; } this.truncateEntries(decoratedEntries); // Clobber `labels` and `resource` fields with WriteOptions from the user. const reqOpts = extend({ logName: this.formattedName_, entries: decoratedEntries, resource, }, options); delete reqOpts.gaxOptions; // Propagate maxRetries properly into writeLogEntries call if (!((_b = options.gaxOptions) === null || _b === void 0 ? void 0 : _b.maxRetries) && ((_c = this.logging.options) === null || _c === void 0 ? void 0 : _c.maxRetries)) { options.gaxOptions = extend({ maxRetries: this.logging.options.maxRetries, }, options.gaxOptions); } return this.logging.loggingService.writeLogEntries(reqOpts, options.gaxOptions, this.defaultWriteDeleteCallback); } /** * getOrSetResource looks for GCP service context first at the user * declaration level (snakecasing keys), then in the Logging instance, * before finally detecting a resource from the environment. * The resource is then memoized at the Logging instance level for future use. * * @param options * @private */ async getOrSetResource(options) { if (options.resource) { if (options.resource.labels) (0, log_common_1.snakecaseKeys)(options.resource.labels); return options.resource; } await this.logging.setDetectedResource(); return this.logging.detectedResource; } /** * All entries are passed through here in order be formatted and serialized. * User provided Entry values are formatted per LogEntry specifications. * Read more about the LogEntry format: * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry * * @private * * @param {object[]} entries - Entry objects. * @returns {object[]} Serialized entries. * @throws if there is an error during serialization. */ decorateEntries(entries) { return entries.map(entry => { if (!(entry instanceof entry_1.Entry)) { entry = this.entry(entry); } return entry.toJSON({ removeCircular: this.removeCircular_, }, this.logging.projectId); }); } // TODO consider refactoring `truncateEntries` so that it does not mutate /** * Truncate log entries at maxEntrySize, so that error is not thrown, see: * https://cloud.google.com/logging/quotas * * @private * * @param {object|string} the JSON log entry. * @returns {object|string} truncated JSON log entry. */ truncateEntries(entries) { return entries.forEach(entry => { if (this.maxEntrySize === undefined) return; const payloadSize = JSON.stringify(entry).length; if (payloadSize < this.maxEntrySize) return; let delta = payloadSize - this.maxEntrySize; if (entry.textPayload) { entry.textPayload = entry.textPayload.slice(0, Math.max(entry.textPayload.length - delta, 0)); } else { for (const field of this.jsonFieldsToTruncate) { const msg = dotProp.get(entry, field, ''); if (msg !== null && msg !== undefined && msg !== '') { dotProp.set(entry, field, msg.slice(0, Math.max(msg.length - delta, 0))); delta -= Math.min(msg.length, delta); if (delta <= 0) { break; } } } } }); } // TODO: in a future breaking release, delete this extranenous function. /** * Return an array of log entries with the desired severity assigned. * * @private * * @param {object|object[]} entries - Log entries. * @param {string} severity - The desired severity level. */ static assignSeverityToEntries_(entries, severity) { return (0, log_common_1.assignSeverityToEntries)(entries, severity); } // TODO: in a future breaking release, delete this extranenous function. /** * Format the name of a log. A log's full name is in the format of * 'projects/{projectId}/logs/{logName}'. * * @private * * @returns {string} */ static formatName_(projectId, name) { return (0, log_common_1.formatLogName)(projectId, name); } } exports.Log = Log; /*! Developer Documentation * * All async methods (except for streams) will call a callback in the event * that a callback is provided . */ (0, promisify_1.callbackifyAll)(Log, { exclude: ['entry', 'getEntriesStream'] }); //# sourceMappingURL=log.js.map