don't log metrics for postgres in testing
Some checks failed
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 1m50s
Publish Docker image / Build (push) Successful in 4m32s
Test (production install and build) / production (22.11.0) (push) Successful in 1m21s
Test (backend) / e2e (22.11.0) (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Test (backend) / unit (22.11.0) (pull_request) Has been cancelled
Test (backend) / e2e (22.11.0) (pull_request) Has been cancelled
Lint / pnpm_install (pull_request) Successful in 1m17s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m29s
Lint / lint (backend) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Publish Docker image / Build (pull_request) Has been cancelled
Lint / lint (backend) (pull_request) Has been cancelled
Lint / lint (frontend) (pull_request) Has been cancelled
Lint / lint (frontend-embed) (pull_request) Has been cancelled
Lint / lint (frontend-shared) (pull_request) Has been cancelled
Lint / lint (misskey-bubble-game) (pull_request) Has been cancelled
Lint / lint (misskey-js) (pull_request) Has been cancelled
Lint / lint (misskey-reversi) (pull_request) Has been cancelled
Lint / lint (sw) (pull_request) Has been cancelled
Lint / typecheck (backend) (pull_request) Has been cancelled
Lint / typecheck (misskey-js) (pull_request) Has been cancelled
Lint / typecheck (sw) (pull_request) Has been cancelled

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
This commit is contained in:
ゆめ 2024-11-17 06:01:25 -06:00
parent 4d44adfaa9
commit 2d111feba9
No known key found for this signature in database
10 changed files with 187 additions and 186 deletions

View file

@ -7,11 +7,6 @@ import Redis from 'ioredis';
import { loadConfig } from '../built/config.js'; import { loadConfig } from '../built/config.js';
import { createPostgresDataSource } from '../built/postgres.js'; import { createPostgresDataSource } from '../built/postgres.js';
const timeout = setTimeout(() => {
console.error('Timeout while connecting to databases.');
process.exit(1);
}, 120000);
const config = loadConfig(); const config = loadConfig();
async function connectToPostgres() { async function connectToPostgres() {
@ -59,5 +54,3 @@ const promises = Array
]); ]);
await Promise.allSettled(promises); await Promise.allSettled(promises);
clearTimeout(timeout);

View file

@ -43,6 +43,12 @@ mBuildInfo.set({
node_version: process.version node_version: process.version
}, 1); }, 1);
const mStartupTime = new prom.Gauge({
name: 'misskey_startup_time',
help: 'Misskey startup time',
labelNames: ['pid']
});
function greet() { function greet() {
if (!envOption.quiet) { if (!envOption.quiet) {
//#region Misskey logo //#region Misskey logo
@ -112,6 +118,8 @@ export async function masterMain() {
}); });
} }
mStartupTime.set({ pid: process.pid }, Date.now());
if (envOption.disableClustering) { if (envOption.disableClustering) {
if (envOption.onlyServer) { if (envOption.onlyServer) {
await server(); await server();

View file

@ -31,6 +31,7 @@ export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>; export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
function withMetrics<T>(queue: Bull.Queue<T>): Bull.Queue<T> { function withMetrics<T>(queue: Bull.Queue<T>): Bull.Queue<T> {
if (process.env.NODE_ENV !== 'test') {
setInterval(async () => { setInterval(async () => {
mActiveJobs.set({ queue: queue.name }, await queue.getActiveCount()); mActiveJobs.set({ queue: queue.name }, await queue.getActiveCount());
mDelayedJobs.set({ queue: queue.name }, await queue.getDelayedCount()); mDelayedJobs.set({ queue: queue.name }, await queue.getDelayedCount());
@ -40,6 +41,7 @@ function withMetrics<T>(queue: Bull.Queue<T>): Bull.Queue<T> {
queue.on('waiting', () => { queue.on('waiting', () => {
mJobReceivedCounter.inc({ queue: queue.name }); mJobReceivedCounter.inc({ queue: queue.name });
}); });
}
return queue; return queue;
} }

View file

@ -40,15 +40,15 @@ import { ApQuestionService } from './models/ApQuestionService.js';
import type { Resolver } from './ApResolverService.js'; import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
@Injectable() const mInboxReceived = new prom.Counter({
export class ApInboxService {
private logger: Logger;
private mInboxReceived = new prom.Counter({
name: 'misskey_ap_inbox_received_total', name: 'misskey_ap_inbox_received_total',
help: 'Total number of activities received by AP inbox', help: 'Total number of activities received by AP inbox',
labelNames: ['host', 'type'], labelNames: ['host', 'type'],
}); });
@Injectable()
export class ApInboxService {
private logger: Logger;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
@ -138,49 +138,49 @@ export class ApInboxService {
if (actor.isSuspended) return; if (actor.isSuspended) return;
if (isCreate(activity)) { if (isCreate(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'create' }); mInboxReceived.inc({ host: actor.host, type: 'create' });
return await this.create(actor, activity); return await this.create(actor, activity);
} else if (isDelete(activity)) { } else if (isDelete(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'delete' }); mInboxReceived.inc({ host: actor.host, type: 'delete' });
return await this.delete(actor, activity); return await this.delete(actor, activity);
} else if (isUpdate(activity)) { } else if (isUpdate(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'update' }); mInboxReceived.inc({ host: actor.host, type: 'update' });
return await this.update(actor, activity); return await this.update(actor, activity);
} else if (isFollow(activity)) { } else if (isFollow(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'follow' }); mInboxReceived.inc({ host: actor.host, type: 'follow' });
return await this.follow(actor, activity); return await this.follow(actor, activity);
} else if (isAccept(activity)) { } else if (isAccept(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'accept' }); mInboxReceived.inc({ host: actor.host, type: 'accept' });
return await this.accept(actor, activity); return await this.accept(actor, activity);
} else if (isReject(activity)) { } else if (isReject(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'reject' }); mInboxReceived.inc({ host: actor.host, type: 'reject' });
return await this.reject(actor, activity); return await this.reject(actor, activity);
} else if (isAdd(activity)) { } else if (isAdd(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'add' }); mInboxReceived.inc({ host: actor.host, type: 'add' });
return await this.add(actor, activity); return await this.add(actor, activity);
} else if (isRemove(activity)) { } else if (isRemove(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'remove' }); mInboxReceived.inc({ host: actor.host, type: 'remove' });
return await this.remove(actor, activity); return await this.remove(actor, activity);
} else if (isAnnounce(activity)) { } else if (isAnnounce(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'announce' }); mInboxReceived.inc({ host: actor.host, type: 'announce' });
return await this.announce(actor, activity); return await this.announce(actor, activity);
} else if (isLike(activity)) { } else if (isLike(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'like' }); mInboxReceived.inc({ host: actor.host, type: 'like' });
return await this.like(actor, activity); return await this.like(actor, activity);
} else if (isUndo(activity)) { } else if (isUndo(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'undo' }); mInboxReceived.inc({ host: actor.host, type: 'undo' });
return await this.undo(actor, activity); return await this.undo(actor, activity);
} else if (isBlock(activity)) { } else if (isBlock(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'block' }); mInboxReceived.inc({ host: actor.host, type: 'block' });
return await this.block(actor, activity); return await this.block(actor, activity);
} else if (isFlag(activity)) { } else if (isFlag(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'flag' }); mInboxReceived.inc({ host: actor.host, type: 'flag' });
return await this.flag(actor, activity); return await this.flag(actor, activity);
} else if (isMove(activity)) { } else if (isMove(activity)) {
this.mInboxReceived.inc({ host: actor.host, type: 'move' }); mInboxReceived.inc({ host: actor.host, type: 'move' });
return await this.move(actor, activity); return await this.move(actor, activity);
} else { } else {
this.mInboxReceived.inc({ host: actor.host, type: 'unknown' }); mInboxReceived.inc({ host: actor.host, type: 'unknown' });
return `unrecognized activity type: ${activity.type}`; return `unrecognized activity type: ${activity.type}`;
} }
} }

View file

@ -342,7 +342,8 @@ export function createPostgresDataSource(config: Config, isMain = false) {
}, },
} : false, } : false,
logging: log ? 'all' : ['query'], logging: log ? 'all' : ['query'],
logger: (isMain || log) ? new MyCustomLogger(!log) : undefined, logger: process.env.NODE_ENV === 'test' ? undefined :
(isMain || log) ? new MyCustomLogger(!log) : undefined,
maxQueryExecutionTime: 500, maxQueryExecutionTime: 500,
entities: entities, entities: entities,
migrations: ['../../migration/*.js'], migrations: ['../../migration/*.js'],

View file

@ -38,41 +38,41 @@ type UpdateInstanceJob = {
shouldUnsuspend: boolean, shouldUnsuspend: boolean,
}; };
@Injectable() const mIncomingApProcessingTime = new prom.Histogram({
export class InboxProcessorService implements OnApplicationShutdown {
private logger: Logger;
private updateInstanceQueue: CollapsedQueue<MiNote['id'], UpdateInstanceJob>;
private mIncomingApProcessingTime = new prom.Histogram({
name: 'misskey_incoming_ap_processing_time', name: 'misskey_incoming_ap_processing_time',
help: 'Incoming AP processing time in seconds', help: 'Incoming AP processing time in seconds',
labelNames: ['incoming_host', 'incoming_type', 'success'], labelNames: ['incoming_host', 'incoming_type', 'success'],
buckets: [0.01, 0.1, 0.5, 1, 5, 10, 30, 60, 300, 1800], buckets: [0.01, 0.1, 0.5, 1, 5, 10, 30, 60, 300, 1800],
}); });
private mIncomingApEvent = new prom.Counter({ const mIncomingApEvent = new prom.Counter({
name: 'misskey_incoming_ap_event', name: 'misskey_incoming_ap_event',
help: 'Incoming AP event', help: 'Incoming AP event',
labelNames: ['incoming_host', 'incoming_type'], labelNames: ['incoming_host', 'incoming_type'],
}); });
private mIncomingApEventAccepted = new prom.Counter({ const mIncomingApEventAccepted = new prom.Counter({
name: 'misskey_incoming_ap_event_accepted', name: 'misskey_incoming_ap_event_accepted',
help: 'Incoming AP event accepted', help: 'Incoming AP event accepted',
labelNames: ['incoming_host', 'incoming_type'], labelNames: ['incoming_host', 'incoming_type'],
}); });
private mIncomingApReject = new prom.Counter({ const mIncomingApReject = new prom.Counter({
name: 'misskey_incoming_ap_reject', name: 'misskey_incoming_ap_reject',
help: 'Incoming AP reject', help: 'Incoming AP reject',
labelNames: ['incoming_host', 'incoming_type', 'reason'], labelNames: ['incoming_host', 'incoming_type', 'reason'],
}); });
private mincomingApProcessingError = new prom.Counter({ const mincomingApProcessingError = new prom.Counter({
name: 'misskey_incoming_ap_processing_error', name: 'misskey_incoming_ap_processing_error',
help: 'Incoming AP processing error', help: 'Incoming AP processing error',
labelNames: ['incoming_host', 'incoming_type'], labelNames: ['incoming_host', 'incoming_type'],
}); });
@Injectable()
export class InboxProcessorService implements OnApplicationShutdown {
private logger: Logger;
private updateInstanceQueue: CollapsedQueue<MiNote['id'], UpdateInstanceJob>;
constructor( constructor(
@Inject(DI.meta) @Inject(DI.meta)
@ -127,13 +127,13 @@ export class InboxProcessorService implements OnApplicationShutdown {
}; };
if (!this.utilityService.isFederationAllowedHost(host)) { if (!this.utilityService.isFederationAllowedHost(host)) {
incCounter(this.mIncomingApReject, { reason: 'host_not_allowed' }); incCounter(mIncomingApReject, { reason: 'host_not_allowed' });
return `Blocked request: ${host}`; return `Blocked request: ${host}`;
} }
const keyIdLower = signature.keyId.toLowerCase(); const keyIdLower = signature.keyId.toLowerCase();
if (keyIdLower.startsWith('acct:')) { if (keyIdLower.startsWith('acct:')) {
incCounter(this.mIncomingApReject, { reason: 'keyid_acct' }); incCounter(mIncomingApReject, { reason: 'keyid_acct' });
return `Old keyId is no longer supported. ${keyIdLower}`; return `Old keyId is no longer supported. ${keyIdLower}`;
} }
@ -151,7 +151,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
// 対象が4xxならスキップ // 対象が4xxならスキップ
if (err instanceof StatusError) { if (err instanceof StatusError) {
if (!err.isRetryable) { if (!err.isRetryable) {
incCounter(this.mIncomingApReject, { reason: 'actor_key_unresolvable' }); incCounter(mIncomingApReject, { reason: 'actor_key_unresolvable' });
throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`); throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
} }
throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`); throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`);
@ -161,13 +161,13 @@ export class InboxProcessorService implements OnApplicationShutdown {
// それでもわからなければ終了 // それでもわからなければ終了
if (authUser == null) { if (authUser == null) {
incCounter(this.mIncomingApReject, { reason: 'actor_unresolvable' }); incCounter(mIncomingApReject, { reason: 'actor_unresolvable' });
throw new Bull.UnrecoverableError('skip: failed to resolve user'); throw new Bull.UnrecoverableError('skip: failed to resolve user');
} }
// publicKey がなくても終了 // publicKey がなくても終了
if (authUser.key == null) { if (authUser.key == null) {
incCounter(this.mIncomingApReject, { reason: 'publickey_unresolvable' }); incCounter(mIncomingApReject, { reason: 'publickey_unresolvable' });
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey'); throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
} }
@ -180,7 +180,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
const ldSignature = activity.signature; const ldSignature = activity.signature;
if (ldSignature) { if (ldSignature) {
if (ldSignature.type !== 'RsaSignature2017') { if (ldSignature.type !== 'RsaSignature2017') {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_unsupported' }); incCounter(mIncomingApReject, { reason: 'ld_signature_unsupported' });
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`); throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
} }
@ -194,12 +194,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
// keyIdからLD-Signatureのユーザーを取得 // keyIdからLD-Signatureのユーザーを取得
authUser = await this.apDbResolverService.getAuthUserFromKeyId(ldSignature.creator); authUser = await this.apDbResolverService.getAuthUserFromKeyId(ldSignature.creator);
if (authUser == null) { if (authUser == null) {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_user_unresolvable' }); incCounter(mIncomingApReject, { reason: 'ld_signature_user_unresolvable' });
throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした'); throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
} }
if (authUser.key == null) { if (authUser.key == null) {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_publickey_unavailable' }); incCounter(mIncomingApReject, { reason: 'ld_signature_publickey_unavailable' });
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした'); throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
} }
@ -208,7 +208,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
// LD-Signature検証 // LD-Signature検証
const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false); const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
if (!verified) { if (!verified) {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_verification_failed' }); incCounter(mIncomingApReject, { reason: 'ld_signature_verification_failed' });
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました'); throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
} }
@ -231,17 +231,17 @@ export class InboxProcessorService implements OnApplicationShutdown {
// もう一度actorチェック // もう一度actorチェック
if (authUser.user.uri !== activity.actor) { if (authUser.user.uri !== activity.actor) {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_actor_mismatch' }); incCounter(mIncomingApReject, { reason: 'ld_signature_actor_mismatch' });
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`); throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
} }
const ldHost = this.utilityService.extractDbHost(authUser.user.uri); const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
if (!this.utilityService.isFederationAllowedHost(ldHost)) { if (!this.utilityService.isFederationAllowedHost(ldHost)) {
incCounter(this.mIncomingApReject, { reason: 'fed_host_not_allowed' }); incCounter(mIncomingApReject, { reason: 'fed_host_not_allowed' });
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`); throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
} }
} else { } else {
incCounter(this.mIncomingApReject, { reason: 'ld_signature_unavailable' }); incCounter(mIncomingApReject, { reason: 'ld_signature_unavailable' });
throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`); throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`);
} }
} }
@ -251,7 +251,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
const signerHost = this.utilityService.extractDbHost(authUser.user.uri!); const signerHost = this.utilityService.extractDbHost(authUser.user.uri!);
const activityIdHost = this.utilityService.extractDbHost(activity.id); const activityIdHost = this.utilityService.extractDbHost(activity.id);
if (signerHost !== activityIdHost) { if (signerHost !== activityIdHost) {
incCounter(this.mIncomingApReject, 'host_signature_mismatch'); incCounter(mIncomingApReject, 'host_signature_mismatch');
throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`); throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`);
} }
} }
@ -279,7 +279,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
this.fetchInstanceMetadataService.fetchInstanceMetadata(i); this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
}); });
incCounter(this.mIncomingApEvent, {}); incCounter(mIncomingApEvent, {});
// アクティビティを処理 // アクティビティを処理
const begin = +new Date(); const begin = +new Date();
@ -292,25 +292,25 @@ export class InboxProcessorService implements OnApplicationShutdown {
} catch (e) { } catch (e) {
if (e instanceof IdentifiableError) { if (e instanceof IdentifiableError) {
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') { if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
incCounter(this.mIncomingApReject, { reason: 'blocked_notes_with_prohibited_words' }); incCounter(mIncomingApReject, { reason: 'blocked_notes_with_prohibited_words' });
return 'blocked notes with prohibited words'; return 'blocked notes with prohibited words';
} }
if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') { if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') {
incCounter(this.mIncomingApReject, { reason: 'actor_suspended' }); incCounter(mIncomingApReject, { reason: 'actor_suspended' });
return 'actor has been suspended'; return 'actor has been suspended';
} }
if (e.id === 'd450b8a9-48e4-4dab-ae36-f4db763fda7c') { // invalid Note if (e.id === 'd450b8a9-48e4-4dab-ae36-f4db763fda7c') { // invalid Note
incCounter(this.mIncomingApReject, { reason: 'invalid_note' }); incCounter(mIncomingApReject, { reason: 'invalid_note' });
return e.message; return e.message;
} }
} }
const end = +new Date(); const end = +new Date();
observeHistogram(this.mIncomingApProcessingTime, { success: 'false' }, (end - begin) / 1000); observeHistogram(mIncomingApProcessingTime, { success: 'false' }, (end - begin) / 1000);
incCounter(this.mincomingApProcessingError, { reason: 'unknown' }); incCounter(mincomingApProcessingError, { reason: 'unknown' });
throw e; throw e;
} }
observeHistogram(this.mIncomingApProcessingTime, { success: 'true' }, (+new Date() - begin) / 1000); observeHistogram(mIncomingApProcessingTime, { success: 'true' }, (+new Date() - begin) / 1000);
incCounter(this.mIncomingApEventAccepted, {}); incCounter(mIncomingApEventAccepted, {});
return 'ok'; return 'ok';
} }

View file

@ -55,65 +55,65 @@ function categorizeRequestPath(path: string): 'api' | 'health' | 'vite' | 'other
return 'other'; return 'other';
} }
@Injectable() const mRequestTime = new prom.Histogram({
export class ServerService implements OnApplicationShutdown {
private logger: Logger;
#fastify: FastifyInstance;
private mRequestTime = new prom.Histogram({
name: 'misskey_http_request_duration_seconds', name: 'misskey_http_request_duration_seconds',
help: 'Duration of handling HTTP requests in seconds', help: 'Duration of handling HTTP requests in seconds',
labelNames: ['host', 'cate', 'method', 'path'], labelNames: ['host', 'cate', 'method', 'path'],
buckets: [0.001, 0.1, 0.5, 1, 2, 5], buckets: [0.001, 0.1, 0.5, 1, 2, 5],
}); });
private mRequestsReceived = new prom.Counter({ const mRequestsReceived = new prom.Counter({
name: 'misskey_http_requests_received_total', name: 'misskey_http_requests_received_total',
help: 'Total number of HTTP requests received', help: 'Total number of HTTP requests received',
labelNames: [], labelNames: [],
}); });
private mNotFoundServed = new prom.Counter({ const mNotFoundServed = new prom.Counter({
name: 'misskey_http_not_found_served_total', name: 'misskey_http_not_found_served_total',
help: 'Total number of HTTP 404 responses served', help: 'Total number of HTTP 404 responses served',
labelNames: ['method', 'cate'], labelNames: ['method', 'cate'],
}); });
private mMethodNotAllowedServed = new prom.Counter({ const mMethodNotAllowedServed = new prom.Counter({
name: 'misskey_http_method_not_allowed_served_total', name: 'misskey_http_method_not_allowed_served_total',
help: 'Total number of HTTP 405 responses served', help: 'Total number of HTTP 405 responses served',
labelNames: ['method', 'cate'], labelNames: ['method', 'cate'],
}); });
private mTooManyRequestsServed = new prom.Counter({ const mTooManyRequestsServed = new prom.Counter({
name: 'misskey_http_too_many_requests_served_total', name: 'misskey_http_too_many_requests_served_total',
help: 'Total number of HTTP 429 responses served', help: 'Total number of HTTP 429 responses served',
labelNames: ['method', 'cate'], labelNames: ['method', 'cate'],
}); });
private mAggregateRequestsServed = new prom.Counter({ const mAggregateRequestsServed = new prom.Counter({
name: 'misskey_http_requests_served_total', name: 'misskey_http_requests_served_total',
help: 'Total number of HTTP requests served including invalid requests', help: 'Total number of HTTP requests served including invalid requests',
labelNames: ['host', 'cate', 'status'], labelNames: ['host', 'cate', 'status'],
}); });
private mRequestsServedByPath = new prom.Counter({ const mRequestsServedByPath = new prom.Counter({
name: 'misskey_http_requests_served_by_path', name: 'misskey_http_requests_served_by_path',
help: 'Total number of HTTP requests served', help: 'Total number of HTTP requests served',
labelNames: ['host', 'cate', 'method', 'path', 'status'], labelNames: ['host', 'cate', 'method', 'path', 'status'],
}); });
private mFatalErrorCount = new prom.Counter({ const mFatalErrorCount = new prom.Counter({
name: 'misskey_fatal_http_errors_total', name: 'misskey_fatal_http_errors_total',
help: 'Total number of HTTP errors that propagate to the top level', help: 'Total number of HTTP errors that propagate to the top level',
labelNames: ['host', 'cate', 'method', 'path'], labelNames: ['host', 'cate', 'method', 'path'],
}); });
private mLastSuccessfulRequest = new prom.Gauge({ const mLastSuccessfulRequest = new prom.Gauge({
name: 'misskey_http_last_successful_request_timestamp_seconds', name: 'misskey_http_last_successful_request_timestamp_seconds',
help: 'Unix Timestamp of the last successful HTTP request', help: 'Unix Timestamp of the last successful HTTP request',
labelNames: [], labelNames: [],
}); });
@Injectable()
export class ServerService implements OnApplicationShutdown {
private logger: Logger;
#fastify: FastifyInstance;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
@ -160,14 +160,14 @@ export class ServerService implements OnApplicationShutdown {
if (this.config.prometheusMetrics?.enable) { if (this.config.prometheusMetrics?.enable) {
fastify.addHook('onRequest', (_request, reply, done) => { fastify.addHook('onRequest', (_request, reply, done) => {
reply.header('x-request-received', (+new Date()).toString()); reply.header('x-request-received', (+new Date()).toString());
this.mRequestsReceived.inc(); mRequestsReceived.inc();
done(); done();
}); });
fastify.addHook('onError', (request, _reply, error, done) => { fastify.addHook('onError', (request, _reply, error, done) => {
const url = new URL(request.url, this.config.url); const url = new URL(request.url, this.config.url);
const logPath = sanitizeRequestURI(url.pathname); const logPath = sanitizeRequestURI(url.pathname);
this.mFatalErrorCount.inc({ mFatalErrorCount.inc({
host: request.hostname, host: request.hostname,
method: request.method, method: request.method,
path: logPath, path: logPath,
@ -182,14 +182,14 @@ export class ServerService implements OnApplicationShutdown {
const cate = categorizeRequestPath(logPath); const cate = categorizeRequestPath(logPath);
const received = reply.getHeader('x-request-received') as string; const received = reply.getHeader('x-request-received') as string;
this.mAggregateRequestsServed.inc({ mAggregateRequestsServed.inc({
host: request.hostname, host: request.hostname,
cate, cate,
status: reply.statusCode, status: reply.statusCode,
}); });
if (reply.statusCode === 429) { if (reply.statusCode === 429) {
this.mTooManyRequestsServed.inc({ mTooManyRequestsServed.inc({
method: request.method, method: request.method,
cate, cate,
}); });
@ -199,14 +199,14 @@ export class ServerService implements OnApplicationShutdown {
} }
if (reply.statusCode === 404) { if (reply.statusCode === 404) {
this.mNotFoundServed.inc({ mNotFoundServed.inc({
method: request.method, method: request.method,
cate, cate,
}); });
if (received) { if (received) {
const duration = (+new Date()) - parseInt(received); const duration = (+new Date()) - parseInt(received);
this.mRequestTime.observe({ mRequestTime.observe({
host: request.hostname, host: request.hostname,
method: request.method, method: request.method,
cate, cate,
@ -218,7 +218,7 @@ export class ServerService implements OnApplicationShutdown {
} }
if (reply.statusCode === 405) { if (reply.statusCode === 405) {
this.mMethodNotAllowedServed.inc({ mMethodNotAllowedServed.inc({
method: request.method, method: request.method,
cate, cate,
}); });
@ -229,7 +229,7 @@ export class ServerService implements OnApplicationShutdown {
if (received) { if (received) {
const duration = (+new Date()) - parseInt(received); const duration = (+new Date()) - parseInt(received);
this.mRequestTime.observe({ mRequestTime.observe({
host: request.hostname, host: request.hostname,
method: request.method, method: request.method,
cate, cate,
@ -243,10 +243,10 @@ export class ServerService implements OnApplicationShutdown {
} }
if (reply.statusCode <= 299) { if (reply.statusCode <= 299) {
this.mLastSuccessfulRequest.set(+new Date() / 1000); mLastSuccessfulRequest.set(+new Date() / 1000);
} }
this.mRequestsServedByPath.inc({ mRequestsServedByPath.inc({
host: request.hostname, host: request.hostname,
method: request.method, method: request.method,
path: logPath, path: logPath,

View file

@ -22,15 +22,15 @@ export class AuthenticationError extends Error {
} }
} }
@Injectable() const mAuthenticationFailureCounter = new prom.Counter({
export class AuthenticateService implements OnApplicationShutdown {
private appCache: MemoryKVCache<MiApp>;
private mAuthenticationFailureCounter = new prom.Counter({
name: 'misskey_authentication_failure_total', name: 'misskey_authentication_failure_total',
help: 'Total number of authentication failures', help: 'Total number of authentication failures',
labelNames: ['cred_ty'], labelNames: ['cred_ty'],
}); });
@Injectable()
export class AuthenticateService implements OnApplicationShutdown {
private appCache: MemoryKVCache<MiApp>;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
@ -58,7 +58,7 @@ export class AuthenticateService implements OnApplicationShutdown {
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>); () => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
if (user == null) { if (user == null) {
this.mAuthenticationFailureCounter.inc({ cred_ty: 'native' }); mAuthenticationFailureCounter.inc({ cred_ty: 'native' });
throw new AuthenticationError('user not found'); throw new AuthenticationError('user not found');
} }
@ -73,7 +73,7 @@ export class AuthenticateService implements OnApplicationShutdown {
}); });
if (accessToken == null) { if (accessToken == null) {
this.mAuthenticationFailureCounter.inc({ cred_ty: 'access_token' }); mAuthenticationFailureCounter.inc({ cred_ty: 'access_token' });
throw new AuthenticationError('invalid signature'); throw new AuthenticationError('invalid signature');
} }

View file

@ -30,14 +30,14 @@ import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
@Injectable() const mSigninFailureCounter = new prom.Counter({
export class SigninApiService {
private mSigninFailureCounter = new prom.Counter({
name: 'misskey_misskey_signin_failure', name: 'misskey_misskey_signin_failure',
help: 'The number of failed sign-ins', help: 'The number of failed sign-ins',
labelNames: ['reason'], labelNames: ['reason'],
}); });
@Injectable()
export class SigninApiService {
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@ -100,7 +100,7 @@ export class SigninApiService {
// not more than 1 attempt per second and not more than 10 attempts per hour // not more than 1 attempt per second and not more than 10 attempts per hour
await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip)); await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
} catch (err) { } catch (err) {
this.mSigninFailureCounter.inc({ reason: 'rate_limit' }); mSigninFailureCounter.inc({ reason: 'rate_limit' });
reply.code(429); reply.code(429);
return { return {
error: { error: {
@ -112,13 +112,13 @@ export class SigninApiService {
} }
if (typeof username !== 'string') { if (typeof username !== 'string') {
this.mSigninFailureCounter.inc({ reason: 'bad_form' }); mSigninFailureCounter.inc({ reason: 'bad_form' });
reply.code(400); reply.code(400);
return; return;
} }
if (token != null && typeof token !== 'string') { if (token != null && typeof token !== 'string') {
this.mSigninFailureCounter.inc({ reason: 'bad_form' }); mSigninFailureCounter.inc({ reason: 'bad_form' });
reply.code(400); reply.code(400);
return; return;
} }
@ -130,14 +130,14 @@ export class SigninApiService {
}) as MiLocalUser; }) as MiLocalUser;
if (user == null) { if (user == null) {
this.mSigninFailureCounter.inc({ reason: 'user_not_found' }); mSigninFailureCounter.inc({ reason: 'user_not_found' });
return error(404, { return error(404, {
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
}); });
} }
if (user.isSuspended) { if (user.isSuspended) {
this.mSigninFailureCounter.inc({ reason: 'user_suspended' }); mSigninFailureCounter.inc({ reason: 'user_suspended' });
return error(403, { return error(403, {
id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
}); });
@ -162,7 +162,7 @@ export class SigninApiService {
} }
if (typeof password !== 'string') { if (typeof password !== 'string') {
this.mSigninFailureCounter.inc({ reason: 'bad_form' }); mSigninFailureCounter.inc({ reason: 'bad_form' });
reply.code(400); reply.code(400);
return; return;
} }
@ -180,7 +180,7 @@ export class SigninApiService {
success: false, success: false,
}); });
this.mSigninFailureCounter.inc({ reason: failure?.id ?? `unknown_error_${status ?? 500}` }); mSigninFailureCounter.inc({ reason: failure?.id ?? `unknown_error_${status ?? 500}` });
return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
}; };
@ -188,35 +188,35 @@ export class SigninApiService {
if (process.env.NODE_ENV !== 'test') { if (process.env.NODE_ENV !== 'test') {
if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) { if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
this.mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_hcaptcha' }); mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_hcaptcha' });
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, err);
}); });
} }
if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) { if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
this.mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_mcaptcha' }); mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_mcaptcha' });
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, err);
}); });
} }
if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) { if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
this.mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_recaptcha' }); mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_recaptcha' });
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, err);
}); });
} }
if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) { if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => { await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
this.mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_turnstile' }); mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_turnstile' });
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, err);
}); });
} }
if (this.meta.enableTestcaptcha) { if (this.meta.enableTestcaptcha) {
await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => {
this.mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_testcaptcha' }); mSigninFailureCounter.inc({ reason: 'captcha_verification_failed_testcaptcha' });
throw new FastifyReplyError(400, err); throw new FastifyReplyError(400, err);
}); });
} }

View file

@ -5,7 +5,6 @@ import { NestFactory } from '@nestjs/core';
import { MainModule } from '@/MainModule.js'; import { MainModule } from '@/MainModule.js';
import { ServerService } from '@/server/ServerService.js'; import { ServerService } from '@/server/ServerService.js';
import { loadConfig } from '@/config.js'; import { loadConfig } from '@/config.js';
import { NestLogger } from '@/NestLogger.js';
import { INestApplicationContext } from '@nestjs/common'; import { INestApplicationContext } from '@nestjs/common';
const config = loadConfig(); const config = loadConfig();
@ -22,10 +21,8 @@ let serverService: ServerService;
async function launch() { async function launch() {
await killTestServer(); await killTestServer();
console.log('starting application...');
app = await NestFactory.createApplicationContext(MainModule, { app = await NestFactory.createApplicationContext(MainModule, {
logger: new NestLogger(), logger: ["debug", "log", "error", "warn", "verbose"],
}); });
serverService = app.get(ServerService); serverService = app.get(ServerService);
await serverService.launch(); await serverService.launch();
@ -84,7 +81,7 @@ async function startControllerEndpoints(port = config.port + 1000) {
console.log('starting application...'); console.log('starting application...');
app = await NestFactory.createApplicationContext(MainModule, { app = await NestFactory.createApplicationContext(MainModule, {
logger: new NestLogger(), logger: ["debug", "log", "error", "warn", "verbose"],
}); });
serverService = app.get(ServerService); serverService = app.get(ServerService);
await serverService.launch(); await serverService.launch();