"use strict";
// Copyright 2021-2024 Google LLC
//
// 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
//
// https://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.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.env = exports.DebugLogBackendBase = exports.placeholder = exports.AdhocDebugLogger = exports.LogSeverity = void 0;
exports.getNodeBackend = getNodeBackend;
exports.getDebugBackend = getDebugBackend;
exports.getStructuredBackend = getStructuredBackend;
exports.setBackend = setBackend;
exports.log = log;
const node_events_1 = require("node:events");
const process = __importStar(require("node:process"));
const util = __importStar(require("node:util"));
const colours_1 = require("./colours");
// Some functions (as noted) are based on the Node standard library, from
// the following file:
//
// https://github.com/nodejs/node/blob/main/lib/internal/util/debuglog.js
/**
* This module defines an ad-hoc debug logger for Google Cloud Platform
* client libraries in Node. An ad-hoc debug logger is a tool which lets
* users use an external, unified interface (in this case, environment
* variables) to determine what logging they want to see at runtime. This
* isn't necessarily fed into the console, but is meant to be under the
* control of the user. The kind of logging that will be produced by this
* is more like "call retry happened", not "event you'd want to record
* in Cloud Logger".
*
* More for Googlers implementing libraries with it:
* go/cloud-client-logging-design
*/
/**
* Possible log levels. These are a subset of Cloud Observability levels.
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
*/
var LogSeverity;
(function (LogSeverity) {
LogSeverity["DEFAULT"] = "DEFAULT";
LogSeverity["DEBUG"] = "DEBUG";
LogSeverity["INFO"] = "INFO";
LogSeverity["WARNING"] = "WARNING";
LogSeverity["ERROR"] = "ERROR";
})(LogSeverity || (exports.LogSeverity = LogSeverity = {}));
/**
* Our logger instance. This actually contains the meat of dealing
* with log lines, including EventEmitter. This contains the function
* that will be passed back to users of the package.
*/
class AdhocDebugLogger extends node_events_1.EventEmitter {
/**
* @param upstream The backend will pass a function that will be
* called whenever our logger function is invoked.
*/
constructor(namespace, upstream) {
super();
this.namespace = namespace;
this.upstream = upstream;
this.func = Object.assign(this.invoke.bind(this), {
// Also add an instance pointer back to us.
instance: this,
// And pull over the EventEmitter functionality.
on: (event, listener) => this.on(event, listener),
});
// Convenience methods for log levels.
this.func.debug = (...args) => this.invokeSeverity(LogSeverity.DEBUG, ...args);
this.func.info = (...args) => this.invokeSeverity(LogSeverity.INFO, ...args);
this.func.warn = (...args) => this.invokeSeverity(LogSeverity.WARNING, ...args);
this.func.error = (...args) => this.invokeSeverity(LogSeverity.ERROR, ...args);
this.func.sublog = (namespace) => log(namespace, this.func);
}
invoke(fields, ...args) {
// Push out any upstream logger first.
if (this.upstream) {
this.upstream(fields, ...args);
}
// Emit sink events.
this.emit('log', fields, args);
}
invokeSeverity(severity, ...args) {
this.invoke({ severity }, ...args);
}
}
exports.AdhocDebugLogger = AdhocDebugLogger;
/**
* This can be used in place of a real logger while waiting for Promises or disabling logging.
*/
exports.placeholder = new AdhocDebugLogger('', () => { }).func;
/**
* The base class for debug logging backends. It's possible to use this, but the
* same non-guarantees above still apply (unstable interface, etc).
*
* @private
* @internal
*/
class DebugLogBackendBase {
constructor() {
var _a;
this.cached = new Map();
this.filters = [];
this.filtersSet = false;
// Look for the Node config variable for what systems to enable. We'll store
// these for the log method below, which will call setFilters() once.
let nodeFlag = (_a = process.env[exports.env.nodeEnables]) !== null && _a !== void 0 ? _a : '*';
if (nodeFlag === 'all') {
nodeFlag = '*';
}
this.filters = nodeFlag.split(',');
}
log(namespace, fields, ...args) {
try {
if (!this.filtersSet) {
this.setFilters();
this.filtersSet = true;
}
let logger = this.cached.get(namespace);
if (!logger) {
logger = this.makeLogger(namespace);
this.cached.set(namespace, logger);
}
logger(fields, ...args);
}
catch (e) {
// Silently ignore all errors; we don't want them to interfere with
// the user's running app.
// e;
console.error(e);
}
}
}
exports.DebugLogBackendBase = DebugLogBackendBase;
// The basic backend. This one definitely works, but it's less feature-filled.
//
// Rather than using util.debuglog, this implements the same basic logic directly.
// The reason for this decision is that debuglog checks the value of the
// NODE_DEBUG environment variable before any user code runs; we therefore
// can't pipe our own enables into it (and util.debuglog will never print unless
// the user duplicates it into NODE_DEBUG, which isn't reasonable).
//
class NodeBackend extends DebugLogBackendBase {
constructor() {
super(...arguments);
// Default to allowing all systems, since we gate earlier based on whether the
// variable is empty.
this.enabledRegexp = /.*/g;
}
isEnabled(namespace) {
return this.enabledRegexp.test(namespace);
}
makeLogger(namespace) {
if (!this.enabledRegexp.test(namespace)) {
return () => { };
}
return (fields, ...args) => {
var _a;
// TODO: `fields` needs to be turned into a string here, one way or another.
const nscolour = `${colours_1.Colours.green}${namespace}${colours_1.Colours.reset}`;
const pid = `${colours_1.Colours.yellow}${process.pid}${colours_1.Colours.reset}`;
let level;
switch (fields.severity) {
case LogSeverity.ERROR:
level = `${colours_1.Colours.red}${fields.severity}${colours_1.Colours.reset}`;
break;
case LogSeverity.INFO:
level = `${colours_1.Colours.magenta}${fields.severity}${colours_1.Colours.reset}`;
break;
case LogSeverity.WARNING:
level = `${colours_1.Colours.yellow}${fields.severity}${colours_1.Colours.reset}`;
break;
default:
level = (_a = fields.severity) !== null && _a !== void 0 ? _a : LogSeverity.DEFAULT;
break;
}
const msg = util.formatWithOptions({ colors: colours_1.Colours.enabled }, ...args);
const filteredFields = Object.assign({}, fields);
delete filteredFields.severity;
const fieldsJson = Object.getOwnPropertyNames(filteredFields).length
? JSON.stringify(filteredFields)
: '';
const fieldsColour = fieldsJson
? `${colours_1.Colours.grey}${fieldsJson}${colours_1.Colours.reset}`
: '';
console.error('%s [%s|%s] %s%s', pid, nscolour, level, msg, fieldsJson ? ` ${fieldsColour}` : '');
};
}
// Regexp patterns below are from here:
// https://github.com/nodejs/node/blob/c0aebed4b3395bd65d54b18d1fd00f071002ac20/lib/internal/util/debuglog.js#L36
setFilters() {
const totalFilters = this.filters.join(',');
const regexp = totalFilters
.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
.replace(/\*/g, '.*')
.replace(/,/g, '$|^');
this.enabledRegexp = new RegExp(`^${regexp}$`, 'i');
}
}
/**
* @returns A backend based on Node util.debuglog; this is the default.
*/
function getNodeBackend() {
return new NodeBackend();
}
class DebugBackend extends DebugLogBackendBase {
constructor(pkg) {
super();
this.debugPkg = pkg;
}
makeLogger(namespace) {
const debugLogger = this.debugPkg(namespace);
return (fields, ...args) => {
// TODO: `fields` needs to be turned into a string here.
debugLogger(args[0], ...args.slice(1));
};
}
setFilters() {
var _a;
const existingFilters = (_a = process.env['NODE_DEBUG']) !== null && _a !== void 0 ? _a : '';
process.env['NODE_DEBUG'] = `${existingFilters}${existingFilters ? ',' : ''}${this.filters.join(',')}`;
}
}
/**
* Creates a "debug" package backend. The user must call require('debug') and pass
* the resulting object to this function.
*
* ```
* setBackend(getDebugBackend(require('debug')))
* ```
*
* https://www.npmjs.com/package/debug
*
* Note: Google does not explicitly endorse or recommend this package; it's just
* being provided as an option.
*
* @returns A backend based on the npm "debug" package.
*/
function getDebugBackend(debugPkg) {
return new DebugBackend(debugPkg);
}
/**
* This pretty much works like the Node logger, but it outputs structured
* logging JSON matching Google Cloud's ingestion specs. Rather than handling
* its own output, it wraps another backend. The passed backend must be a subclass
* of `DebugLogBackendBase` (any of the backends exposed by this package will work).
*/
class StructuredBackend extends DebugLogBackendBase {
constructor(upstream) {
var _a;
super();
this.upstream = (_a = upstream) !== null && _a !== void 0 ? _a : new NodeBackend();
}
makeLogger(namespace) {
const debugLogger = this.upstream.makeLogger(namespace);
return (fields, ...args) => {
var _a;
const severity = (_a = fields.severity) !== null && _a !== void 0 ? _a : LogSeverity.INFO;
const json = Object.assign({
severity,
message: util.format(...args),
}, fields);
const jsonString = JSON.stringify(json);
debugLogger(fields, jsonString);
};
}
setFilters() {
this.upstream.setFilters();
}
}
/**
* Creates a "structured logging" backend. This pretty much works like the
* Node logger, but it outputs structured logging JSON matching Google
* Cloud's ingestion specs instead of plain text.
*
* ```
* setBackend(getStructuredBackend())
* ```
*
* @param upstream If you want to use something besides the Node backend to
* write the actual log lines into, pass that here.
* @returns A backend based on Google Cloud structured logging.
*/
function getStructuredBackend(upstream) {
return new StructuredBackend(upstream);
}
/**
* The environment variables that we standardized on, for all ad-hoc logging.
*/
exports.env = {
/**
* Filter wildcards specific to the Node syntax, and similar to the built-in
* utils.debuglog() environment variable. If missing, disables logging.
*/
nodeEnables: 'GOOGLE_SDK_NODE_LOGGING',
};
// Keep a copy of all namespaced loggers so users can reliably .on() them.
// Note that these cached functions will need to deal with changes in the backend.
const loggerCache = new Map();
// Our current global backend. This might be:
let cachedBackend = undefined;
/**
* Set the backend to use for our log output.
* - A backend object
* - null to disable logging
* - undefined for "nothing yet", defaults to the Node backend
*
* @param backend Results from one of the get*Backend() functions.
*/
function setBackend(backend) {
cachedBackend = backend;
loggerCache.clear();
}
/**
* Creates a logging function. Multiple calls to this with the same namespace
* will produce the same logger, with the same event emitter hooks.
*
* Namespaces can be a simple string ("system" name), or a qualified string
* (system:subsystem), which can be used for filtering, or for "system:*".
*
* @param namespace The namespace, a descriptive text string.
* @returns A function you can call that works similar to console.log().
*/
function log(namespace, parent) {
// If the enable flag isn't set, do nothing.
const enablesFlag = process.env[exports.env.nodeEnables];
if (!enablesFlag) {
return exports.placeholder;
}
// This might happen mostly if the typings are dropped in a user's code,
// or if they're calling from JavaScript.
if (!namespace) {
return exports.placeholder;
}
// Handle sub-loggers.
if (parent) {
namespace = `${parent.instance.namespace}:${namespace}`;
}
// Reuse loggers so things like event sinks are persistent.
const existing = loggerCache.get(namespace);
if (existing) {
return existing.func;
}
// Do we have a backend yet?
if (cachedBackend === null) {
// Explicitly disabled.
return exports.placeholder;
}
else if (cachedBackend === undefined) {
// One hasn't been made yet, so default to Node.
cachedBackend = getNodeBackend();
}
// The logger is further wrapped so we can handle the backend changing out.
const logger = (() => {
let previousBackend = undefined;
const newLogger = new AdhocDebugLogger(namespace, (fields, ...args) => {
if (previousBackend !== cachedBackend) {
// Did the user pass a custom backend?
if (cachedBackend === null) {
// Explicitly disabled.
return;
}
else if (cachedBackend === undefined) {
// One hasn't been made yet, so default to Node.
cachedBackend = getNodeBackend();
}
previousBackend = cachedBackend;
}
cachedBackend === null || cachedBackend === void 0 ? void 0 : cachedBackend.log(namespace, fields, ...args);
});
return newLogger;
})();
loggerCache.set(namespace, logger);
return logger.func;
}
//# sourceMappingURL=logging-utils.js.map