feat: add protocol interaction observability
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, ConflictException, ForbiddenException, forwardRef, HttpException, Inject, Injectable, NotFoundException, OnModuleDestroy, OnModuleInit, UnprocessableEntityException } from '@nestjs/common';
|
||||
import { BadRequestException, ConflictException, ForbiddenException, forwardRef, HttpException, Inject, Injectable, NotFoundException, OnModuleDestroy, OnModuleInit, Optional, UnprocessableEntityException } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { Queue, Worker } from 'bullmq';
|
||||
import { createHash, createHmac, randomBytes, randomUUID } from 'node:crypto';
|
||||
@@ -10,6 +10,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SendChainService } from '../send-chain/send-chain.service';
|
||||
import { decryptSecret, encryptSecret } from './open-api.crypto';
|
||||
import type { OpenApiAuthContext } from './open-api.types';
|
||||
import { ProtocolLogsService } from '../protocol-logs/protocol-logs.service';
|
||||
|
||||
const WEBHOOK_QUEUE = 'http-webhook-delivery';
|
||||
const DELIVERY_MODES = ['cmpp', 'http', 'both', 'none'] as const;
|
||||
@@ -45,7 +46,11 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
private queue?: Queue<{ deliveryId: string }>;
|
||||
private worker?: Worker<{ deliveryId: string }>;
|
||||
|
||||
constructor(private readonly prisma: PrismaService, @Inject(forwardRef(() => SendChainService)) private readonly sendChain: SendChainService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(forwardRef(() => SendChainService)) private readonly sendChain: SendChainService,
|
||||
@Optional() private readonly protocolLogs?: ProtocolLogsService,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
const connection = bullmqConnection();
|
||||
@@ -203,6 +208,21 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
const response = { code: 'ACCEPTED', requestId, messageId: message?.messageId, clientMessageId: input.clientMessageId ?? null, status: message?.status ?? task.status, acceptedAt: new Date().toISOString() };
|
||||
await this.prisma.openApiRequest.update({ where: { id: request.id }, data: { status: 'completed', httpStatus: 202, businessCode: 'ACCEPTED', responseBody: response, messageRecordId: message?.id, durationMs: Date.now() - startedAt, completedAt: new Date() } });
|
||||
this.protocolLogs?.record({
|
||||
protocol: 'http',
|
||||
direction: 'client_to_platform',
|
||||
eventType: 'send_request',
|
||||
status: 'accepted',
|
||||
tenantId: auth.application.tenantId,
|
||||
applicationId: auth.application.id,
|
||||
messageId: message?.messageId,
|
||||
requestId,
|
||||
phone: mobile,
|
||||
resultCode: 'ACCEPTED',
|
||||
durationMs: Date.now() - startedAt,
|
||||
payloadBytes: Buffer.byteLength(content, 'utf8'),
|
||||
detail: { clientMessageId: input.clientMessageId },
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
let outwardError = error;
|
||||
@@ -213,6 +233,19 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
const failure = normalizeOpenApiFailure(outwardError);
|
||||
await this.prisma.openApiRequest.update({ where: { id: request.id }, data: { status: 'failed', httpStatus: failure.httpStatus, businessCode: failure.code, responseBody: failure.responseBody, durationMs: Date.now() - startedAt, completedAt: new Date() } });
|
||||
this.protocolLogs?.record({
|
||||
protocol: 'http',
|
||||
direction: 'client_to_platform',
|
||||
eventType: 'send_request',
|
||||
status: 'failed',
|
||||
tenantId: auth.application.tenantId,
|
||||
applicationId: auth.application.id,
|
||||
requestId,
|
||||
phone: mobile,
|
||||
resultCode: failure.code,
|
||||
durationMs: Date.now() - startedAt,
|
||||
payloadBytes: Buffer.byteLength(content, 'utf8'),
|
||||
});
|
||||
throw outwardError;
|
||||
}
|
||||
}
|
||||
@@ -326,6 +359,21 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
const success = responseStatus !== undefined && responseStatus >= 200 && responseStatus < 300;
|
||||
const retryable = errorMessage !== undefined || responseStatus === 408 || responseStatus === 429 || (responseStatus !== undefined && responseStatus >= 500);
|
||||
await this.prisma.httpWebhookAttempt.create({ data: { deliveryId, attemptNo, responseStatus, responseSummary, errorMessage, durationMs: Date.now() - startedAt, requestHeaders: { 'x-event-id': delivery.event.eventId, 'x-event-type': delivery.event.eventType, 'x-timestamp': timestamp, 'x-signature': 'sha256=***' } } });
|
||||
this.protocolLogs?.record({
|
||||
protocol: 'http',
|
||||
direction: 'platform_to_client',
|
||||
eventType: `${delivery.event.eventType}_webhook`,
|
||||
status: success ? 'success' : retryable ? 'retrying' : 'failed',
|
||||
tenantId: delivery.event.tenantId,
|
||||
applicationId: delivery.event.applicationId,
|
||||
messageId: delivery.event.messageId,
|
||||
requestId: delivery.event.eventId,
|
||||
resultCode: responseStatus ?? 'NETWORK_ERROR',
|
||||
durationMs: Date.now() - startedAt,
|
||||
payloadBytes: Buffer.byteLength(body, 'utf8'),
|
||||
retryCount: attemptNo - 1,
|
||||
detail: { deliveryId, attemptNo, error: errorMessage },
|
||||
});
|
||||
if (success) {
|
||||
await this.prisma.httpWebhookDelivery.update({ where: { id: deliveryId }, data: { status: 'delivered', attemptCount: attemptNo, lastHttpStatus: responseStatus, lastError: null, deliveredAt: new Date(), nextRetryAt: null } });
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user