@google-cloud/logging
Version:
Cloud Logging Client Library for Node.js
405 lines • 17.2 kB
JavaScript
"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