summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2024-05-28 17:06:33 +0900
committerGitHub <noreply@github.com>2024-05-28 17:06:33 +0900
commit80f3cb96b02eaaeb513670224d33b8842414963e (patch)
treead54a85773701b2d60b73bd09ae7bf471451c1ff /packages/backend/src
parentfix(federation): InboxにきたCreate, AnnounceのobjectがBearcaps urlだっ... (diff)
downloadsharkey-80f3cb96b02eaaeb513670224d33b8842414963e.tar.gz
sharkey-80f3cb96b02eaaeb513670224d33b8842414963e.tar.bz2
sharkey-80f3cb96b02eaaeb513670224d33b8842414963e.zip
feat: sentry integration (#13897)
* wip * wip * wip * wip * Update CHANGELOG.md * Update ApiCallService.ts * Update config.ts
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/boot/master.ts20
-rw-r--r--packages/backend/src/config.ts7
-rw-r--r--packages/backend/src/server/api/ApiCallService.ts77
3 files changed, 79 insertions, 25 deletions
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 30f9477ccf..75e1a80cd1 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -10,6 +10,8 @@ import * as os from 'node:os';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
+import * as Sentry from '@sentry/node';
+import { nodeProfilingIntegration } from '@sentry/profiling-node';
import Logger from '@/logger.js';
import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js';
@@ -71,6 +73,24 @@ export async function masterMain() {
bootLogger.succ('Misskey initialized');
+ if (config.sentryForBackend) {
+ Sentry.init({
+ integrations: [
+ ...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []),
+ ],
+
+ // Performance Monitoring
+ tracesSampleRate: 1.0, // Capture 100% of the transactions
+
+ // Set sampling rate for profiling - this is relative to tracesSampleRate
+ profilesSampleRate: 1.0,
+
+ maxBreadcrumbs: 0,
+
+ ...config.sentryForBackend.options,
+ });
+ }
+
if (envOption.disableClustering) {
if (envOption.onlyServer) {
await server();
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 0ca1fa55c1..0ac521d409 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -7,6 +7,7 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
+import * as Sentry from '@sentry/node';
import type { RedisOptions } from 'ioredis';
type RedisOptionsSource = Partial<RedisOptions> & {
@@ -56,6 +57,8 @@ type Source = {
index: string;
scope?: 'local' | 'global' | string[];
};
+ sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
+ sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
@@ -166,6 +169,8 @@ export type Config = {
redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource;
+ sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
+ sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
@@ -234,6 +239,8 @@ export function loadConfig(): Config {
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
+ sentryForBackend: config.sentryForBackend,
+ sentryForFrontend: config.sentryForFrontend,
id: config.id,
proxy: config.proxy,
proxySmtp: config.proxySmtp,
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 9836689872..271ef80554 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -7,6 +7,7 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common';
+import * as Sentry from '@sentry/node';
import { DI } from '@/di-symbols.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
@@ -17,6 +18,7 @@ import { MetaService } from '@/core/MetaService.js';
import { createTemp } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
+import type { Config } from '@/config.js';
import { ApiError } from './error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
@@ -38,6 +40,9 @@ export class ApiCallService implements OnApplicationShutdown {
private userIpHistoriesClearIntervalId: NodeJS.Timeout;
constructor(
+ @Inject(DI.config)
+ private config: Config,
+
@Inject(DI.userIpsRepository)
private userIpsRepository: UserIpsRepository,
@@ -88,6 +93,48 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
+ #onExecError(ep: IEndpoint, data: any, err: Error): void {
+ if (err instanceof ApiError || err instanceof AuthenticationError) {
+ throw err;
+ } else {
+ const errId = randomUUID();
+ this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
+ ep: ep.name,
+ ps: data,
+ e: {
+ message: err.message,
+ code: err.name,
+ stack: err.stack,
+ id: errId,
+ },
+ });
+ console.error(err, errId);
+
+ if (this.config.sentryForBackend) {
+ Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
+ extra: {
+ ep: ep.name,
+ ps: data,
+ e: {
+ message: err.message,
+ code: err.name,
+ stack: err.stack,
+ id: errId,
+ },
+ },
+ });
+ }
+
+ throw new ApiError(null, {
+ e: {
+ message: err.message,
+ code: err.name,
+ id: errId,
+ },
+ });
+ }
+ }
+
@bindThis
public handleRequest(
endpoint: IEndpoint & { exec: any },
@@ -362,31 +409,11 @@ export class ApiCallService implements OnApplicationShutdown {
}
// API invoking
- return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => {
- if (err instanceof ApiError || err instanceof AuthenticationError) {
- throw err;
- } else {
- const errId = randomUUID();
- this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
- ep: ep.name,
- ps: data,
- e: {
- message: err.message,
- code: err.name,
- stack: err.stack,
- id: errId,
- },
- });
- console.error(err, errId);
- throw new ApiError(null, {
- e: {
- message: err.message,
- code: err.name,
- id: errId,
- },
- });
- }
- });
+ if (this.config.sentryForBackend) {
+ return await Sentry.startSpan({ name: 'API: ' + ep.name }, () => ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err)));
+ } else {
+ return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err));
+ }
}
@bindThis