Skip to content

Commit

Permalink
Very basic support for Sentry. (#398)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
David Teller authored Nov 30, 2022
1 parent e35b855 commit 2915757
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 9 deletions.
14 changes: 14 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,20 @@ health:
# Defaults to 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://[email protected]/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.
web:
# Whether to enable web APIs.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"typescript-formatter": "^7.2"
},
"dependencies": {
"@sentry/node": "^7.17.2",
"@sentry/tracing": "^7.17.2",
"await-lock": "^2.2.2",
"body-parser": "^1.20.1",
"config": "^3.3.8",
Expand Down
13 changes: 11 additions & 2 deletions src/ManagementRoomOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import * as Sentry from "@sentry/node";
import { extractRequestError, LogLevel, LogService, MatrixClient, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk";
import { IConfig } from "./config";
import { htmlEscape } from "./utils";
Expand All @@ -34,7 +35,7 @@ export default class ManagementRoomOutput {
private readonly managementRoomId: string,
private readonly client: MatrixClient,
private readonly config: IConfig,
) {
) {

}

Expand Down Expand Up @@ -94,6 +95,9 @@ export default class ManagementRoomOutput {
* @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> {
if (level === LogLevel.ERROR) {
Sentry.captureMessage(`${module}: ${message}`, 'error');
}
if (!additionalRoomIds) additionalRoomIds = [];
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];

Expand All @@ -115,7 +119,12 @@ export default class ManagementRoomOutput {
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);
Expand Down
4 changes: 2 additions & 2 deletions src/Mjolnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,10 @@ export class Mjolnir {
LogService.error("Mjolnir", extractRequestError(err));
this.stop();
await this.managementRoomOutput.logMessage(LogLevel.ERROR, "Mjolnir@startup", "Startup failed due to error - see console");
throw err;
} catch (e) {
LogService.error("Mjolnir", `Failed to report startup error to the management room: ${e}`);
throw err;
}
throw err;
}
}

Expand Down Expand Up @@ -602,3 +601,4 @@ export class Mjolnir {
}
}
}

10 changes: 10 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ export interface IConfig {
healthyStatus: 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: {
enabled: boolean;
Expand Down
11 changes: 9 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ limitations under the License.
*/

import * as path from "path";

import { Healthz } from "./health/healthz";

import {
LogLevel,
LogService,
Expand All @@ -23,10 +26,10 @@ import {
RichConsoleLogger,
SimpleFsStorageProvider
} from "matrix-bot-sdk";

import { read as configRead } from "./config";
import { Healthz } from "./health/healthz";
import { Mjolnir } from "./Mjolnir";
import { patchMatrixClient } from "./utils";
import { initializeSentry, patchMatrixClient } from "./utils";


(async function () {
Expand All @@ -39,6 +42,10 @@ import { patchMatrixClient } from "./utils";

LogService.info("index", "Starting bot...");

// Initialize error reporting as early as possible.
if (config.health.sentry) {
initializeSentry(config);
}
const healthz = new Healthz(config);
healthz.isHealthy = false; // start off unhealthy
if (config.health.healthz.enabled) {
Expand Down
28 changes: 28 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
} from "matrix-bot-sdk";
import { ClientRequest, IncomingMessage } from "http";
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 { IConfig } from "./config";

// Define a few aliases to simplify parsing durations.

Expand Down Expand Up @@ -396,3 +400,27 @@ export function patchMatrixClient() {
patchMatrixClientForConciseExceptions();
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;
4 changes: 3 additions & 1 deletion test/integration/mjolnirSetupUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from "matrix-bot-sdk";
import { Mjolnir} from '../../src/Mjolnir';
import { overrideRatelimitForUser, registerUser } from "./clientHelper";
import { patchMatrixClient } from "../../src/utils";
import { initializeSentry, patchMatrixClient } from "../../src/utils";
import { IConfig } from "../../src/config";

/**
Expand All @@ -49,6 +49,8 @@ export async function ensureAliasedRoomExists(client: MatrixClient, alias: strin
}

async function configureMjolnir(config: IConfig) {
// Initialize error monitoring as early as possible.
initializeSentry(config);
try {
await registerUser(config.homeserverUrl, config.pantalaimon.username, config.pantalaimon.username, config.pantalaimon.password, true)
} catch (e) {
Expand Down
59 changes: 57 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,51 @@
domhandler "^4.2.0"
selderee "^0.6.0"

"@sentry/[email protected]":
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/[email protected]":
version "7.22.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.22.0.tgz#58e4ce77b523048e0f31e2ea4b597946d76f6079"
integrity sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q==

"@sentry/[email protected]":
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":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
Expand Down Expand Up @@ -829,6 +874,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
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==

[email protected]:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
Expand Down Expand Up @@ -2203,6 +2253,11 @@ lru-cache@^7.10.1:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4"
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:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
Expand Down Expand Up @@ -3436,9 +3491,9 @@ tsconfig-paths@^3.5.0:
minimist "^1.2.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"
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==

tslint@^6.1.3:
Expand Down

0 comments on commit 2915757

Please sign in to comment.