Track connected account webhook subscription lifecycle metrics (#23710)

Emits created/renewed/deleted counters and their failure counterparts
from the messaging and calendar webhook subscription services.

Each counter carries channel_type and provider attributes so the Grafana
panels can break them down. Infra side: twentyhq/twenty-infra#841

Co-authored-by: neo773 <huzef@twenty.com>
This commit is contained in:
neo773
2026-08-03 19:21:17 +05:30
committed by GitHub
parent 22d83c75e6
commit 96ea1e1ffc
4 changed files with 100 additions and 0 deletions
@@ -77,4 +77,10 @@ export enum MetricsKeys {
SdkClientGenerationDurationMs = 'sdk-client-generation/duration-ms',
ConnectedAccountSyncWebhookReceivedMessaging = 'connected-account-sync-webhook/received/messaging',
ConnectedAccountSyncWebhookReceivedCalendar = 'connected-account-sync-webhook/received/calendar',
ConnectedAccountWebhookSubscriptionCreated = 'connected-account-webhook-subscription/created',
ConnectedAccountWebhookSubscriptionCreationFailed = 'connected-account-webhook-subscription/creation-failed',
ConnectedAccountWebhookSubscriptionRenewed = 'connected-account-webhook-subscription/renewed',
ConnectedAccountWebhookSubscriptionRenewalFailed = 'connected-account-webhook-subscription/renewal-failed',
ConnectedAccountWebhookSubscriptionDeleted = 'connected-account-webhook-subscription/deleted',
ConnectedAccountWebhookSubscriptionDeletionFailed = 'connected-account-webhook-subscription/deletion-failed',
}
@@ -10,6 +10,8 @@ import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { WebhookSubscriptionDriverFactory } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-subscription-driver-factory.service';
@@ -24,6 +26,7 @@ export class CalendarWebhookSubscriptionService {
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
private readonly webhookSubscriptionDriverFactory: WebhookSubscriptionDriverFactory,
private readonly exceptionHandlerService: ExceptionHandlerService,
private readonly metricsService: MetricsService,
) {}
async createSubscription(
@@ -82,6 +85,12 @@ export class CalendarWebhookSubscriptionService {
webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
webhookSubscriptionExpiresAt: result.expiresAt,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreated,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
await this.calendarChannelRepository.update(calendarChannel.id, {
webhookSubscriptionClientState: clientState,
@@ -89,6 +98,12 @@ export class CalendarWebhookSubscriptionService {
webhookSubscriptionExpiresAt: null,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreationFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: workspaceId },
});
@@ -149,11 +164,23 @@ export class CalendarWebhookSubscriptionService {
webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
webhookSubscriptionExpiresAt: result.expiresAt,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
await this.calendarChannelRepository.update(calendarChannel.id, {
webhookSubscriptionStatus: WebhookSubscriptionStatus.FAILED,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewalFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: calendarChannel.workspaceId },
});
@@ -191,13 +218,32 @@ export class CalendarWebhookSubscriptionService {
try {
await driver.deleteSubscription(this.toContext(calendarChannel));
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeleted,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeletionFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: calendarChannel.workspaceId },
});
}
}
private buildMetricAttributes(provider: string) {
return {
channel_type: WebhookSubscriptionChannelType.CALENDAR,
provider,
};
}
private toContext(
calendarChannel: CalendarChannelEntity,
): WebhookSubscriptionContext {
@@ -10,6 +10,8 @@ import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { WebhookSubscriptionDriverFactory } from 'src/modules/connected-account/webhook-subscription-manager/services/webhook-subscription-driver-factory.service';
@@ -24,6 +26,7 @@ export class MessagingWebhookSubscriptionService {
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
private readonly webhookSubscriptionDriverFactory: WebhookSubscriptionDriverFactory,
private readonly exceptionHandlerService: ExceptionHandlerService,
private readonly metricsService: MetricsService,
) {}
async createSubscription(
@@ -81,6 +84,12 @@ export class MessagingWebhookSubscriptionService {
webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
webhookSubscriptionExpiresAt: result.expiresAt,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreated,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
await this.messageChannelRepository.update(messageChannel.id, {
webhookSubscriptionClientState: clientState,
@@ -88,6 +97,12 @@ export class MessagingWebhookSubscriptionService {
webhookSubscriptionExpiresAt: null,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionCreationFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: workspaceId },
});
@@ -147,11 +162,23 @@ export class MessagingWebhookSubscriptionService {
webhookSubscriptionStatus: WebhookSubscriptionStatus.ACTIVE,
webhookSubscriptionExpiresAt: result.expiresAt,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
await this.messageChannelRepository.update(messageChannel.id, {
webhookSubscriptionStatus: WebhookSubscriptionStatus.FAILED,
});
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionRenewalFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: messageChannel.workspaceId },
});
@@ -189,13 +216,32 @@ export class MessagingWebhookSubscriptionService {
try {
await driver.deleteSubscription(this.toContext(messageChannel));
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeleted,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
} catch (error) {
this.metricsService.incrementCounterBy({
key: MetricsKeys.ConnectedAccountWebhookSubscriptionDeletionFailed,
amount: 1,
attributes: this.buildMetricAttributes(connectedAccount.provider),
});
this.exceptionHandlerService.captureExceptions([error], {
workspace: { id: messageChannel.workspaceId },
});
}
}
private buildMetricAttributes(provider: string) {
return {
channel_type: WebhookSubscriptionChannelType.MESSAGING,
provider,
};
}
private toContext(
messageChannel: MessageChannelEntity,
): WebhookSubscriptionContext {
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
@@ -21,6 +22,7 @@ import { WebhookSubscriptionManagerModule } from 'src/modules/connected-account/
imports: [
WebhookSubscriptionManagerModule,
FeatureFlagModule,
MetricsModule,
WorkspaceIteratorModule,
TypeOrmModule.forFeature([
WorkspaceEntity,