Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/goofy-needles-stay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@embedly/builder": patch
"@embedly/logging": patch
"@embedly/api": patch
"@embedly/bot": patch
---

add structured error logging with error message and stack trace to all span catch blocks across bot and API
9 changes: 9 additions & 0 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
EMBEDLY_CACHING_POST,
EMBEDLY_NO_LINK_IN_MESSAGE,
EMBEDLY_NO_VALID_LINK,
EMBEDLY_PARSE_POST_ID_FAILED,
type EmbedlyPostContext,
type FormattedLog,
formatLog
Expand Down Expand Up @@ -216,6 +217,14 @@ const app = (env: Env, ctx: ExecutionContext) =>
message: error.message
});
root_span.recordException(error);
logger.error(
formatLog(EMBEDLY_PARSE_POST_ID_FAILED, {
platform: handler.name,
post_url: url,
error_message: error.message,
error_stack: error.stack
})
);
root_span.end();
return status(400, {
type: "EMBEDLY_INVALID_URL",
Expand Down
89 changes: 75 additions & 14 deletions apps/bot/src/commands/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {
type EmbedFlags
} from "@embedly/builder";
import {
EMBEDLY_CREATE_EMBED_FAILED,
EMBEDLY_EMBED_CREATED_COMMAND,
EMBEDLY_NO_LINK_IN_MESSAGE,
EMBEDLY_NO_LINK_WARN,
EMBEDLY_NO_VALID_LINK,
EMBEDLY_NO_VALID_LINK_WARN,
EMBEDLY_SEND_MESSAGE_FAILED,
EMBEDLY_UNHANDLED_ERROR,
type EmbedlyInteractionContext,
type EmbedlySource,
formatDiscord,
Expand Down Expand Up @@ -206,26 +209,66 @@ export class EmbedCommand extends Command {
"create_embed",
async (s) => {
s.setAttribute("embedly.platform", platform.type);
const embed = await Platforms[platform.type].createEmbed(data);
s.end();
return embed;
try {
const embed =
await Platforms[platform.type].createEmbed(data);
return embed;
} catch (error: any) {
s.setStatus({
code: SpanStatusCode.ERROR,
message: error.message ?? String(error)
});
s.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_CREATE_EMBED_FAILED, {
interaction_id: interaction.id,
user_id: interaction.user.id,
source,
platform: platform.type,
error_message: error.message,
error_stack: error.stack
})
);
throw error;
} finally {
s.end();
}
}
);

const bot_message = await this.container.tracer.startActiveSpan(
"send_message",
async (s) => {
const res = await interaction.editReply({
components: [Embed.getDiscordEmbed(embed, flags)!],
flags: ["IsComponentsV2"],
allowedMentions: {
parse: [],
repliedUser: false
}
});
s.setAttribute("discord.bot_message_id", res.id);
s.end();
return res;
try {
const res = await interaction.editReply({
components: [Embed.getDiscordEmbed(embed, flags)!],
flags: ["IsComponentsV2"],
allowedMentions: {
parse: [],
repliedUser: false
}
});
s.setAttribute("discord.bot_message_id", res.id);
return res;
} catch (error: any) {
s.setStatus({
code: SpanStatusCode.ERROR,
message: error.message ?? String(error)
});
s.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_SEND_MESSAGE_FAILED, {
interaction_id: interaction.id,
user_id: interaction.user.id,
source,
error_message: error.message,
error_stack: error.stack
})
);
throw error;
} finally {
s.end();
}
}
);

Expand Down Expand Up @@ -274,6 +317,15 @@ export class EmbedCommand extends Command {
message: error.message
});
root_span.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_UNHANDLED_ERROR, {
interaction_id: interaction.id,
user_id: interaction.user.id,
source: "context_menu",
error_message: error.message,
error_stack: error.stack
})
);
} finally {
root_span.end();
}
Expand Down Expand Up @@ -325,6 +377,15 @@ export class EmbedCommand extends Command {
message: error.message
});
root_span.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_UNHANDLED_ERROR, {
interaction_id: interaction.id,
user_id: interaction.user.id,
source: "command",
error_message: error.message,
error_stack: error.stack
})
);
} finally {
root_span.end();
}
Expand Down
73 changes: 62 additions & 11 deletions apps/bot/src/listeners/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
type EmbedFlags
} from "@embedly/builder";
import {
EMBEDLY_CREATE_EMBED_FAILED,
EMBEDLY_EMBED_CREATED_MESSAGE,
EMBEDLY_SEND_MESSAGE_FAILED,
EMBEDLY_UNHANDLED_ERROR,
type EmbedlyInteractionContext,
type EmbedlyPostContext,
formatLog
Expand Down Expand Up @@ -145,10 +148,30 @@ export class MessageListener extends Listener<
"create_embed",
async (s) => {
s.setAttribute("embedly.platform", platform.type);
const embed =
await Platforms[platform.type].createEmbed(data);
s.end();
return embed;
try {
const embed =
await Platforms[platform.type].createEmbed(data);
return embed;
} catch (error: any) {
s.setStatus({
code: SpanStatusCode.ERROR,
message: error.message ?? String(error)
});
s.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_CREATE_EMBED_FAILED, {
message_id: message.id,
user_id: message.author.id,
source: "message",
platform: platform.type,
error_message: error.message,
error_stack: error.stack
})
);
throw error;
} finally {
s.end();
}
}
);

Expand Down Expand Up @@ -184,13 +207,32 @@ export class MessageListener extends Listener<
await this.container.tracer.startActiveSpan(
"send_message",
async (s) => {
const res =
ind > 0 && message.channel.isSendable()
? await message.channel.send(msg)
: await message.reply(msg);
s.setAttribute("discord.bot_message_id", res.id);
s.end();
return res;
try {
const res =
ind > 0 && message.channel.isSendable()
? await message.channel.send(msg)
: await message.reply(msg);
s.setAttribute("discord.bot_message_id", res.id);
return res;
} catch (error: any) {
s.setStatus({
code: SpanStatusCode.ERROR,
message: error.message ?? String(error)
});
s.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_SEND_MESSAGE_FAILED, {
message_id: message.id,
user_id: message.author.id,
source: "message",
error_message: error.message,
error_stack: error.stack
})
);
throw error;
} finally {
s.end();
}
}
);
this.container.embed_authors.set(
Expand Down Expand Up @@ -222,6 +264,15 @@ export class MessageListener extends Listener<
message: error.message
});
root_span.recordException(error);
this.container.logger.error(
formatLog(EMBEDLY_UNHANDLED_ERROR, {
message_id: message.id,
user_id: message.author.id,
source: "message",
error_message: error.message,
error_stack: error.stack
})
);
} finally {
root_span.end();
}
Expand Down
13 changes: 10 additions & 3 deletions packages/builder/src/Embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,10 @@ export class Embed implements EmbedData {
prefix_emoji: string
): string {
const username_part = username
? ` (${hyperlink(`@${escapeMarkdown(username, { italic: true, underline: true })}`, profile_url!)})`
? ` (${hyperlink(
`@${escapeMarkdown(username, { italic: true, underline: true })}`,
profile_url!
)})`
: "";

const full_text = `${prefix_emoji} ${name}${username_part}`.trim();
Expand All @@ -347,7 +350,9 @@ export class Embed implements EmbedData {

container.addTextDisplayComponents((builder) =>
builder.setContent(
`${stats.length > 0 ? `${subtext(stats.join(" "))}\n` : ""}${embed.emoji} • ${time(
`${stats.length > 0 ? `${subtext(stats.join(" "))}\n` : ""}${
embed.emoji
} • ${time(
embed.timestamp,
TimestampStyles.LongDateShortTime
)} • ${hyperlink(`View on ${embed.platform}`, embed.url)}`
Expand All @@ -366,7 +371,9 @@ export class Embed implements EmbedData {

return Object.entries(stats_data).map(
([key, val]) =>
`${statEmojis[key as keyof StatEmojis]} ${Embed.NumberFormatter.format(val)}`
`${statEmojis[key as keyof StatEmojis]} ${Embed.NumberFormatter.format(
val
)}`
);
}
}
41 changes: 40 additions & 1 deletion packages/logging/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface EmbedlyInteractionContext {
message_id?: string;
source?: EmbedlySource;
platform?: PlatformName;
error_message?: string;
error_stack?: string;
}

export const EMBEDLY_NO_LINK_IN_MESSAGE: EmbedlyErrorBase<EmbedlyInteractionContext> =
Expand Down Expand Up @@ -56,6 +58,8 @@ export interface EmbedlyPostContext {
resp_status?: number;
resp_message?: string;
resp_data?: any;
error_message?: string;
error_stack?: string;
}

export const EMBEDLY_FETCH_PLATFORM = (
Expand Down Expand Up @@ -180,6 +184,39 @@ export const EMBEDLY_AUTO_DELETE_INFO: EmbedlyLogBase<EmbedlyDeleteContext> =
"Bot embed automatically deleted because the original message was deleted."
};

export const EMBEDLY_UNHANDLED_ERROR: EmbedlyErrorBase<EmbedlyInteractionContext> =
{
type: "EMBEDLY_UNHANDLED_ERROR",
status: 500,
title: "Unhandled error.",
detail: "An unhandled error occurred while processing a request."
};

export const EMBEDLY_CREATE_EMBED_FAILED: EmbedlyErrorBase<EmbedlyInteractionContext> =
{
type: "EMBEDLY_CREATE_EMBED_FAILED",
status: 500,
title: "Failed to create embed.",
detail: "An error occurred while creating the embed from post data."
};

export const EMBEDLY_SEND_MESSAGE_FAILED: EmbedlyErrorBase<EmbedlyInteractionContext> =
{
type: "EMBEDLY_SEND_MESSAGE_FAILED",
status: 500,
title: "Failed to send message.",
detail:
"An error occurred while sending the embed message to Discord."
};

export const EMBEDLY_PARSE_POST_ID_FAILED: EmbedlyErrorBase<EmbedlyPostContext> =
{
type: "EMBEDLY_PARSE_POST_ID_FAILED",
status: 400,
title: "Failed to parse post ID.",
detail: "An error occurred while parsing the post ID from the URL."
};

export const EMBEDLY_NO_LINK_WARN: EmbedlyLogBase<EmbedlyInteractionContext> =
{
type: "EMBEDLY_NO_LINK_WARN",
Expand Down Expand Up @@ -300,5 +337,7 @@ export class EmbedlyLogger {
export function formatDiscord<
T extends EmbedlyErrorBase<EmbedlyInteractionContext>
>(err: T, ctx: T["context"]) {
return `**__${err.title}__**\n${err.detail}\n\n-# [${err.type}]: ${ctx?.interaction_id || ctx?.message_id}`;
return `**__${err.title}__**\n${err.detail}\n\n-# [${err.type}]: ${
ctx?.interaction_id || ctx?.message_id
}`;
}