raygun
Version:
Raygun package for Node.js, written in TypeScript
563 lines (562 loc) • 24.4 kB
JavaScript
/*
* 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 };