UNPKG

raygun

Version:

Raygun package for Node.js, written in TypeScript

563 lines (562 loc) 24.4 kB
/* * raygun * https://github.com/MindscapeHQ/raygun4node * * Copyright (c) 2015 MindscapeHQ * Licensed under the MIT license. */ "use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; var breadcrumbs = __importStar(require("./raygun.breadcrumbs")); var raygun_breadcrumbs_express_1 = require("./raygun.breadcrumbs.express"); var raygun_batch_1 = require("./raygun.batch"); var raygun_messageBuilder_1 = require("./raygun.messageBuilder"); var raygun_offline_1 = require("./raygun.offline"); var timer_1 = require("./timer"); var raygunTransport = __importStar(require("./raygun.transport")); var raygunSyncTransport = __importStar(require("./raygun.sync.transport")); var uuid_1 = require("uuid"); var debug = require("debug")("raygun"); var apmBridge = undefined; try { if (module.parent) { apmBridge = module.parent.require("raygun-apm/lib/src/crash_reporting"); } else { apmBridge = require("raygun-apm/lib/src/crash_reporting"); } } catch (e) { apmBridge = null; } var DEFAULT_BATCH_FREQUENCY = 1000; // ms var DEFAULT_TIMEOUT = 5000; // ms function emptyCallback() { } var Raygun = /** @class */ (function () { function Raygun() { this._filters = []; this._version = ""; this._batch = false; } /** * Initializes the Raygun Client. * Use like: * ```js * const raygunClient = new Raygun.Client().init({ * apiKey: 'YOUR_API_KEY' * }); * ``` * @param options - Raygun Client options */ Raygun.prototype.init = function (options) { this._apiKey = options.apiKey; this._filters = options.filters || []; this._host = options.host; this._port = options.port; this._useSSL = options.useSSL !== false; this._onBeforeSend = options.onBeforeSend; this._timeout = options.timeout; this._isOffline = options.isOffline; this._groupingKey = options.groupingKey; this._tags = options.tags; this._useHumanStringForObject = options.useHumanStringForObject === undefined ? true : options.useHumanStringForObject; this._reportColumnNumbers = options.reportColumnNumbers; this._innerErrorFieldName = options.innerErrorFieldName || "cause"; // VError function to retrieve inner error; debug("[raygun.ts] Client initialized"); if (options.reportUncaughtExceptions) { this.reportUncaughtExceptions(); } if (options.batch && this._apiKey) { var frequency = options.batchFrequency || DEFAULT_BATCH_FREQUENCY; this._batch = options.batch; this._batchTransport = new raygun_batch_1.RaygunBatchTransport({ interval: frequency, httpOptions: { host: this._host, port: this._port, useSSL: !!this._useSSL, apiKey: this._apiKey, timeout: this._timeout || DEFAULT_TIMEOUT, }, }); } this.expressHandler = this.expressHandler.bind(this); this.send = this.send.bind(this); this._offlineStorage = options.offlineStorage || new raygun_offline_1.OfflineStorage(this.offlineTransport()); this._offlineStorageOptions = options.offlineStorageOptions; if (this._isOffline) { this._offlineStorage.init(this._offlineStorageOptions); } return this; }; /** * Override this method to provide user data from the send() method original request parameters. * @param req - as RequestParams, may be null if send() was called without providing request parameters. */ Raygun.prototype.user = function (req) { return null; }; /** * If you're using the `raygunClient.expressHandler`, you can send custom data along by setting this function. * * ```js * raygunClient.expressCustomData = function (err, req) { * return { 'level': err.level }; * }; * ``` * * @param error - error captured * @param request - original request object */ Raygun.prototype.expressCustomData = function (error, request) { return {}; }; /** * Set the version of the calling application * @param version - Version as String */ Raygun.prototype.setVersion = function (version) { this._version = version; return this; }; /** * Access or mutate the candidate error payload immediately before it is sent, * or skip the sending action by returning null. * @param onBeforeSend - callback that must return a Message object to send, or null to skip sending it. */ Raygun.prototype.onBeforeSend = function (onBeforeSend) { this._onBeforeSend = onBeforeSend; return this; }; /** * Set the grouping key. Also, available as `init()` configuration parameter. * @param groupingKey - grouping key method callback */ Raygun.prototype.groupingKey = function (groupingKey) { this._groupingKey = groupingKey; return this; }; /** * Notifies the Raygun Client that the machine is offline. * Raygun Crash Reports will be stored and sent when the machine is back online. */ Raygun.prototype.offline = function () { this.offlineStorage().init(this._offlineStorageOptions); this._isOffline = true; }; /** * Notifies the Raygun Client that the machine is online. * Stored Raygun Crash Reports will be delivered. * @param callback - sent result callback */ Raygun.prototype.online = function (callback) { this._isOffline = false; this.offlineStorage().send(callback || emptyCallback); }; /** * Set global tags to Raygun Client * @param tags - list of Tags */ Raygun.prototype.setTags = function (tags) { this._tags = tags; }; /** * Adds breadcrumb to current context * @param breadcrumb - either a string message or a Breadcrumb object */ Raygun.prototype.addBreadcrumb = function (breadcrumb) { breadcrumbs.addBreadcrumb(breadcrumb); }; /** * Manually clear stored breadcrumbs for current context */ Raygun.prototype.clearBreadcrumbs = function () { breadcrumbs.clear(); }; Raygun.prototype.transport = function () { if (this._batch && this._batchTransport) { return this._batchTransport; } return raygunTransport; }; /** * Sends exception to Raygun. * @param exception - exception to send. * @param customData - custom data to attach to the error report. * @param request - custom RequestParams. * @param tags - list of Tags to attach to the error report. * @param timestamp - provides a custom timestamp as Date object or number in milliseconds since epoch. * @param userInfo - provides the user information to this error report. Has priority over the user(request) method. * @returns IncomingMessage if message was delivered, null if stored, rejected with Error if failed. */ Raygun.prototype.send = function (exception_1) { return __awaiter(this, arguments, void 0, function (exception, _a) { var _timestamp, sendOptionsResult, message, sendOptions, stopTimer_1; var _this = this; var _b = _a === void 0 ? {} : _a, customData = _b.customData, request = _b.request, tags = _b.tags, timestamp = _b.timestamp, userInfo = _b.userInfo; return __generator(this, function (_c) { _timestamp = typeof timestamp === "number" ? new Date(timestamp) : timestamp; sendOptionsResult = this.buildSendOptions(exception, customData, request, tags, _timestamp, userInfo); message = sendOptionsResult.message; if (!sendOptionsResult.valid) { console.error("[Raygun4Node] Encountered an error sending an error to Raygun. No API key is configured, please ensure .init is called with api key. See docs for more info."); return [2 /*return*/, Promise.reject(sendOptionsResult.message)]; } if (sendOptionsResult.skip) { console.log("[Raygun4Node] Skip sending message: ".concat(sendOptionsResult.message.details.error, ".")); return [2 /*return*/, Promise.resolve(null)]; } sendOptions = sendOptionsResult.options; if (this._isOffline) { // Server is offline, store in Offline Storage return [2 /*return*/, new Promise(function (resolve, reject) { _this.offlineStorage().save(JSON.stringify(message), function (error) { if (error) { console.error("[Raygun4Node] Error storing message while offline", error); reject(error); } else { debug("[raygun.ts] Stored message while offline"); // Resolved value is null because message is stored resolve(null); } }); })]; } else { stopTimer_1 = (0, timer_1.startTimer)(); // Use current transport to send request. // Transport can be batch or default. return [2 /*return*/, this.transport() .send(sendOptions) .then(function (response) { var durationInMs = stopTimer_1(); debug("[raygun.ts] Successfully sent message (duration=".concat(durationInMs, "ms)")); return response; }) .catch(function (error) { var durationInMs = stopTimer_1(); debug("[raygun.ts] Error sending message (duration=".concat(durationInMs, "ms): ").concat(error)); return error; })]; } return [2 /*return*/]; }); }); }; Raygun.prototype.reportUncaughtExceptions = function () { var _this = this; var _a = process.versions.node .split(".") .map(function (part) { return parseInt(part, 10); }), major = _a[0], minor = _a[1]; if (major < 12 || (major === 12 && minor < 17) || (major === 13 && minor < 7)) { console.log("[Raygun4Node] Warning: reportUncaughtExceptions requires at least Node v12.17.0 or v13.7.0. Uncaught exceptions will not be automatically reported."); return; } // Handles uncaught exceptions, e.g. missing try/catch on a throw. process.on("uncaughtExceptionMonitor", function (e) { _this.sendSync(e, ["uncaughtException"]); }); // Handles uncaught Promise rejections, e.g. missing .catch(...) on a Promise. // Unhandled Promise rejections are not caught by the "uncaughtExceptionMonitor". process.on("unhandledRejection", function (reason, promise) { if (reason instanceof Error || typeof reason === "string") { _this.sendSync(reason, ["unhandledRejection"]); } else { // `reason` type is unknown, wrap in String _this.sendSync("Unhandled Rejection: ".concat(reason), ["unhandledRejection"]); } }); }; /** * Send error using synchronous transport. * Only used internally to report uncaught exceptions or unhandled promises. * @param exception - error to report * @param tags - optional tags */ Raygun.prototype.sendSync = function (exception, tags) { var result = this.buildSendOptions(exception, null, undefined, tags); if (result.valid) { if (result.skip) { console.log("[Raygun4Node] Skip sending message: ".concat(result.message.details.error, ".")); return; } raygunSyncTransport.send(result.options); } }; /** * Attach as express middleware to create a breadcrumb store scope per request. * e.g. `app.use(raygun.expressHandlerBreadcrumbs);` * Then call to `raygun.addBreadcrumb(...)` to add breadcrumbs to the future Raygun `send` call. * @param req - Express Request * @param res - Express response * @param next - Next Express function */ Raygun.prototype.expressHandlerBreadcrumbs = function (req, res, next) { breadcrumbs.runWithBreadcrumbs(function () { (0, raygun_breadcrumbs_express_1.addRequestBreadcrumb)(req); // Make the current breadcrumb store available to the express error handler res.locals.breadcrumbs = breadcrumbs.getBreadcrumbs(); next(); }); }; /** * Attach as express middleware to report application errors to Raygun automatically. * e.g. `app.use(raygun.expressHandler);` * @param err - Captured Error by Express * @param req - Express Request * @param res - Express response * @param next - Next Express function */ Raygun.prototype.expressHandler = function (err, req, res, next) { var _this = this; var _a; var customData; if (typeof this.expressCustomData === "function") { customData = this.expressCustomData(err, req); } else { customData = this.expressCustomData; } // Convert the Express Request to an object that can be sent to Raygun var requestParams = { hostname: req.hostname, path: req.path, method: req.method, ip: (_a = req.ip) !== null && _a !== void 0 ? _a : "", query: req.query, headers: req.headers, body: req.body, }; var sendParams = { customData: customData || {}, request: requestParams, tags: ["UnhandledException"], }; // If a local store of breadcrumbs exist in the response // run in scoped breadcrumbs store if (res.locals && res.locals.breadcrumbs) { breadcrumbs.runWithBreadcrumbs(function () { debug("sending express error with scoped breadcrumbs store"); _this.send(err, sendParams).catch(function (err) { console.error("[Raygun] Failed to send Express error", err); }); }, res.locals.breadcrumbs); } else { debug("sending express error with global breadcrumbs store"); // Otherwise, run with the global breadcrumbs store this.send(err, sendParams) .then(function (response) { // Clear global breadcrumbs store after successful sent breadcrumbs.clear(); }) .catch(function (err) { console.error("[Raygun] Failed to send Express error", err); }); } next(err); }; Raygun.prototype.stop = function () { if (this._batchTransport) { debug("[raygun.ts] Batch transport stopped"); this._batchTransport.stopProcessing(); } }; Raygun.prototype.buildSendOptions = function (exception, customData, request, tags, timestamp, userInfo) { var mergedTags = []; var skip = false; if (this._tags) { mergedTags = mergedTags.concat(this._tags); } if (tags) { mergedTags = mergedTags.concat(tags); } var builder = new raygun_messageBuilder_1.RaygunMessageBuilder({ filters: this._filters, useHumanStringForObject: this._useHumanStringForObject, reportColumnNumbers: this._reportColumnNumbers, innerErrorFieldName: this._innerErrorFieldName, timestamp: timestamp, }) .setErrorDetails(exception) .setRequestDetails(request) .setMachineName() .setEnvironmentDetails() .setUserCustomData(customData) .setUser(userInfo || this.user(request) || this._user) .setVersion(this._version) .setBreadcrumbs(breadcrumbs.getBreadcrumbs()) .setTags(mergedTags); var message = builder.build(); if (this._groupingKey) { message.details.groupingKey = typeof this._groupingKey === "function" ? this._groupingKey(message, exception, customData, request, tags) : null; } if (this._onBeforeSend && typeof this._onBeforeSend === "function") { var _message = this._onBeforeSend(message, exception, customData, request, tags); if (_message) { message = _message; } else { debug("[raygun.ts] onBeforeSend returned null, cancelling send"); skip = true; } } if (apmBridge) { var correlationId = (0, uuid_1.v4)(); apmBridge.notify(exception, correlationId); message.details.correlationId = correlationId; } var apiKey = this._apiKey; if (!apiKey) { return { valid: false, message: message, }; } return { valid: true, message: message, skip: skip, options: __assign({ message: JSON.stringify(message) }, (this._batch ? {} : { http: { host: this._host, port: this._port, useSSL: !!this._useSSL, apiKey: apiKey, timeout: this._timeout || DEFAULT_TIMEOUT, }, })), }; }; Raygun.prototype.offlineTransport = function () { var transport = this.transport(); var httpOptions = { host: this._host, port: this._port, useSSL: this._useSSL || false, apiKey: this._apiKey || "", timeout: this._timeout || DEFAULT_TIMEOUT, }; return { send: function (message) { transport .send(__assign({ message: message }, (transport instanceof raygun_batch_1.RaygunBatchTransport ? {} : { http: httpOptions }))) .then(function (response) { if (!(transport instanceof raygun_batch_1.RaygunBatchTransport)) { debug("[raygun.ts] Sent message from offline transport: ".concat(response === null || response === void 0 ? void 0 : response.statusCode, " ").concat(response === null || response === void 0 ? void 0 : response.statusMessage)); } }) .catch(function (error) { console.error("[Raygun4Node] Failed to send message from offline transport", error); }); }, }; }; Raygun.prototype.offlineStorage = function () { var storage = this._offlineStorage; if (storage) { return storage; } storage = this._offlineStorage = new raygun_offline_1.OfflineStorage(this.offlineTransport()); return storage; }; return Raygun; }()); exports.Client = Raygun; exports.default = { Client: exports.Client };