feat: add logging
This commit is contained in:
+56
-46
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable, ServiceUnavailableException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
ChannelCredentials,
|
||||
@@ -17,6 +13,7 @@ import { loadSync } from '@grpc/proto-loader';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { Environment } from '../config/env.validation';
|
||||
import { StructuredLogger } from '../common/logging/structured-logger.service';
|
||||
|
||||
const requestDeadlineMs = 15_000;
|
||||
|
||||
@@ -129,13 +126,15 @@ class RateLimiter {
|
||||
|
||||
@Injectable()
|
||||
export class TbankClient {
|
||||
private readonly logger = new Logger(TbankClient.name);
|
||||
private readonly queue: RateLimiter;
|
||||
private readonly users: TbankClientService;
|
||||
private readonly operations: TbankClientService;
|
||||
private readonly instruments: TbankClientService;
|
||||
|
||||
constructor(config: ConfigService<Environment, true>) {
|
||||
constructor(
|
||||
config: ConfigService<Environment, true>,
|
||||
private readonly logger: StructuredLogger,
|
||||
) {
|
||||
const sourceProtoRoot = join(process.cwd(), 'src/tbank/proto/contracts');
|
||||
const protoRoot = existsSync(sourceProtoRoot)
|
||||
? sourceProtoRoot
|
||||
@@ -174,14 +173,7 @@ export class TbankClient {
|
||||
this.queue = new RateLimiter(
|
||||
config.getOrThrow<number>('TBANK_RATE_LIMIT_PER_SECOND'),
|
||||
);
|
||||
this.logger.log({
|
||||
event: 'client_configured',
|
||||
endpoint,
|
||||
rateLimitPerSecond: config.getOrThrow<number>(
|
||||
'TBANK_RATE_LIMIT_PER_SECOND',
|
||||
),
|
||||
customCa: Boolean(caPath),
|
||||
});
|
||||
this.logger.info('tbank.client.configured');
|
||||
}
|
||||
|
||||
async validateToken(token: string): Promise<void> {
|
||||
@@ -242,41 +234,59 @@ export class TbankClient {
|
||||
client: TbankClientService,
|
||||
method: string,
|
||||
request: object,
|
||||
retry = true,
|
||||
): Promise<Response> {
|
||||
if (this.queue.size >= 50)
|
||||
throw new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' });
|
||||
try {
|
||||
return await this.queue.run(() =>
|
||||
this.request<Response>(token, client, method, request),
|
||||
);
|
||||
} catch (error) {
|
||||
const code = (error as ServiceError).code;
|
||||
const retryable = [
|
||||
status.UNAVAILABLE,
|
||||
status.DEADLINE_EXCEEDED,
|
||||
status.RESOURCE_EXHAUSTED,
|
||||
].includes(code);
|
||||
this.logger.warn({
|
||||
event: 'tbank_rpc_failed',
|
||||
method,
|
||||
grpcCode: code,
|
||||
retryable,
|
||||
...(method === 'getInstrumentBy'
|
||||
? {
|
||||
instrumentIdType: (request as { idType?: string }).idType,
|
||||
instrumentId: (request as { id?: string }).id,
|
||||
}
|
||||
: {}),
|
||||
if (this.queue.size >= 50) {
|
||||
this.logger.event('error', 'tbank.rpc.failed', {
|
||||
operation: method,
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
attempts: 0,
|
||||
});
|
||||
if (retry && retryable) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return this.call(token, client, method, request, false);
|
||||
}
|
||||
if ([status.UNAUTHENTICATED, status.PERMISSION_DENIED].includes(code))
|
||||
throw new Error('INVALID_TBANK_TOKEN');
|
||||
throw new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' });
|
||||
}
|
||||
|
||||
for (let attempts = 1; attempts <= 2; attempts += 1) {
|
||||
const startedAt = performance.now();
|
||||
try {
|
||||
const response = await this.queue.run(() =>
|
||||
this.request<Response>(token, client, method, request),
|
||||
);
|
||||
this.logger.event('debug', 'tbank.rpc.completed', {
|
||||
operation: method,
|
||||
attempts,
|
||||
durationMs: Math.round(performance.now() - startedAt),
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
const grpcCode = (error as ServiceError).code;
|
||||
const retryable = [
|
||||
status.UNAVAILABLE,
|
||||
status.DEADLINE_EXCEEDED,
|
||||
status.RESOURCE_EXHAUSTED,
|
||||
].includes(grpcCode);
|
||||
if (retryable && attempts < 2) {
|
||||
this.logger.event('warn', 'tbank.rpc.retry', {
|
||||
operation: method,
|
||||
grpcCode,
|
||||
attempts,
|
||||
durationMs: Math.round(performance.now() - startedAt),
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
continue;
|
||||
}
|
||||
this.logger.event('error', 'tbank.rpc.failed', {
|
||||
operation: method,
|
||||
grpcCode,
|
||||
attempts,
|
||||
durationMs: Math.round(performance.now() - startedAt),
|
||||
});
|
||||
if (
|
||||
[status.UNAUTHENTICATED, status.PERMISSION_DENIED].includes(grpcCode)
|
||||
)
|
||||
throw new Error('INVALID_TBANK_TOKEN');
|
||||
throw new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' });
|
||||
}
|
||||
}
|
||||
throw new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' });
|
||||
}
|
||||
|
||||
private request<Response>(
|
||||
|
||||
Reference in New Issue
Block a user