Verified Commit 88f5920f authored by Mathias B.'s avatar Mathias B.
Browse files

Merge branch 'features/better-error-handling' into trunk

Implémentation d'une meilleure gestion des erreurs

Closes #17
parents 20281748 c5ef527e
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
......@@ -66,6 +77,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.12.3"
......@@ -304,6 +330,7 @@ dependencies = [
"serde",
"serde_json",
"serenity",
"stable-eyre",
"tokio",
"tokio-postgres",
"tracing",
......@@ -522,6 +549,12 @@ dependencies = [
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
name = "gimli"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
[[package]]
name = "h2"
version = "0.3.3"
......@@ -892,6 +925,15 @@ dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.8.0"
......@@ -1246,6 +1288,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "rustc-demangle"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49"
[[package]]
name = "rustls"
version = "0.19.1"
......@@ -1432,6 +1480,17 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "stable-eyre"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556fec8c2da34c70b75f16d88df8a8cd7e652e567ff097b7e9df0022c8695cc4"
dependencies = [
"backtrace",
"eyre",
"indenter",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
......
......@@ -40,6 +40,7 @@ serde_json = "1.0.61"
# Eror handling
eyre = "0.6.5"
stable-eyre = "0.2"
datastructs = { path = "../datastructs", features = ["serenity_integration"] }
cryptoutils = { path = "../cryptoutils" }
......
......@@ -10,7 +10,7 @@ fn main() {
.output()
.ok()
.map(|o| String::from_utf8(o.stdout).unwrap())
.or(Some("unknown-version".to_string()))
.or_else(|| Some("unknown-version".to_string()))
.unwrap();
let version = version.trim();
......
use serenity::prelude::*;
use serenity::model::prelude::*;
use serenity::async_trait;
use tracing::{error, info, debug};
use tracing::{info, debug};
use crate::repositories::{GuildRepository, TicketRepository};
use crate::repositories::ChannelRepository;
use crate::repositories::UserRepository;
......@@ -18,92 +18,79 @@ pub struct Handler;
impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
info!("Cache is ready");
functions::update_guilds_count(&ctx, guilds.len()).await;
tokio::spawn(synchrotron::event_handler::cache_ready(ctx.clone(), guilds.clone()));
process_handler_result(functions::update_guilds_count(&ctx, guilds.len()).await);
process_handler_result(synchrotron::event_handler::cache_ready(&ctx, guilds).await);
}
async fn channel_create(&self, ctx: Context, channel: &GuildChannel) {
let creation_result = ChannelRepository::insert(ChannelData::from(channel)).await;
match creation_result {
Ok(_) => {}
Err(e) => {
error!("Erreur lors de l'insertion d'une channel: {:?}", e);
}
}
tokio::spawn(crate::modules::markov::channel_create(ctx.clone(), channel.clone()));
process_handler_result(ChannelRepository::insert(ChannelData::from(channel)).await);
process_handler_result(crate::modules::markov::channel_create(&ctx, &channel).await);
}
async fn category_create(&self, _ctx: Context, category: &ChannelCategory) {
let creation_result = ChannelRepository::insert(ChannelData::from(category)).await;
match creation_result {
Ok(_) => {}
Err(e) => {
error!("Erreur lors de l'insertion d'une channel: {:?}", e);
}
}
process_handler_result(ChannelRepository::insert(ChannelData::from(category)).await);
}
async fn category_delete(&self, _ctx: Context, category: &ChannelCategory) {
tokio::spawn(ChannelRepository::delete(i64::from(category.id)));
process_handler_result(ChannelRepository::delete(i64::from(category.id)).await);
}
async fn channel_delete(&self, _ctx: Context, channel: &GuildChannel) {
tokio::spawn(ChannelRepository::delete(i64::from(channel.id)));
tokio::spawn(TicketRepository::close_by_channel_id(channel.id));
process_handler_result(TicketRepository::close_by_channel_id(channel.id).await);
process_handler_result(ChannelRepository::delete(i64::from(channel.id)).await);
}
async fn channel_update(&self, _ctx: Context, _old: Option<Channel>, new: Channel) {
// We are not listening to DMs
let channel = new.guild().expect("Expected a guild channel");
tokio::spawn(ChannelRepository::update(ChannelData::from(&channel)));
tokio::spawn(crate::modules::markov::channel_update(channel.clone()));
let channel = new.guild().expect("Expected a guild channel update, not a DM one");
process_handler_result(ChannelRepository::update(ChannelData::from(&channel)).await);
process_handler_result(crate::modules::markov::channel_update(&channel).await);
}
async fn guild_ban_addition(&self, _ctx: Context, guild_id: GuildId, banned_user: User) {
tokio::spawn(crate::modules::logging::guild_ban_addition(guild_id, banned_user.clone()));
tokio::spawn(MemberRepository::remove_membership(banned_user.id, guild_id));
process_handler_result(crate::modules::logging::guild_ban_addition(guild_id, &banned_user).await);
process_handler_result(MemberRepository::remove_membership(banned_user.id, guild_id).await);
}
async fn guild_create(&self, ctx: Context, guild: Guild, is_new: bool) {
info!("Guild {} received", guild.id);
tokio::spawn(synchrotron::event_handler::guild_create(ctx.clone(), guild.clone(), is_new));
process_handler_result(synchrotron::event_handler::guild_create(&ctx, guild, is_new).await);
}
async fn guild_delete(&self, ctx: Context, incomplete: GuildUnavailable, _full: Option<Guild>) {
info!("Guild {} left, destroying", incomplete.id);
tokio::spawn(GuildRepository::destroy(i64::from(incomplete.id)));
functions::update_guilds_count(&ctx, ctx.cache.guilds().await.len()).await;
process_handler_result(functions::update_guilds_count(&ctx, ctx.cache.guilds().await.len()).await);
process_handler_result(GuildRepository::destroy(i64::from(incomplete.id)).await);
}
async fn guild_member_addition(&self, ctx: Context, guild_id: GuildId, new_member: Member) {
UserRepository::upsert(UserData::from(&new_member.user)).await;
async fn guild_member_addition(&self, ctx: Context, guild_id: GuildId, mut new_member: Member) {
process_handler_result(UserRepository::upsert(UserData::from(&new_member.user)).await);
MemberRepository::upsert(
new_member.user.id,
guild_id,
new_member.nick.as_ref(),
&new_member.joined_at.unwrap_or_else(Utc::now),
).await;
process_handler_result(
MemberRepository::upsert(
new_member.user.id,
guild_id,
new_member.nick.as_ref(),
&new_member.joined_at.unwrap_or_else(Utc::now),
).await
);
tokio::spawn(crate::modules::welcome::greet_member(ctx.clone(), guild_id, new_member.clone()));
tokio::spawn(crate::modules::welcome::set_role_to_new_member(ctx.clone(), guild_id, new_member.clone()));
tokio::spawn(crate::modules::logging::guild_member_addition(guild_id, new_member.clone()));
process_handler_result(crate::modules::welcome::greet_member(&ctx, guild_id, &new_member).await);
process_handler_result(crate::modules::welcome::set_role_to_new_member(&ctx, guild_id, &mut new_member).await);
process_handler_result(crate::modules::logging::guild_member_addition(guild_id, &new_member).await);
}
async fn guild_member_removal(&self, ctx: Context, guild_id: GuildId, user: User, member_data_if_available: Option<Member>) {
tokio::spawn(crate::modules::welcome::goodbye_member(ctx.clone(), guild_id, user.clone()));
tokio::spawn(crate::modules::logging::guild_member_removal(ctx.clone(), guild_id, user.clone(), member_data_if_available.clone()));
tokio::spawn(MemberRepository::remove_membership(user.id, guild_id));
process_handler_result(crate::modules::welcome::goodbye_member(&ctx, guild_id, &user).await);
process_handler_result(crate::modules::logging::guild_member_removal(&ctx, guild_id, &user, member_data_if_available.as_ref()).await);
process_handler_result(MemberRepository::remove_membership(user.id, guild_id).await);
}
async fn guild_member_update(&self, _ctx: Context, old_if_available: Option<Member>, new: Member) {
tokio::spawn(crate::modules::logging::guild_member_update(old_if_available.clone(), new.clone()));
tokio::spawn(MemberRepository::update(new.user.id, new.guild_id, new.nick.clone()));
process_handler_result(MemberRepository::update(new.user.id, new.guild_id, new.nick.as_ref()).await);
process_handler_result(crate::modules::logging::guild_member_update(old_if_available.as_ref(), &new).await);
}
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
......@@ -116,7 +103,7 @@ impl EventHandler for Handler {
// On a reçu tous les membres
if chunk.chunk_index >= chunk.chunk_count - 1 {
synchrotron::synchronize_guild(&ctx, chunk.guild_id).await;
process_handler_result(synchrotron::synchronize_guild(&ctx, chunk.guild_id).await);
}
}
......@@ -127,11 +114,11 @@ impl EventHandler for Handler {
async fn guild_update(&self, _ctx: Context, _old_data_if_available: Option<Guild>, _new_but_incomplete: PartialGuild) {}
async fn message(&self, ctx: Context, message: Message) {
let channel = message.channel(&ctx).await.unwrap();
let channel = message.channel(&ctx).await.expect("Expected the cache to contain the channel");
let channel2 = channel.clone();
match channel2.private() {
// In a guild
// In a private channel (we shouldn't see any of this since the bot is not listening to DMs)
Some(private_channel) => {
info!(
"Dans un channel privé avec {}: {}",
......@@ -144,7 +131,7 @@ impl EventHandler for Handler {
// In public
None => {
let guild = message.guild(&ctx).await.unwrap();
let guild = message.guild(&ctx).await.expect("Expected the cache to contain the guild");
info!(
"{} ({}) - {}: {}: {}",
&guild.name,
......@@ -158,7 +145,7 @@ impl EventHandler for Handler {
crate::modules::metrics::on_message();
tokio::spawn(crate::modules::markov::new_message(ctx.clone(), message.clone()));
process_handler_result(crate::modules::markov::new_message(&ctx, &message).await);
}
async fn message_delete(&self, ctx: Context, channel_id: ChannelId, deleted_message_id: MessageId, guild_id: Option<GuildId>) {
......@@ -167,10 +154,9 @@ impl EventHandler for Handler {
return;
}
let guild_id = guild_id.unwrap();
tokio::spawn(crate::repositories::MarkovRepository::delete_single(deleted_message_id, guild_id));
tokio::spawn(crate::modules::logging::on_message_delete(ctx.clone(), channel_id, deleted_message_id, guild_id));
let guild_id = guild_id.expect("Somehow, the guild_id is still none !");
process_handler_result(crate::repositories::MarkovRepository::delete_single(deleted_message_id, guild_id).await);
process_handler_result(crate::modules::logging::on_message_delete(&ctx, channel_id, deleted_message_id, guild_id).await);
}
async fn message_delete_bulk(&self, _ctx: Context, _channel_id: ChannelId, multiple_deleted_messages_ids: Vec<MessageId>, guild_id: Option<GuildId>) {
......@@ -178,20 +164,20 @@ impl EventHandler for Handler {
return;
}
let guild_id = guild_id.unwrap();
let guild_id = guild_id.expect("Somehow, the guild_id is still none !");
tokio::spawn(crate::repositories::MarkovRepository::delete_multiple(multiple_deleted_messages_ids.clone().into_boxed_slice(), guild_id));
process_handler_result(crate::repositories::MarkovRepository::delete_multiple(multiple_deleted_messages_ids.into_boxed_slice(), guild_id).await)
}
async fn message_update(&self, ctx: Context, old: Option<Message>, new: Option<Message>, event: MessageUpdateEvent) {
info!("Message {} édité: {:?}", event.id, event.content);
tokio::spawn(crate::modules::markov::update_message(ctx.clone(), event.clone()));
tokio::spawn(crate::modules::logging::on_message_update(ctx.clone(), old.clone(), new.clone(), event.clone()));
process_handler_result(crate::modules::markov::update_message(&ctx, &event).await);
process_handler_result(crate::modules::logging::on_message_update(&ctx, old.as_ref(), new.as_ref(), &event).await);
}
async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
info!("Réaction {} ajoutée sur {:?}", reaction.emoji, reaction.guild_id);
tokio::spawn(crate::modules::ticketing::reaction_add(ctx.clone(), reaction.clone()));
process_handler_result(crate::modules::ticketing::reaction_add(&ctx, &reaction).await);
}
async fn ready(&self, _ctx: Context, ready: Ready) {
......@@ -199,6 +185,13 @@ impl EventHandler for Handler {
}
async fn user_update(&self, _ctx: Context, _old_data: CurrentUser, new: CurrentUser) {
tokio::spawn(UserRepository::update(UserData::from(&new)));
process_handler_result(UserRepository::update(UserData::from(&new)).await);
}
}
fn process_handler_result(result: std::result::Result<(), impl Into<eyre::Report>>) {
if let Err(future_error) = result {
let future_error = future_error.into();
tracing::error!("Future has errorred in handler: {:?}", future_error)
}
}
\ No newline at end of file
}
......@@ -24,6 +24,7 @@ use tracing_subscriber::util::SubscriberInitExt;
use serenity::framework::standard::buckets::LimitedFor;
use crate::raw_handler::RawHandler;
use tokio::time::Duration;
use eyre::WrapErr;
pub struct ShardManagerContainer;
......@@ -32,11 +33,12 @@ impl TypeMapKey for ShardManagerContainer {
}
static BOT_PREFIX: Lazy<String> = Lazy::new(|| {
std::env::var("BOT_PREFIX").unwrap_or_else(|_| panic!("Expected a BOT_PREFIX in the environment"))
std::env::var("BOT_PREFIX").expect("Expected a BOT_PREFIX in the environment")
});
#[tokio::main]
async fn main() {
async fn main() -> eyre::Result<()> {
stable_eyre::install()?;
tracing_subscriber::fmt()
.with_max_level(
Level::INFO
......@@ -44,10 +46,11 @@ async fn main() {
.finish()
.init();
dotenv::dotenv().expect("Couldn't load any .env file");
dotenv::dotenv()
.context("Could not load the .env file")?;
let token = std::env::var("DISCORD_TOKEN").expect("Expected a DISCORD_TOKEN in the environment");
let database_url = std::env::var("DATABASE_URL").expect("Expected a POSTGRESQL_URL in the environment");
let token = std::env::var("DISCORD_TOKEN").context("Expected a DISCORD_TOKEN in the environment")?;
let database_url = std::env::var("DATABASE_URL").context("Expected a POSTGRESQL_URL in the environment")?;
info!("Initializing encryption key");
let _ = cryptoutils::CryptoUtils::with(Box::new(cryptoutils::strategies::encrypted::ChaCha20Poly1304Encryption));
......@@ -56,7 +59,7 @@ async fn main() {
info!("Connecting to database");
let (postgres_client, connection) = tokio_postgres::connect(&database_url, tokio_postgres::NoTls)
.await
.expect("Can't connect to the database");
.context("Can't connect to the database")?;
tokio::spawn(async move {
if let Err(e) = connection.await {
......@@ -102,7 +105,7 @@ async fn main() {
GatewayIntents::GUILD_MESSAGE_REACTIONS
)
.await
.expect("Error creating client");
.context("Erreur lors de la création du client")?;
client.cache_and_http.cache.set_max_messages(500).await;
......@@ -113,7 +116,7 @@ async fn main() {
info!("Starting client");
if let Err(reason) = client.start().await {
error!("Client error: {:?}", reason)
}
client.start().await?;
Ok(())
}
......@@ -4,9 +4,8 @@ use serenity::framework::standard::CommandResult;
use serenity::model::prelude::Message;
use serenity::client::bridge::gateway::ShardId;
use tracing::{error, warn};
use crate::{ShardManagerContainer};
use crate::ShardManagerContainer;
use eyre::{ContextCompat, WrapErr};
#[command]
#[description = "Une simple commande pour déterminer la latence du bot"]
......@@ -14,36 +13,25 @@ use crate::{ShardManagerContainer};
async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
let data = ctx.data.read().await;
let shard_manager = match data.get::<ShardManagerContainer>() {
None => {
error!("Something wrong happened while acquiring shard manager data");
let _ = msg.channel_id.say(ctx, "Il y a eu un souci lors de l'acquision du gestionnaire de shards").await;
return Ok(());
},
Some(container) => container,
};
let shard_manager = data.get::<ShardManagerContainer>()
.context("Ne peut pas obtenir de ShardManagerContainer depuis le stockage du bot")?;
let manager = shard_manager.lock().await;
let runners = manager.runners.lock().await;
let runner = match runners.get(&ShardId(ctx.shard_id)) {
None => {
warn!("No shard found");
let _ = msg.channel_id.say(ctx, "Aucune shard n'a été trouvé").await;
return Ok(());
},
Some(runner) => runner,
};
let runner = runners.get(&ShardId(ctx.shard_id))
.context(format!("Shard {} non trouvée dans l'exécuteur", ctx.shard_id))?;
match runner.latency {
None => { let _ = msg.channel_id.say(ctx, ":ping_pong: Ping à je ne sais combien. Mais y'a un ping quand même !").await; }
Some(ping) => { let _ = msg.channel_id.say(ctx, &format!(":ping_pong: Ping à {:?}", ping)).await; }
let message = match runner.latency {
None => ":ping_pong: Ping à je ne sais combien. Mais y'a un ping quand même !".to_string(),
Some(ping) => format!(":ping_pong: Ping à {:?}", ping)
};
msg.reply(ctx, &message)
.await
.context("Erreur lors de l'envoi du message de latence")?;
Ok(())
}
......
......@@ -41,7 +41,9 @@ pub async fn help(
groups: &[&'static CommandGroup],
owners: HashSet<UserId>
) -> CommandResult {
let _ = help_commands::with_embeds(context, msg, args, help_opt, groups, owners).await;
if help_commands::with_embeds(context, msg, args, help_opt, groups, owners).await.is_none() {
tracing::warn!("No command found");
}
Ok(())
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ use crate::repositories::{ConfigRepository, MemberRepository};
use itertools::Itertools;
use std::collections::HashSet;
use tracing::warn;
use eyre::{WrapErr, ContextCompat};
#[command]
#[description = "Informations concernant le serveur"]
......@@ -32,7 +33,9 @@ async fn server_info(ctx: &Context, msg: &Message) -> CommandResult {
.as_ref()
.map_or("Automatique", |location| translate_region(&location))
})
.unique()
.collect::<HashSet<&str>>()
.iter()
.copied()
.collect_vec();
......@@ -215,9 +218,8 @@ pub async fn user_info(ctx: &Context, msg: &Message, args: Args) -> CommandResul
#[command]
#[description = "Affiche les règles du serveur, si elles ont été configurés dans le panel administateur du bot"]
async fn rules(ctx: &Context, message: &Message) -> CommandResult {
let config = ConfigRepository::get_by_guild_id(message.guild_id.unwrap()).await.ok_or("No configuration")?;
tracing::debug!("{:#?}", config);
let config = ConfigRepository::get_by_guild_id(message.guild_id.context("Pas de guilde attachée au message")?).await?
.with_context(|| format!("Pas de configuration pour la guilde"))?;
message.channel_id.send_message(&ctx, |m| {
let rules = match config.rules {
......@@ -245,7 +247,7 @@ async fn info(ctx: &Context, message: &Message) -> CommandResult {
let memory_stats = procinfo::StatM::collect().await.unwrap();
let guilds_count = ctx.cache.guild_count().await;
let users_count = ctx.cache.user_count().await;
let members_count = MemberRepository::count_all_members().await;
let members_count = MemberRepository::count_all_members().await?;
let message_result = message.channel_id.send_message(
ctx,
......@@ -294,15 +296,13 @@ async fn info(ctx: &Context, message: &Message) -> CommandResult {
#[command]
#[description = "Affiche les moyens de contacter le développeur"]
async fn contact(ctx: &Context, message: &Message) -> CommandResult {
let message_result = message.reply(ctx, r#"
message.reply(ctx, r#"
Moyens de contact:
- :e_mail: `discord-bot[at/arobase]l4p1n.ch` ─ Merci d'utiliser cette adresse mail à des fins utiles.
- :eyes: D'autres moyens de contact prochainement
"#).await;
if let Err(e) = message_result {
warn!("Ne peut pas envoyer les infos: {:?}", e);
}
"#)
.await
.context("Ne peut pas envoyer les informations de contact au format embed")?;
Ok(())
}
......
......@@ -4,21 +4,21 @@ use crate::repositories::{ConfigRepository, LoggingRepository};
use datastructs::bot::logging::{LoggingMetadata, LoggingEventType};
use tracing::instrument;
pub async fn on_message_update(ctx: Context, old: Option<Message>, new: Option<Message>, event: MessageUpdateEvent) {
pub async fn on_message_update(ctx: &Context, old: Option<&Message>, new: Option<&Message>, event: &MessageUpdateEvent) -> eyre::Result<()> {
let guild_id = match event.guild_id {
Some(g) => g,
None => return,
None => return Ok(()),
};
if old.is_none() || new.is_none() {
return;
return Ok(());
}
let old = old.unwrap();
let new = new.unwrap();
if new.content.is_empty() || old.content == new.content || !wants_logging(guild_id).await {
return;
if new.content.is_empty() || old.content == new.content || !wants_logging(guild_id).await? {
return Ok(());
}
let old_content = old.content_safe(&ctx).await;
......@@ -39,12 +39,14 @@ pub async fn on_message_update(ctx: Context, old: Option<Message>, new: Option<M
event_type: i16::from(&content),
};
LoggingRepository::insert_log(&metadata, &content).await;
LoggingRepository::insert_log(&metadata, &content).await?;
Ok(())
}
pub async fn on_message_delete(ctx: Context, channel_id: ChannelId, deleted_message_id: MessageId, guild_id: GuildId) {
if !wants_logging(guild_id).await {
return;
pub async fn on_message_delete(ctx: &Context, channel_id: ChannelId, deleted_message_id: MessageId, guild_id: GuildId) -> eyre::Result<()> {
if !wants_logging(guild_id).await? {
return Ok(());
}
let content = LoggingEventType::MessageDeleted {
......@@ -64,15 +66,17 @@ pub async fn on_message_delete(ctx: Context, channel_id: ChannelId, deleted_mess
event_type: i16::from(&content),
};
LoggingRepository::insert_log(&metadata, &content).await;
LoggingRepository::insert_log(&metadata, &content).await?;