bunyan-gcp-logging
Version:
Bunyan Transport for GCP (Google Cloud Platform) using Cloud Logging
119 lines (118 loc) • 4.37 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createStream = exports.GCPCloudLoggingTransformer = void 0;
const util_1 = require("util");
const bunyan_1 = __importDefault(require("bunyan"));
const stream_1 = require("stream");
// Map of Cloud Logging levels.
const BUNYAN_TO_GCP_CLOUD_LOGGING = new Map([
[60, 'CRITICAL'],
[50, 'ERROR'],
[40, 'WARNING'],
[30, 'INFO'],
[20, 'DEBUG'],
[10, 'DEBUG'],
]);
/**
* TAKEN from bunyan source code. (but modified)
*
* A fast JSON.stringify that handles cycles and getter exceptions (when
* safeJsonStringify is installed).
*
* This function attempts to use the regular JSON.stringify for speed, but on
* error (e.g. JSON cycle detection exception) it falls back to safe stringify
* handlers that can deal with cycles and/or getter exceptions.
*/
function fastAndSafeJsonStringify(rec) {
try {
return JSON.stringify(rec);
}
catch (ex) {
try {
return JSON.stringify(rec, bunyan_1.default.safeCycles());
}
catch (e) {
return util_1.format('(Exception in JSON.stringify(rec): %j. ', e.message);
}
}
}
class GCPCloudLoggingTransformer extends stream_1.Transform {
constructor() {
super({
writableObjectMode: true,
});
}
_transform(chunk, encoding, callback) {
if (typeof chunk === 'string') {
callback(new Error('Bad configuration. Use raw stream type on bunyan'));
return;
}
let entry;
try {
entry = this.formatEntry(chunk);
}
catch (err) {
callback(err);
return;
}
callback(undefined, fastAndSafeJsonStringify(entry) + '\n');
}
/**
* Format a bunyan record into a Stackdriver log entry.
*/
formatEntry(record) {
var _a, _b;
// extract field we want to transform or discard
const { name: logName, msg, level, err, v, hostname, pid, time: timestamp, ...others } = record;
const entry = {
logName,
timestamp,
message: (_a = record === null || record === void 0 ? void 0 : record.message) !== null && _a !== void 0 ? _a : msg,
severity: BUNYAN_TO_GCP_CLOUD_LOGGING.get(Number(level)),
...others,
};
/**
* If this is an error, report the full stack trace. This allows
* Cloud Logging Error Reporting to pick up errors automatically (for
* severity 'error' or higher). In this case we leave the 'msg' property
* intact.
*
* @see https://cloud.google.com/error-reporting/docs/formatting-error-messages
*/
if (err && (err === null || err === void 0 ? void 0 : err.stack)) {
entry.message = err.stack;
entry.serviceContext = { service: logName };
}
/**
* Try to get HTTP Request from log data to make an log on the GCP Cloud Logggin format.
*/
const req = (_b = record === null || record === void 0 ? void 0 : record.httpRequest) !== null && _b !== void 0 ? _b : record === null || record === void 0 ? void 0 : record.req;
if (req)
entry.httpRequest = this.formatHttpEntry(req);
return entry;
}
formatHttpEntry(request) {
var _a, _b, _c;
const httpRequest = {
requestMethod: (_a = request === null || request === void 0 ? void 0 : request.method) !== null && _a !== void 0 ? _a : '',
requestUrl: (_b = request === null || request === void 0 ? void 0 : request.url) !== null && _b !== void 0 ? _b : '',
remoteIp: (_c = request === null || request === void 0 ? void 0 : request.remoteAddress) !== null && _c !== void 0 ? _c : '',
...request,
};
return httpRequest;
}
}
exports.GCPCloudLoggingTransformer = GCPCloudLoggingTransformer;
function createStream(level, out) {
const transformer = new GCPCloudLoggingTransformer();
transformer.pipe(out || process.stdout);
return {
level,
type: 'raw',
stream: transformer,
};
}
exports.createStream = createStream;