Skip to content

Commit

Permalink
Merge remote-branch 'misskey/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
noridev committed May 29, 2024
2 parents 5e202d3 + cf670e8 commit 9bfc9bc
Show file tree
Hide file tree
Showing 13 changed files with 781 additions and 37 deletions.
15 changes: 15 additions & 0 deletions .config/docker_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,21 @@ redis:

id: 'aidx'

# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────

# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.

#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

Expand Down
15 changes: 15 additions & 0 deletions .config/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ redis:

id: 'aidx'

# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────

# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.

#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

Expand Down
15 changes: 15 additions & 0 deletions .devcontainer/devcontainer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ redis:

id: 'aidx'

# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────

# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.

#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- 管理者向け権限 `read:admin:show-users``read:admin:show-user` に統合されました。必要に応じてAPIトークンを再発行してください。

### General
- Feat: エラートラッキングにSentryを使用できるようになりました
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
- Enhance: アンテナでBotによるノートを除外できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
Expand Down
16 changes: 16 additions & 0 deletions chart/files/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ redis:
# ID SETTINGS AFTER THAT!

id: "aidx"

# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────

# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.

#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'

# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
"node": ">=20.10.0"
"node": "^20.10.0"
},
"scripts": {
"start": "node ./built/boot/entry.js",
Expand Down Expand Up @@ -89,6 +89,8 @@
"@nestjs/core": "10.3.8",
"@nestjs/testing": "10.3.8",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "^8.5.0",
"@sentry/profiling-node": "^8.5.0",
"@simplewebauthn/server": "10.0.0",
"@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.5.0",
Expand Down
20 changes: 20 additions & 0 deletions packages/backend/src/boot/master.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -74,6 +76,24 @@ export async function masterMain() {

bootLogger.succ(chalk.hex('#ffa9c3')('Cherry') + chalk.hex('#95e3e8')('Pick') + (' 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();
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> & {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -183,6 +186,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;
Expand Down Expand Up @@ -253,6 +258,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,
Expand Down
7 changes: 4 additions & 3 deletions packages/backend/src/core/CustomEmojiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,10 +349,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<Record<string, string>> {
const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost)));
const res = {} as any;
const res = {} as Record<string, string>;
for (let i = 0; i < emojiNames.length; i++) {
if (emojis[i] != null) {
res[emojiNames[i]] = emojis[i];
const resolvedEmoji = emojis[i];
if (resolvedEmoji != null) {
res[emojiNames[i]] = resolvedEmoji;
}
}
return res;
Expand Down
14 changes: 7 additions & 7 deletions packages/backend/src/core/DriveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,20 +510,20 @@ export class DriveService {

if (user && !force) {
// Check if there is a file with the same hash
const much = await this.driveFilesRepository.findOneBy({
const matched = await this.driveFilesRepository.findOneBy({
md5: info.md5,
userId: user.id,
});

if (much) {
this.registerLogger.info(`file with same hash is found: ${much.id}`);
if (sensitive && !much.isSensitive) {
if (matched) {
this.registerLogger.info(`file with same hash is found: ${matched.id}`);
if (sensitive && !matched.isSensitive) {
// The file is federated as sensitive for this time, but was federated as non-sensitive before.
// Therefore, update the file to sensitive.
await this.driveFilesRepository.update({ id: much.id }, { isSensitive: true });
much.isSensitive = true;
await this.driveFilesRepository.update({ id: matched.id }, { isSensitive: true });
matched.isSensitive = true;
}
return much;
return matched;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/FetchInstanceMetadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class FetchInstanceMetadataService {
throw new Error('No wellknown links');
}

const links = wellknown.links as any[];
const links = wellknown.links as ({ rel: string, href: string; })[];

const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
Expand Down
77 changes: 52 additions & 25 deletions packages/backend/src/server/api/ApiCallService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 type { FlashToken } from '@/misc/flash-token.js';
import { ApiError } from './error.js';
import { RateLimiterService } from './RateLimiterService.js';
Expand All @@ -39,6 +41,9 @@ export class ApiCallService implements OnApplicationShutdown {
private userIpHistoriesClearIntervalId: NodeJS.Timeout;

constructor(
@Inject(DI.config)
private config: Config,

@Inject(DI.userIpsRepository)
private userIpsRepository: UserIpsRepository,

Expand Down Expand Up @@ -89,6 +94,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 },
Expand Down Expand Up @@ -372,31 +419,11 @@ export class ApiCallService implements OnApplicationShutdown {
}

// API invoking
return await ep.exec(data, user, token, flashToken, 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, flashToken, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err));
}
}

@bindThis
Expand Down
Loading

0 comments on commit 9bfc9bc

Please sign in to comment.