perf(backend): lazy load sentry

This commit is contained in:
syuilo 2025-11-30 14:04:41 +09:00
parent fe01a5a28f
commit d55f51a69b
4 changed files with 42 additions and 28 deletions

View File

@ -10,8 +10,6 @@ import * as os from 'node:os';
import cluster from 'node:cluster'; import cluster from 'node:cluster';
import chalk from 'chalk'; import chalk from 'chalk';
import chalkTemplate from 'chalk-template'; import chalkTemplate from 'chalk-template';
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
import Logger from '@/logger.js'; import Logger from '@/logger.js';
import { loadConfig } from '@/config.js'; import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -74,6 +72,9 @@ export async function masterMain() {
bootLogger.succ('Misskey initialized'); bootLogger.succ('Misskey initialized');
if (config.sentryForBackend) { if (config.sentryForBackend) {
const Sentry = await import('@sentry/node');
const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
Sentry.init({ Sentry.init({
integrations: [ integrations: [
...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []), ...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []),

View File

@ -4,8 +4,6 @@
*/ */
import cluster from 'node:cluster'; import cluster from 'node:cluster';
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
import { envOption } from '@/env.js'; import { envOption } from '@/env.js';
import { loadConfig } from '@/config.js'; import { loadConfig } from '@/config.js';
import { jobQueue, server } from './common.js'; import { jobQueue, server } from './common.js';
@ -17,6 +15,9 @@ export async function workerMain() {
const config = loadConfig(); const config = loadConfig();
if (config.sentryForBackend) { if (config.sentryForBackend) {
const Sentry = await import('@sentry/node');
const { nodeProfilingIntegration } = await import('@sentry/profiling-node');
Sentry.init({ Sentry.init({
integrations: [ integrations: [
...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []), ...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []),

View File

@ -5,7 +5,6 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import * as Bull from 'bullmq'; import * as Bull from 'bullmq';
import * as Sentry from '@sentry/node';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
@ -157,6 +156,13 @@ export class QueueProcessorService implements OnApplicationShutdown {
}; };
} }
let Sentry: typeof import('@sentry/node') | undefined;
if (Sentry != null) {
import('@sentry/node').then((mod) => {
Sentry = mod;
});
}
//#region system //#region system
{ {
const processer = (job: Bull.Job) => { const processer = (job: Bull.Job) => {
@ -175,7 +181,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
}; };
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => { this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: System: ' + job.name }, () => processer(job)); return Sentry.startSpan({ name: 'Queue: System: ' + job.name }, () => processer(job));
} else { } else {
return processer(job); return processer(job);
@ -192,7 +198,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err: Error) => { .on('failed', (job, err: Error) => {
logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -232,7 +238,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
}; };
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => { this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: DB: ' + job.name }, () => processer(job)); return Sentry.startSpan({ name: 'Queue: DB: ' + job.name }, () => processer(job));
} else { } else {
return processer(job); return processer(job);
@ -249,7 +255,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -264,7 +270,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region deliver //#region deliver
{ {
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => { this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: Deliver' }, () => this.deliverProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: Deliver' }, () => this.deliverProcessorService.process(job));
} else { } else {
return this.deliverProcessorService.process(job); return this.deliverProcessorService.process(job);
@ -289,7 +295,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -304,7 +310,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region inbox //#region inbox
{ {
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => { this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: Inbox' }, () => this.inboxProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: Inbox' }, () => this.inboxProcessorService.process(job));
} else { } else {
return this.inboxProcessorService.process(job); return this.inboxProcessorService.process(job);
@ -329,7 +335,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -344,7 +350,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region user-webhook deliver //#region user-webhook deliver
{ {
this.userWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.USER_WEBHOOK_DELIVER, (job) => { this.userWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.USER_WEBHOOK_DELIVER, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: UserWebhookDeliver' }, () => this.userWebhookDeliverProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: UserWebhookDeliver' }, () => this.userWebhookDeliverProcessorService.process(job));
} else { } else {
return this.userWebhookDeliverProcessorService.process(job); return this.userWebhookDeliverProcessorService.process(job);
@ -369,7 +375,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -384,7 +390,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region system-webhook deliver //#region system-webhook deliver
{ {
this.systemWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.SYSTEM_WEBHOOK_DELIVER, (job) => { this.systemWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.SYSTEM_WEBHOOK_DELIVER, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: SystemWebhookDeliver' }, () => this.systemWebhookDeliverProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: SystemWebhookDeliver' }, () => this.systemWebhookDeliverProcessorService.process(job));
} else { } else {
return this.systemWebhookDeliverProcessorService.process(job); return this.systemWebhookDeliverProcessorService.process(job);
@ -409,7 +415,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`); logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -434,7 +440,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
}; };
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => { this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: Relationship: ' + job.name }, () => processer(job)); return Sentry.startSpan({ name: 'Queue: Relationship: ' + job.name }, () => processer(job));
} else { } else {
return processer(job); return processer(job);
@ -456,7 +462,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -479,7 +485,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
}; };
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => { this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: ObjectStorage: ' + job.name }, () => processer(job)); return Sentry.startSpan({ name: 'Queue: ObjectStorage: ' + job.name }, () => processer(job));
} else { } else {
return processer(job); return processer(job);
@ -497,7 +503,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => { .on('failed', (job, err) => {
logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) }); logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
if (config.sentryForBackend) { if (Sentry != null) {
Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, { Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
level: 'error', level: 'error',
extra: { job, err }, extra: { job, err },
@ -512,7 +518,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region ended poll notification //#region ended poll notification
{ {
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => { this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: EndedPollNotification' }, () => this.endedPollNotificationProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: EndedPollNotification' }, () => this.endedPollNotificationProcessorService.process(job));
} else { } else {
return this.endedPollNotificationProcessorService.process(job); return this.endedPollNotificationProcessorService.process(job);
@ -527,7 +533,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
//#region post scheduled note //#region post scheduled note
{ {
this.postScheduledNoteQueueWorker = new Bull.Worker(QUEUE.POST_SCHEDULED_NOTE, async (job) => { this.postScheduledNoteQueueWorker = new Bull.Worker(QUEUE.POST_SCHEDULED_NOTE, async (job) => {
if (this.config.sentryForBackend) { if (Sentry != null) {
return Sentry.startSpan({ name: 'Queue: PostScheduledNote' }, () => this.postScheduledNoteProcessorService.process(job)); return Sentry.startSpan({ name: 'Queue: PostScheduledNote' }, () => this.postScheduledNoteProcessorService.process(job));
} else { } else {
return this.postScheduledNoteProcessorService.process(job); return this.postScheduledNoteProcessorService.process(job);

View File

@ -7,7 +7,6 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as stream from 'node:stream/promises'; import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as Sentry from '@sentry/node';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { getIpHash } from '@/misc/get-ip-hash.js'; import { getIpHash } from '@/misc/get-ip-hash.js';
import type { MiLocalUser, MiUser } from '@/models/User.js'; import type { MiLocalUser, MiUser } from '@/models/User.js';
@ -37,6 +36,7 @@ export class ApiCallService implements OnApplicationShutdown {
private logger: Logger; private logger: Logger;
private userIpHistories: Map<MiUser['id'], Set<string>>; private userIpHistories: Map<MiUser['id'], Set<string>>;
private userIpHistoriesClearIntervalId: NodeJS.Timeout; private userIpHistoriesClearIntervalId: NodeJS.Timeout;
private Sentry: typeof import('@sentry/node') | null = null;
constructor( constructor(
@Inject(DI.meta) @Inject(DI.meta)
@ -59,6 +59,12 @@ export class ApiCallService implements OnApplicationShutdown {
this.userIpHistoriesClearIntervalId = setInterval(() => { this.userIpHistoriesClearIntervalId = setInterval(() => {
this.userIpHistories.clear(); this.userIpHistories.clear();
}, 1000 * 60 * 60); }, 1000 * 60 * 60);
if (this.config.sentryForBackend) {
import('@sentry/node').then((Sentry) => {
this.Sentry = Sentry;
});
}
} }
#sendApiError(reply: FastifyReply, err: ApiError): void { #sendApiError(reply: FastifyReply, err: ApiError): void {
@ -120,8 +126,8 @@ export class ApiCallService implements OnApplicationShutdown {
}, },
}); });
if (this.config.sentryForBackend) { if (this.Sentry != null) {
Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, { this.Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
level: 'error', level: 'error',
user: { user: {
id: userId, id: userId,
@ -432,8 +438,8 @@ export class ApiCallService implements OnApplicationShutdown {
} }
// API invoking // API invoking
if (this.config.sentryForBackend) { if (this.Sentry != null) {
return await Sentry.startSpan({ return await this.Sentry.startSpan({
name: 'API: ' + ep.name, name: 'API: ' + ep.name,
}, () => ep.exec(data, user, token, file, request.ip, request.headers) }, () => ep.exec(data, user, token, file, request.ip, request.headers)
.catch((err: Error) => this.#onExecError(ep, data, err, user?.id))); .catch((err: Error) => this.#onExecError(ep, data, err, user?.id)));