Skip to content

Commit

Permalink
feat: add custom spans in trace
Browse files Browse the repository at this point in the history
  • Loading branch information
KishenKumarrrrr committed Nov 22, 2024
1 parent 3ccb15a commit 566e538
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 198 deletions.
69 changes: 37 additions & 32 deletions backend/src/email/middlewares/email-callback.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Request, Response, NextFunction } from 'express'
import { EmailCallbackService } from '@email/services'
import { loggerWithLabel } from '@core/logger'
import { tracer } from 'dd-trace'

const logger = loggerWithLabel(module)

Expand All @@ -9,22 +10,24 @@ const isAuthenticated = (
res: Response,
next: NextFunction
): Response | void => {
const authHeader = req.get('authorization')
if (!authHeader) {
// SNS will send 2 request:
// - first one without the basic authorization first and require the callback
// server to respond with 401 WWW-Authenticate Basic realm="Email"
// - second one with the basic authorization
// The above mechanism is based on RFC-2671 https://www.rfc-editor.org/rfc/rfc2617.html#page-8
// Of course, this middleare is to reject all requests without the
// Authorization header as well
res.set('WWW-Authenticate', 'Basic realm="Email"')
return res.sendStatus(401)
}
if (EmailCallbackService.isAuthenticated(authHeader)) {
return next()
}
return res.sendStatus(403)
tracer.wrap('isAuthenticated', () => {
const authHeader = req.get('authorization')
if (!authHeader) {
// SNS will send 2 request:
// - first one without the basic authorization first and require the callback
// server to respond with 401 WWW-Authenticate Basic realm="Email"
// - second one with the basic authorization
// The above mechanism is based on RFC-2671 https://www.rfc-editor.org/rfc/rfc2617.html#page-8
// Of course, this middleare is to reject all requests without the
// Authorization header as well
res.set('WWW-Authenticate', 'Basic realm="Email"')
return res.sendStatus(401)
}
if (EmailCallbackService.isAuthenticated(authHeader)) {
return next()
}
return res.sendStatus(403)
})
}

const parseEvent = async (
Expand All @@ -45,23 +48,25 @@ const printConfirmSubscription = (
res: Response,
next: NextFunction
): Response | void => {
const { Type: type, SubscribeURL: subscribeUrl } = JSON.parse(req.body)
if (type === 'SubscriptionConfirmation') {
const parsed = new URL(subscribeUrl)
if (
parsed.protocol === 'https:' &&
/^sns\.[a-zA-Z0-9-]{3,}\.amazonaws\.com(\.cn)?$/.test(parsed.host)
) {
logger.info({
message: 'Confirm the subscription',
type,
subscribeUrl,
action: 'printConfirmSubscription',
})
return res.sendStatus(202)
tracer.wrap('printConfirmSubscription', () => {
const { Type: type, SubscribeURL: subscribeUrl } = JSON.parse(req.body)
if (type === 'SubscriptionConfirmation') {
const parsed = new URL(subscribeUrl)
if (
parsed.protocol === 'https:' &&
/^sns\.[a-zA-Z0-9-]{3,}\.amazonaws\.com(\.cn)?$/.test(parsed.host)
) {
logger.info({
message: 'Confirm the subscription',
type,
subscribeUrl,
action: 'printConfirmSubscription',
})
return res.sendStatus(202)
}
}
}
return next()
return next()
})
}
export const EmailCallbackMiddleware = {
isAuthenticated,
Expand Down
3 changes: 3 additions & 0 deletions backend/src/email/services/email-callback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Request } from 'express'
import { ses, sendgrid } from '@email/utils/callback/parsers'
import config from '@core/config'
import { loggerWithLabel } from '@core/logger'
import { tracer } from 'dd-trace'

const logger = loggerWithLabel(module)

Expand All @@ -24,7 +25,9 @@ const isAuthenticated = (authHeader?: string): boolean => {
}

const parseEvent = async (req: Request): Promise<void> => {
const parseJsonSpan = tracer.startSpan('parseJson')
const parsed = JSON.parse(req.body)
parseJsonSpan.finish()
let records: Promise<void>[] = []
if (ses.isEvent(req)) {
// body could be one record or an array of records, hence we concat
Expand Down
167 changes: 86 additions & 81 deletions backend/src/email/services/email-transactional.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '@core/constants'
import { Order } from 'sequelize/types/model'
import { Op, WhereOptions } from 'sequelize'
import tracer from 'dd-trace'

const logger = loggerWithLabel(module)

Expand Down Expand Up @@ -130,103 +131,107 @@ async function handleStatusCallbacks(
id: string,
metadata: CallbackMetaData
): Promise<void> {
const emailMessageTransactional = await EmailMessageTransactional.findByPk(id)
if (!emailMessageTransactional) {
throw new Error(`Failed to find emailMessageTransactional for id: ${id}`)
}
tracer.wrap('handleStatusCallbacks', async () => {
const emailMessageTransactional = await EmailMessageTransactional.findByPk(
id
)
if (!emailMessageTransactional) {
throw new Error(`Failed to find emailMessageTransactional for id: ${id}`)
}

const mainRecipientDelivered = metadata.delivery?.recipients?.find(
(e) => e === emailMessageTransactional.recipient
)
const mainRecipientBounced = metadata.bounce?.bouncedRecipients?.find(
(e) => e.emailAddress === emailMessageTransactional.recipient
)
const mainRecipientComplained =
metadata.complaint?.complainedRecipients?.find(
const mainRecipientDelivered = metadata.delivery?.recipients?.find(
(e) => e === emailMessageTransactional.recipient
)
const mainRecipientBounced = metadata.bounce?.bouncedRecipients?.find(
(e) => e.emailAddress === emailMessageTransactional.recipient
)
const mainRecipientComplained =
metadata.complaint?.complainedRecipients?.find(
(e) => e.emailAddress === emailMessageTransactional.recipient
)

switch (type) {
case SesEventType.Delivery:
if (mainRecipientDelivered) {
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Delivered,
deliveredAt: metadata.timestamp,
},
{
where: { id },
}
)
}
break
case SesEventType.Bounce:
// check that bounce applies to the main recipient
if (mainRecipientBounced) {
switch (type) {
case SesEventType.Delivery:
if (mainRecipientDelivered) {
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Delivered,
deliveredAt: metadata.timestamp,
},
{
where: { id },
}
)
}
break
case SesEventType.Bounce:
// check that bounce applies to the main recipient
if (mainRecipientBounced) {
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Bounced,
errorCode:
metadata.bounce?.bounceType === 'Permanent'
? 'Hard bounce'
: 'Soft bounce',
errorSubType: metadata.bounce?.bounceSubType,
},
{
where: { id },
}
)
}
break
case SesEventType.Complaint:
// check that complaint applies to the main recipient
if (mainRecipientComplained) {
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Complaint,
errorCode: metadata.complaint?.complaintFeedbackType,
errorSubType: metadata.complaint?.complaintSubType,
},
{
where: { id },
}
)
}
break
case SesEventType.Open:
// Cannot check that open applies to the main recipient
// we only update the DB if there was no previous error
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Bounced,
errorCode:
metadata.bounce?.bounceType === 'Permanent'
? 'Hard bounce'
: 'Soft bounce',
errorSubType: metadata.bounce?.bounceSubType,
status: TransactionalEmailMessageStatus.Opened,
openedAt: metadata.timestamp,
},
{
where: { id },
where: { id, errorCode: null },
}
)
}
break
case SesEventType.Complaint:
// check that complaint applies to the main recipient
if (mainRecipientComplained) {
break
case SesEventType.Send:
// Cannot check that send applies to the main recipient
// we only update the DB if there was no previous error
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Complaint,
errorCode: metadata.complaint?.complaintFeedbackType,
errorSubType: metadata.complaint?.complaintSubType,
status: TransactionalEmailMessageStatus.Sent,
sentAt: metadata.timestamp,
},
{
where: { id },
where: { id, errorCode: null },
}
)
}
break
case SesEventType.Open:
// Cannot check that open applies to the main recipient
// we only update the DB if there was no previous error
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Opened,
openedAt: metadata.timestamp,
},
{
where: { id, errorCode: null },
}
)
break
case SesEventType.Send:
// Cannot check that send applies to the main recipient
// we only update the DB if there was no previous error
await EmailMessageTransactional.update(
{
status: TransactionalEmailMessageStatus.Sent,
sentAt: metadata.timestamp,
},
{
where: { id, errorCode: null },
}
)
break
default:
logger.warn({
message: 'Unable to handle messages with this type',
type,
id,
metadata,
})
}
break
default:
logger.warn({
message: 'Unable to handle messages with this type',
type,
id,
metadata,
})
}
})
}

async function listMessages({
Expand Down
Loading

0 comments on commit 566e538

Please sign in to comment.