mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Very basic support for Sentry.
The Sentry package is very useful for monitoring runtime errors. With this PR, we simply add the necessary mechanism to: - log to sentry any uncaught error that reaches the toplevel, including startup errors; - log to sentry any error that we already log internally as `ERROR`.
This commit is contained in:
parent
e35b855744
commit
27ab1c710f
@ -209,6 +209,20 @@ health:
|
|||||||
# Defaults to 418.
|
# Defaults to 418.
|
||||||
unhealthyStatus: 418
|
unhealthyStatus: 418
|
||||||
|
|
||||||
|
# Sentry options. Sentry is a tool used to receive/collate/triage runtime
|
||||||
|
# errors and performance issues. Skip this section if you do not wish to use
|
||||||
|
# Sentry.
|
||||||
|
sentry:
|
||||||
|
# The key used to upload Sentry data to the server.
|
||||||
|
# dsn: "https://XXXXXXXXX@example.com/YYY
|
||||||
|
|
||||||
|
# Frequency of performance monitoring.
|
||||||
|
# A number in [0.0, 1.0], where 0.0 means "don't bother with tracing"
|
||||||
|
# and 1.0 means "trace performance at every opportunity".
|
||||||
|
# tracesSampleRate: 0.5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Options for exposing web APIs.
|
# Options for exposing web APIs.
|
||||||
web:
|
web:
|
||||||
# Whether to enable web APIs.
|
# Whether to enable web APIs.
|
||||||
|
@ -44,6 +44,8 @@
|
|||||||
"typescript-formatter": "^7.2"
|
"typescript-formatter": "^7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sentry/node": "^7.17.2",
|
||||||
|
"@sentry/tracing": "^7.17.2",
|
||||||
"await-lock": "^2.2.2",
|
"await-lock": "^2.2.2",
|
||||||
"body-parser": "^1.20.1",
|
"body-parser": "^1.20.1",
|
||||||
"config": "^3.3.8",
|
"config": "^3.3.8",
|
||||||
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/node";
|
||||||
import { extractRequestError, LogLevel, LogService, MatrixClient, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk";
|
import { extractRequestError, LogLevel, LogService, MatrixClient, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk";
|
||||||
import { IConfig } from "./config";
|
import { IConfig } from "./config";
|
||||||
import { htmlEscape } from "./utils";
|
import { htmlEscape } from "./utils";
|
||||||
@ -34,7 +35,7 @@ export default class ManagementRoomOutput {
|
|||||||
private readonly managementRoomId: string,
|
private readonly managementRoomId: string,
|
||||||
private readonly client: MatrixClient,
|
private readonly client: MatrixClient,
|
||||||
private readonly config: IConfig,
|
private readonly config: IConfig,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +95,9 @@ export default class ManagementRoomOutput {
|
|||||||
* @param isRecursive Whether logMessage is being called from logMessage.
|
* @param isRecursive Whether logMessage is being called from logMessage.
|
||||||
*/
|
*/
|
||||||
public async logMessage(level: LogLevel, module: string, message: string | any, additionalRoomIds: string[] | string | null = null, isRecursive = false): Promise<any> {
|
public async logMessage(level: LogLevel, module: string, message: string | any, additionalRoomIds: string[] | string | null = null, isRecursive = false): Promise<any> {
|
||||||
|
if (level === LogLevel.ERROR) {
|
||||||
|
Sentry.captureMessage(`${module}: ${message}`, 'error');
|
||||||
|
}
|
||||||
if (!additionalRoomIds) additionalRoomIds = [];
|
if (!additionalRoomIds) additionalRoomIds = [];
|
||||||
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
|
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];
|
||||||
|
|
||||||
@ -115,7 +119,12 @@ export default class ManagementRoomOutput {
|
|||||||
evContent = await this.replaceRoomIdsWithPills(clientMessage, new Set(roomIds), "m.notice");
|
evContent = await this.replaceRoomIdsWithPills(clientMessage, new Set(roomIds), "m.notice");
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.sendMessage(this.managementRoomId, evContent);
|
try {
|
||||||
|
await client.sendMessage(this.managementRoomId, evContent);
|
||||||
|
} catch (ex) {
|
||||||
|
// We want to be informed if we cannot log a message.
|
||||||
|
Sentry.captureException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
levelToFn[level.toString()](module, message);
|
levelToFn[level.toString()](module, message);
|
||||||
|
@ -319,11 +319,10 @@ export class Mjolnir {
|
|||||||
LogService.error("Mjolnir", extractRequestError(err));
|
LogService.error("Mjolnir", extractRequestError(err));
|
||||||
this.stop();
|
this.stop();
|
||||||
await this.managementRoomOutput.logMessage(LogLevel.ERROR, "Mjolnir@startup", "Startup failed due to error - see console");
|
await this.managementRoomOutput.logMessage(LogLevel.ERROR, "Mjolnir@startup", "Startup failed due to error - see console");
|
||||||
throw err;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
LogService.error("Mjolnir", `Failed to report startup error to the management room: ${e}`);
|
LogService.error("Mjolnir", `Failed to report startup error to the management room: ${e}`);
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,3 +601,4 @@ export class Mjolnir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,16 @@ export interface IConfig {
|
|||||||
healthyStatus: number;
|
healthyStatus: number;
|
||||||
unhealthyStatus: number;
|
unhealthyStatus: number;
|
||||||
};
|
};
|
||||||
|
// If specified, attempt to upload any crash statistics to sentry.
|
||||||
|
sentry?: {
|
||||||
|
dsn: string;
|
||||||
|
|
||||||
|
// Frequency of performance monitoring.
|
||||||
|
//
|
||||||
|
// A number in [0.0, 1.0], where 0.0 means "don't bother with tracing"
|
||||||
|
// and 1.0 means "trace performance at every opportunity".
|
||||||
|
tracesSampleRate: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
web: {
|
web: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
11
src/index.ts
11
src/index.ts
@ -15,6 +15,9 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
import { Healthz } from "./health/healthz";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LogLevel,
|
LogLevel,
|
||||||
LogService,
|
LogService,
|
||||||
@ -23,10 +26,10 @@ import {
|
|||||||
RichConsoleLogger,
|
RichConsoleLogger,
|
||||||
SimpleFsStorageProvider
|
SimpleFsStorageProvider
|
||||||
} from "matrix-bot-sdk";
|
} from "matrix-bot-sdk";
|
||||||
|
|
||||||
import { read as configRead } from "./config";
|
import { read as configRead } from "./config";
|
||||||
import { Healthz } from "./health/healthz";
|
|
||||||
import { Mjolnir } from "./Mjolnir";
|
import { Mjolnir } from "./Mjolnir";
|
||||||
import { patchMatrixClient } from "./utils";
|
import { initializeSentry, patchMatrixClient } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
@ -39,6 +42,10 @@ import { patchMatrixClient } from "./utils";
|
|||||||
|
|
||||||
LogService.info("index", "Starting bot...");
|
LogService.info("index", "Starting bot...");
|
||||||
|
|
||||||
|
// Initialize error reporting as early as possible.
|
||||||
|
if (config.health.sentry) {
|
||||||
|
initializeSentry(config);
|
||||||
|
}
|
||||||
const healthz = new Healthz(config);
|
const healthz = new Healthz(config);
|
||||||
healthz.isHealthy = false; // start off unhealthy
|
healthz.isHealthy = false; // start off unhealthy
|
||||||
if (config.health.healthz.enabled) {
|
if (config.health.healthz.enabled) {
|
||||||
|
28
src/utils.ts
28
src/utils.ts
@ -24,7 +24,11 @@ import {
|
|||||||
} from "matrix-bot-sdk";
|
} from "matrix-bot-sdk";
|
||||||
import { ClientRequest, IncomingMessage } from "http";
|
import { ClientRequest, IncomingMessage } from "http";
|
||||||
import { default as parseDuration } from "parse-duration";
|
import { default as parseDuration } from "parse-duration";
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import * as _ from '@sentry/tracing'; // Performing the import activates tracing.
|
||||||
|
|
||||||
import ManagementRoomOutput from "./ManagementRoomOutput";
|
import ManagementRoomOutput from "./ManagementRoomOutput";
|
||||||
|
import { IConfig } from "./config";
|
||||||
|
|
||||||
// Define a few aliases to simplify parsing durations.
|
// Define a few aliases to simplify parsing durations.
|
||||||
|
|
||||||
@ -396,3 +400,27 @@ export function patchMatrixClient() {
|
|||||||
patchMatrixClientForConciseExceptions();
|
patchMatrixClientForConciseExceptions();
|
||||||
patchMatrixClientForRetry();
|
patchMatrixClientForRetry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Sentry for error monitoring and reporting.
|
||||||
|
*
|
||||||
|
* This method is idempotent. If `config` specifies that Sentry
|
||||||
|
* should not be used, it does nothing.
|
||||||
|
*/
|
||||||
|
export function initializeSentry(config: IConfig) {
|
||||||
|
if (sentryInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (config.health.sentry) {
|
||||||
|
// Configure error monitoring with Sentry.
|
||||||
|
let sentry = config.health.sentry;
|
||||||
|
Sentry.init({
|
||||||
|
dsn: sentry.dsn,
|
||||||
|
tracesSampleRate: sentry.tracesSampleRate,
|
||||||
|
});
|
||||||
|
sentryInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Set to `true` once we have initialized `Sentry` to ensure
|
||||||
|
// that we do not attempt to initialize it more than once.
|
||||||
|
let sentryInitialized = false;
|
@ -23,7 +23,7 @@ import {
|
|||||||
} from "matrix-bot-sdk";
|
} from "matrix-bot-sdk";
|
||||||
import { Mjolnir} from '../../src/Mjolnir';
|
import { Mjolnir} from '../../src/Mjolnir';
|
||||||
import { overrideRatelimitForUser, registerUser } from "./clientHelper";
|
import { overrideRatelimitForUser, registerUser } from "./clientHelper";
|
||||||
import { patchMatrixClient } from "../../src/utils";
|
import { initializeSentry, patchMatrixClient } from "../../src/utils";
|
||||||
import { IConfig } from "../../src/config";
|
import { IConfig } from "../../src/config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,6 +49,8 @@ export async function ensureAliasedRoomExists(client: MatrixClient, alias: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function configureMjolnir(config: IConfig) {
|
async function configureMjolnir(config: IConfig) {
|
||||||
|
// Initialize error monitoring as early as possible.
|
||||||
|
initializeSentry(config);
|
||||||
try {
|
try {
|
||||||
await registerUser(config.homeserverUrl, config.pantalaimon.username, config.pantalaimon.username, config.pantalaimon.password, true)
|
await registerUser(config.homeserverUrl, config.pantalaimon.username, config.pantalaimon.username, config.pantalaimon.password, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
59
yarn.lock
59
yarn.lock
@ -104,6 +104,51 @@
|
|||||||
domhandler "^4.2.0"
|
domhandler "^4.2.0"
|
||||||
selderee "^0.6.0"
|
selderee "^0.6.0"
|
||||||
|
|
||||||
|
"@sentry/core@7.22.0":
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.22.0.tgz#8e50f288e5e8fcaa2774daffd2487e042a392893"
|
||||||
|
integrity sha512-qYJiJrL1mfQQln84mNunBRUkXq7xDGQQoNh4Sz9VYP5698va51zmS5BLYRCZ5CkPwRYNuhUqlUXN7bpYGYOOIA==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/types" "7.22.0"
|
||||||
|
"@sentry/utils" "7.22.0"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/node@^7.17.2":
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.22.0.tgz#d575481e56d3326ad457378db5ab7cc804b712fd"
|
||||||
|
integrity sha512-jKhxqKsbEEaY/g3FTzpj1fwukN0IkNv4V+0Fl+t/EmSNUL/7q5FMmDBa+fFQuQzwps8UEpzqPOzMSRapVsoP0w==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/core" "7.22.0"
|
||||||
|
"@sentry/types" "7.22.0"
|
||||||
|
"@sentry/utils" "7.22.0"
|
||||||
|
cookie "^0.4.1"
|
||||||
|
https-proxy-agent "^5.0.0"
|
||||||
|
lru_map "^0.3.3"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/tracing@^7.17.2":
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.22.0.tgz#ec29325ee2c5959670097c104e47a78797cef17b"
|
||||||
|
integrity sha512-s68aSnrRaWQ+Z5oG9ozIegUkofZy9PwicuXYEPA8K/R30F1CVpHvDZ/3KlFnByl+aXTbAyLBQzN2sAObB5g4pQ==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/core" "7.22.0"
|
||||||
|
"@sentry/types" "7.22.0"
|
||||||
|
"@sentry/utils" "7.22.0"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
|
"@sentry/types@7.22.0":
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.22.0.tgz#58e4ce77b523048e0f31e2ea4b597946d76f6079"
|
||||||
|
integrity sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q==
|
||||||
|
|
||||||
|
"@sentry/utils@7.22.0":
|
||||||
|
version "7.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.22.0.tgz#fb46dc2761e2d44cf70bc3e1fba61d55852904b5"
|
||||||
|
integrity sha512-1GiNw1opIngxg0nktCTc9wibh4/LV12kclrnB9dAOHrqazZXHXZRAkjqrhQphKcMpT+3By91W6EofjaDt5a/hg==
|
||||||
|
dependencies:
|
||||||
|
"@sentry/types" "7.22.0"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
"@tootallnate/once@1":
|
"@tootallnate/once@1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||||
@ -829,6 +874,11 @@ cookie@0.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
|
||||||
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
|
||||||
|
|
||||||
|
cookie@^0.4.1:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||||
|
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||||
|
|
||||||
core-util-is@1.0.2:
|
core-util-is@1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
@ -2203,6 +2253,11 @@ lru-cache@^7.10.1:
|
|||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4"
|
||||||
integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==
|
integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ==
|
||||||
|
|
||||||
|
lru_map@^0.3.3:
|
||||||
|
version "0.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||||
|
integrity sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==
|
||||||
|
|
||||||
make-error@^1.1.1:
|
make-error@^1.1.1:
|
||||||
version "1.3.6"
|
version "1.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||||
@ -3436,9 +3491,9 @@ tsconfig-paths@^3.5.0:
|
|||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
tslib@^1.13.0, tslib@^1.8.1:
|
tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.3:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslint@^6.1.3:
|
tslint@^6.1.3:
|
||||||
|
Loading…
Reference in New Issue
Block a user