From 35cbae61bc03a1a0af29f79ee55223d4e9766c07 Mon Sep 17 00:00:00 2001
From: Riley <asonix@asonix.dog>
Date: Mon, 6 Dec 2021 08:54:47 -0600
Subject: [PATCH] Don't drop error context when adding a message to errors
 (#1958)

* Respond directly with LemmyError

Instrument Perform implementations for more precise traces
Use ApiError to format JSON errors when messages are present
Keep SpanTrace output in LemmyError Display impl

* Hide SpanTrace debug output from LemmyError

* Don't log when entering spans, only when leaving

* Update actix-web

* Update actix-rt

* Add newline after error info in LemmyError Display impl

* Propogate span information to blocking operations

* Instrument apub functions

* Use skip_all for more instrument attributes, don't skip 'self' in some api actions

* Make message a static string

* Send proper JSON over websocket

* Add 'message' to LemmyError display if present

* Use a quieter root span builder, don't pretty-print logs

* Keep passwords and emails out of logs

* Re-enable logging Login

* Instrument feeds

* Emit our own errors

* Move error log after status code recording

* Make Sensitive generic over the inner type

* Remove line that logged secrets
---
 Cargo.lock                                    |  28 ++--
 Cargo.toml                                    |   2 +-
 crates/api/Cargo.toml                         |   1 +
 crates/api/src/comment.rs                     |  19 ++-
 crates/api/src/comment_report.rs              |  15 +-
 crates/api/src/community.rs                   |  45 ++++--
 crates/api/src/local_user.rs                  |  93 ++++++++----
 crates/api/src/post.rs                        |  17 ++-
 crates/api/src/post_report.rs                 |  15 +-
 crates/api/src/private_message.rs             |   8 +-
 crates/api/src/site.rs                        |  35 +++--
 crates/api/src/websocket.rs                   |   4 +
 crates/api_common/Cargo.toml                  |   1 +
 crates/api_common/src/comment.rs              |  57 +++----
 crates/api_common/src/community.rs            |  53 +++----
 crates/api_common/src/lib.rs                  |  59 +++++---
 crates/api_common/src/person.rs               | 143 +++++++++---------
 crates/api_common/src/post.rs                 |  62 ++++----
 crates/api_common/src/site.rs                 |  45 +++---
 crates/api_common/src/websocket.rs            |  11 +-
 crates/api_crud/src/comment/create.rs         |  27 ++--
 crates/api_crud/src/comment/delete.rs         |  14 +-
 crates/api_crud/src/comment/read.rs           |  16 +-
 crates/api_crud/src/comment/update.rs         |   7 +-
 crates/api_crud/src/community/create.rs       |  21 ++-
 crates/api_crud/src/community/delete.rs       |  12 +-
 crates/api_crud/src/community/read.rs         |  19 ++-
 crates/api_crud/src/community/update.rs       |   8 +-
 crates/api_crud/src/post/create.rs            |  11 +-
 crates/api_crud/src/post/delete.rs            |   8 +-
 crates/api_crud/src/post/read.rs              |  19 ++-
 crates/api_crud/src/post/update.rs            |   8 +-
 crates/api_crud/src/private_message/create.rs |   8 +-
 crates/api_crud/src/private_message/delete.rs |   8 +-
 crates/api_crud/src/private_message/read.rs   |   3 +-
 crates/api_crud/src/private_message/update.rs |   8 +-
 crates/api_crud/src/site/create.rs            |   6 +-
 crates/api_crud/src/site/read.rs              |  30 ++--
 crates/api_crud/src/site/update.rs            |   7 +-
 crates/api_crud/src/user/create.rs            |  28 ++--
 crates/api_crud/src/user/delete.rs            |  15 +-
 crates/api_crud/src/user/read.rs              |   9 +-
 .../activities/comment/create_or_update.rs    |   4 +
 crates/apub/src/activities/comment/mod.rs     |   1 +
 .../apub/src/activities/community/add_mod.rs  |   4 +
 .../apub/src/activities/community/announce.rs |   4 +
 .../src/activities/community/block_user.rs    |   5 +
 crates/apub/src/activities/community/mod.rs   |   2 +
 .../src/activities/community/remove_mod.rs    |   5 +
 .../apub/src/activities/community/report.rs   |   4 +
 .../activities/community/undo_block_user.rs   |   5 +
 .../apub/src/activities/community/update.rs   |   5 +
 crates/apub/src/activities/deletion/delete.rs |  12 +-
 crates/apub/src/activities/deletion/mod.rs    |   6 +
 .../src/activities/deletion/undo_delete.rs    |  11 +-
 .../apub/src/activities/following/accept.rs   |   4 +
 .../apub/src/activities/following/follow.rs   |   5 +
 .../src/activities/following/undo_follow.rs   |   4 +
 crates/apub/src/activities/mod.rs             |  22 ++-
 .../src/activities/post/create_or_update.rs   |  11 +-
 .../private_message/create_or_update.rs       |   5 +
 .../src/activities/private_message/delete.rs  |   5 +
 .../activities/private_message/undo_delete.rs |   4 +
 crates/apub/src/activities/voting/mod.rs      |   4 +
 .../apub/src/activities/voting/undo_vote.rs   |   5 +
 crates/apub/src/activities/voting/vote.rs     |   5 +
 crates/apub/src/activity_lists.rs             |   1 +
 .../src/collections/community_moderators.rs   |   5 +
 .../apub/src/collections/community_outbox.rs  |   4 +
 crates/apub/src/fetcher/post_or_comment.rs    |   4 +
 crates/apub/src/fetcher/search.rs             |   8 +-
 crates/apub/src/fetcher/user_or_community.rs  |   4 +
 crates/apub/src/fetcher/webfinger.rs          |   9 +-
 crates/apub/src/http/comment.rs               |   5 +-
 crates/apub/src/http/community.rs             |  13 +-
 crates/apub/src/http/mod.rs                   |  27 ++--
 crates/apub/src/http/person.rs                |   9 +-
 crates/apub/src/http/post.rs                  |   5 +-
 crates/apub/src/lib.rs                        |  32 ++--
 crates/apub/src/mentions.rs                   |   2 +
 crates/apub/src/objects/comment.rs            |   8 +-
 crates/apub/src/objects/community.rs          |   6 +
 crates/apub/src/objects/person.rs             |   5 +
 crates/apub/src/objects/post.rs               |   5 +
 crates/apub/src/objects/private_message.rs    |   7 +-
 .../src/protocol/activities/voting/vote.rs    |   3 +-
 crates/apub/src/protocol/objects/page.rs      |   3 +-
 crates/db_schema/src/lib.rs                   |   6 +-
 crates/routes/Cargo.toml                      |   1 +
 crates/routes/src/feeds.rs                    |  11 ++
 crates/utils/src/lib.rs                       |  79 +++++++---
 crates/utils/src/rate_limit/rate_limiter.rs   |  22 ++-
 crates/utils/src/sensitive.rs                 |  98 ++++++++++++
 crates/utils/src/utils.rs                     |  12 +-
 crates/websocket/src/chat_server.rs           |   3 +-
 crates/websocket/src/handlers.rs              |   5 +-
 crates/websocket/src/routes.rs                |   1 -
 src/api_routes.rs                             |   8 +-
 src/lib.rs                                    |   7 +-
 src/main.rs                                   |   4 +-
 src/root_span_builder.rs                      |  85 +++++++++++
 101 files changed, 1125 insertions(+), 529 deletions(-)
 create mode 100644 crates/utils/src/sensitive.rs
 create mode 100644 src/root_span_builder.rs

diff --git a/Cargo.lock b/Cargo.lock
index 5e879b8a..9485c570 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -69,9 +69,9 @@ dependencies = [
 
 [[package]]
 name = "actix-http"
-version = "3.0.0-beta.12"
+version = "3.0.0-beta.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afaeb3d3fcb06b775ac62f05d580aae4afe5a149513333a73f688fdf26c06639"
+checksum = "1bc3f9d97e32d75fae3ad7d955ac005eea3fd3ea60a89132768700911a60fd94"
 dependencies = [
  "actix-codec",
  "actix-rt",
@@ -130,9 +130,9 @@ dependencies = [
 
 [[package]]
 name = "actix-rt"
-version = "2.4.0"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0c218d0a17c120f10ee0c69c9f0c45d87319e8f66b1f065e8412b612fc3e24"
+checksum = "05c2f80ce8d0c990941c7a7a931f69fd0701b76d521f8d36298edf59cd3fbf1f"
 dependencies = [
  "actix-macros",
  "futures-core",
@@ -170,9 +170,9 @@ dependencies = [
 
 [[package]]
 name = "actix-tls"
-version = "3.0.0-beta.8"
+version = "3.0.0-beta.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a563b0245222230c860c1b077ca7309179fff0f575b1914967c1ee385aa5da64"
+checksum = "53d4739910b49c77ea88308a9fbfae544524b34884161527f9978c0102052da0"
 dependencies = [
  "actix-codec",
  "actix-rt",
@@ -182,6 +182,7 @@ dependencies = [
  "futures-core",
  "http",
  "log",
+ "pin-project-lite",
  "tokio-rustls",
  "tokio-util",
  "webpki-roots",
@@ -199,9 +200,9 @@ dependencies = [
 
 [[package]]
 name = "actix-web"
-version = "4.0.0-beta.11"
+version = "4.0.0-beta.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e85aa9bb018d83a0db70f557ba0cde9c6170a5d1de4fede02e377f68c1ac5aa9"
+checksum = "e87cfc4efaad42f8a054e269d1b85046397ff4e8707e49128dea3f99a512a9d6"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -388,9 +389,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
 name = "awc"
-version = "3.0.0-beta.10"
+version = "3.0.0-beta.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f122bed94dc044b13a991b292ff6a3cde4c3fba890a4e3dbbec0f2eedc607f0a"
+checksum = "f9f7d0c472987e454f41c3f4c7fa336ca139707ab255644b0480144c2060c800"
 dependencies = [
  "actix-codec",
  "actix-http",
@@ -1759,6 +1760,7 @@ dependencies = [
  "strum_macros",
  "thiserror",
  "tokio",
+ "tracing",
  "url",
  "uuid",
 ]
@@ -1777,6 +1779,7 @@ dependencies = [
  "lemmy_utils",
  "serde",
  "serde_json",
+ "tracing",
  "url",
 ]
 
@@ -1982,6 +1985,7 @@ dependencies = [
  "serde",
  "sha2",
  "strum",
+ "tracing",
  "url",
 ]
 
@@ -3998,9 +4002,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-actix-web"
-version = "0.5.0-beta.2"
+version = "0.5.0-beta.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cac34827e06f78b69523b2fbe5b2dd4dfc75940b2ea5ba37e4fa2a25d4a0edf"
+checksum = "994e4a59135823bdca121a8d086e3fcc71741c8677b47fa95a6afdd15e8f646f"
 dependencies = [
  "actix-web",
  "pin-project",
diff --git a/Cargo.toml b/Cargo.toml
index 0d38c2e7..040a25cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,7 +50,7 @@ serde = { version = "1.0.130", features = ["derive"] }
 actix = "0.12.0"
 actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["rustls"] }
 tracing = "0.1.29"
-tracing-actix-web = "0.5.0-beta.2"
+tracing-actix-web = { version = "0.5.0-beta.3", default-features = false }
 tracing-error = "0.2.0"
 tracing-log = "0.1.2"
 tracing-subscriber = { version = "0.3.2", features = ["env-filter"] }
diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml
index 96b47f46..c2bc77fa 100644
--- a/crates/api/Cargo.toml
+++ b/crates/api/Cargo.toml
@@ -48,5 +48,6 @@ async-trait = "0.1.51"
 captcha = "0.0.8"
 anyhow = "1.0.44"
 thiserror = "1.0.29"
+tracing = "0.1.29"
 background-jobs = "0.11.0"
 reqwest = { version = "0.11.4", features = ["json"] }
diff --git a/crates/api/src/comment.rs b/crates/api/src/comment.rs
index e673d497..ac066227 100644
--- a/crates/api/src/comment.rs
+++ b/crates/api/src/comment.rs
@@ -23,7 +23,7 @@ use lemmy_db_schema::{
   traits::{Likeable, Saveable},
 };
 use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
 
 use crate::Perform;
@@ -32,6 +32,7 @@ use crate::Perform;
 impl Perform for MarkCommentAsRead {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -56,7 +57,7 @@ impl Perform for MarkCommentAsRead {
 
     // Verify that only the recipient can mark as read
     if local_user_view.person.id != orig_comment.get_recipient_id() {
-      return Err(ApiError::err_plain("no_comment_edit_allowed").into());
+      return Err(LemmyError::from_message("no_comment_edit_allowed"));
     }
 
     // Do the mark as read
@@ -65,7 +66,8 @@ impl Perform for MarkCommentAsRead {
       Comment::update_read(conn, comment_id, read)
     })
     .await?
-    .map_err(|_| ApiError::err_plain("couldnt_update_comment"))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     // Refetch it
     let comment_id = data.comment_id;
@@ -89,6 +91,7 @@ impl Perform for MarkCommentAsRead {
 impl Perform for SaveComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -107,12 +110,14 @@ impl Perform for SaveComment {
       let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
       blocking(context.pool(), save_comment)
         .await?
-        .map_err(|e| ApiError::err("couldnt_save_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_save_comment"))?;
     } else {
       let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
       blocking(context.pool(), unsave_comment)
         .await?
-        .map_err(|e| ApiError::err("couldnt_save_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_save_comment"))?;
     }
 
     let comment_id = data.comment_id;
@@ -134,6 +139,7 @@ impl Perform for SaveComment {
 impl Perform for CreateCommentLike {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -201,7 +207,8 @@ impl Perform for CreateCommentLike {
       let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
       blocking(context.pool(), like)
         .await?
-        .map_err(|e| ApiError::err("couldnt_like_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_like_comment"))?;
 
       Vote::send(
         &object,
diff --git a/crates/api/src/comment_report.rs b/crates/api/src/comment_report.rs
index f1bf7023..8c3e10e5 100644
--- a/crates/api/src/comment_report.rs
+++ b/crates/api/src/comment_report.rs
@@ -14,7 +14,7 @@ use lemmy_db_views::{
   comment_report_view::{CommentReportQueryBuilder, CommentReportView},
   comment_view::CommentView,
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
 
 /// Creates a comment report and notifies the moderators of the community
@@ -22,6 +22,7 @@ use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}
 impl Perform for CreateCommentReport {
   type Response = CommentReportResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -34,10 +35,10 @@ impl Perform for CreateCommentReport {
     // check size of report and check for whitespace
     let reason = data.reason.trim();
     if reason.is_empty() {
-      return Err(ApiError::err_plain("report_reason_required").into());
+      return Err(LemmyError::from_message("report_reason_required"));
     }
     if reason.chars().count() > 1000 {
-      return Err(ApiError::err_plain("report_too_long").into());
+      return Err(LemmyError::from_message("report_too_long"));
     }
 
     let person_id = local_user_view.person.id;
@@ -60,7 +61,8 @@ impl Perform for CreateCommentReport {
       CommentReport::report(conn, &report_form)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_create_report", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_create_report"))?;
 
     let comment_report_view = blocking(context.pool(), move |conn| {
       CommentReportView::read(conn, report.id, person_id)
@@ -96,6 +98,7 @@ impl Perform for CreateCommentReport {
 impl Perform for ResolveCommentReport {
   type Response = CommentReportResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -126,7 +129,8 @@ impl Perform for ResolveCommentReport {
 
     blocking(context.pool(), resolve_fun)
       .await?
-      .map_err(|e| ApiError::err("couldnt_resolve_report", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_resolve_report"))?;
 
     let report_id = data.report_id;
     let comment_report_view = blocking(context.pool(), move |conn| {
@@ -155,6 +159,7 @@ impl Perform for ResolveCommentReport {
 impl Perform for ListCommentReports {
   type Response = ListCommentReportsResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api/src/community.rs b/crates/api/src/community.rs
index b47a4ec6..c0aea602 100644
--- a/crates/api/src/community.rs
+++ b/crates/api/src/community.rs
@@ -54,13 +54,14 @@ use lemmy_db_views_actor::{
   community_view::CommunityView,
   person_view::PersonViewSafe,
 };
-use lemmy_utils::{location_info, utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{location_info, utils::naive_from_unix, ConnectionId, LemmyError};
 use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
 
 #[async_trait::async_trait(?Send)]
 impl Perform for FollowCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -90,13 +91,15 @@ impl Perform for FollowCommunity {
         let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
         blocking(context.pool(), follow)
           .await?
-          .map_err(|e| ApiError::err("community_follower_already_exists", e))?;
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("community_follower_already_exists"))?;
       } else {
         let unfollow =
           move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
         blocking(context.pool(), unfollow)
           .await?
-          .map_err(|e| ApiError::err("community_follower_already_exists", e))?;
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("community_follower_already_exists"))?;
       }
     } else if data.follow {
       // Dont actually add to the community followers here, because you need
@@ -109,7 +112,8 @@ impl Perform for FollowCommunity {
       let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form);
       blocking(context.pool(), unfollow)
         .await?
-        .map_err(|e| ApiError::err("community_follower_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_follower_already_exists"))?;
     }
 
     let community_id = data.community_id;
@@ -134,6 +138,7 @@ impl Perform for FollowCommunity {
 impl Perform for BlockCommunity {
   type Response = BlockCommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -154,7 +159,8 @@ impl Perform for BlockCommunity {
       let block = move |conn: &'_ _| CommunityBlock::block(conn, &community_block_form);
       blocking(context.pool(), block)
         .await?
-        .map_err(|e| ApiError::err("community_block_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_block_already_exists"))?;
 
       // Also, unfollow the community, and send a federated unfollow
       let community_follower_form = CommunityFollowerForm {
@@ -176,7 +182,8 @@ impl Perform for BlockCommunity {
       let unblock = move |conn: &'_ _| CommunityBlock::unblock(conn, &community_block_form);
       blocking(context.pool(), unblock)
         .await?
-        .map_err(|e| ApiError::err("community_block_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_block_already_exists"))?;
     }
 
     let community_view = blocking(context.pool(), move |conn| {
@@ -195,6 +202,7 @@ impl Perform for BlockCommunity {
 impl Perform for BanFromCommunity {
   type Response = BanFromCommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -230,7 +238,8 @@ impl Perform for BanFromCommunity {
       let ban = move |conn: &'_ _| CommunityPersonBan::ban(conn, &community_user_ban_form);
       blocking(context.pool(), ban)
         .await?
-        .map_err(|e| ApiError::err("community_user_already_banned", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_user_already_banned"))?;
 
       // Also unsubscribe them from the community, if they are subscribed
       let community_follower_form = CommunityFollowerForm {
@@ -255,7 +264,8 @@ impl Perform for BanFromCommunity {
       let unban = move |conn: &'_ _| CommunityPersonBan::unban(conn, &community_user_ban_form);
       blocking(context.pool(), unban)
         .await?
-        .map_err(|e| ApiError::err("community_user_already_banned", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_user_already_banned"))?;
       UndoBlockUserFromCommunity::send(
         &community,
         &banned_person,
@@ -336,6 +346,7 @@ impl Perform for BanFromCommunity {
 impl Perform for AddModToCommunity {
   type Response = AddModToCommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -359,12 +370,14 @@ impl Perform for AddModToCommunity {
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
       blocking(context.pool(), join)
         .await?
-        .map_err(|e| ApiError::err("community_moderator_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     } else {
       let leave = move |conn: &'_ _| CommunityModerator::leave(conn, &community_moderator_form);
       blocking(context.pool(), leave)
         .await?
-        .map_err(|e| ApiError::err("community_moderator_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     }
 
     // Mod tables
@@ -434,6 +447,7 @@ impl Perform for AddModToCommunity {
 impl Perform for TransferCommunity {
   type Response = GetCommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -472,7 +486,7 @@ impl Perform for TransferCommunity {
         .map(|a| a.person.id)
         .any(|x| x == local_user_view.person.id)
     {
-      return Err(ApiError::err_plain("not_an_admin").into());
+      return Err(LemmyError::from_message("not_an_admin"));
     }
 
     // You have to re-do the community_moderator table, reordering it.
@@ -502,7 +516,8 @@ impl Perform for TransferCommunity {
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
       blocking(context.pool(), join)
         .await?
-        .map_err(|e| ApiError::err("community_moderator_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     }
 
     // Mod tables
@@ -523,14 +538,16 @@ impl Perform for TransferCommunity {
       CommunityView::read(conn, community_id, Some(person_id))
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     let community_id = data.community_id;
     let moderators = blocking(context.pool(), move |conn| {
       CommunityModeratorView::for_community(conn, community_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     // Return the jwt
     Ok(GetCommunityResponse {
diff --git a/crates/api/src/local_user.rs b/crates/api/src/local_user.rs
index 6f63d14b..a0711d69 100644
--- a/crates/api/src/local_user.rs
+++ b/crates/api/src/local_user.rs
@@ -49,9 +49,9 @@ use lemmy_utils::{
   email::send_email,
   location_info,
   utils::{generate_random_string, is_valid_display_name, is_valid_matrix_id, naive_from_unix},
-  ApiError,
   ConnectionId,
   LemmyError,
+  Sensitive,
 };
 use lemmy_websocket::{
   messages::{CaptchaItem, SendAllMessage},
@@ -63,6 +63,7 @@ use lemmy_websocket::{
 impl Perform for Login {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -76,7 +77,8 @@ impl Perform for Login {
       LocalUserView::find_by_email_or_name(conn, &username_or_email)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_that_username_or_email", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
 
     // Verify the password
     let valid: bool = verify(
@@ -85,7 +87,7 @@ impl Perform for Login {
     )
     .unwrap_or(false);
     if !valid {
-      return Err(ApiError::err_plain("password_incorrect").into());
+      return Err(LemmyError::from_message("password_incorrect"));
     }
 
     // Return the jwt
@@ -94,7 +96,8 @@ impl Perform for Login {
         local_user_view.local_user.id.0,
         &context.secret().jwt_secret,
         &context.settings().hostname,
-      )?,
+      )?
+      .into(),
     })
   }
 }
@@ -103,6 +106,7 @@ impl Perform for Login {
 impl Perform for GetCaptcha {
   type Response = GetCaptchaResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -148,6 +152,7 @@ impl Perform for GetCaptcha {
 impl Perform for SaveUserSettings {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -159,7 +164,7 @@ impl Perform for SaveUserSettings {
 
     let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
     let banner = diesel_option_overwrite_to_url(&data.banner)?;
-    let email = diesel_option_overwrite(&data.email);
+    let email = diesel_option_overwrite(&data.email.clone().map(Sensitive::into_inner));
     let bio = diesel_option_overwrite(&data.bio);
     let display_name = diesel_option_overwrite(&data.display_name);
     let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
@@ -167,7 +172,7 @@ impl Perform for SaveUserSettings {
 
     if let Some(Some(bio)) = &bio {
       if bio.chars().count() > 300 {
-        return Err(ApiError::err_plain("bio_length_overflow").into());
+        return Err(LemmyError::from_message("bio_length_overflow"));
       }
     }
 
@@ -176,13 +181,13 @@ impl Perform for SaveUserSettings {
         display_name.trim(),
         context.settings().actor_name_max_length,
       ) {
-        return Err(ApiError::err_plain("invalid_username").into());
+        return Err(LemmyError::from_message("invalid_username"));
       }
     }
 
     if let Some(Some(matrix_user_id)) = &matrix_user_id {
       if !is_valid_matrix_id(matrix_user_id) {
-        return Err(ApiError::err_plain("invalid_matrix_id").into());
+        return Err(LemmyError::from_message("invalid_matrix_id"));
       }
     }
 
@@ -219,7 +224,8 @@ impl Perform for SaveUserSettings {
       Person::update(conn, person_id, &person_form)
     })
     .await?
-    .map_err(|e| ApiError::err("user_already_exists", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("user_already_exists"))?;
 
     let local_user_form = LocalUserForm {
       person_id,
@@ -253,7 +259,7 @@ impl Perform for SaveUserSettings {
           "user_already_exists"
         };
 
-        return Err(ApiError::err(err_type, e).into());
+        return Err(LemmyError::from(e).with_message(err_type));
       }
     };
 
@@ -263,7 +269,8 @@ impl Perform for SaveUserSettings {
         updated_local_user.id.0,
         &context.secret().jwt_secret,
         &context.settings().hostname,
-      )?,
+      )?
+      .into(),
     })
   }
 }
@@ -272,6 +279,7 @@ impl Perform for SaveUserSettings {
 impl Perform for ChangePassword {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -279,13 +287,13 @@ impl Perform for ChangePassword {
   ) -> Result<LoginResponse, LemmyError> {
     let data: &ChangePassword = self;
     let local_user_view =
-      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
 
     password_length_check(&data.new_password)?;
 
     // Make sure passwords match
     if data.new_password != data.new_password_verify {
-      return Err(ApiError::err_plain("passwords_dont_match").into());
+      return Err(LemmyError::from_message("passwords_dont_match"));
     }
 
     // Check the old password
@@ -295,7 +303,7 @@ impl Perform for ChangePassword {
     )
     .unwrap_or(false);
     if !valid {
-      return Err(ApiError::err_plain("password_incorrect").into());
+      return Err(LemmyError::from_message("password_incorrect"));
     }
 
     let local_user_id = local_user_view.local_user.id;
@@ -311,7 +319,8 @@ impl Perform for ChangePassword {
         updated_local_user.id.0,
         &context.secret().jwt_secret,
         &context.settings().hostname,
-      )?,
+      )?
+      .into(),
     })
   }
 }
@@ -320,6 +329,7 @@ impl Perform for ChangePassword {
 impl Perform for AddAdmin {
   type Response = AddAdminResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -338,7 +348,8 @@ impl Perform for AddAdmin {
       Person::add_admin(conn, added_person_id, added)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_user", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_user"))?;
 
     // Mod tables
     let form = ModAddForm {
@@ -378,6 +389,7 @@ impl Perform for AddAdmin {
 impl Perform for BanPerson {
   type Response = BanPersonResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -395,7 +407,8 @@ impl Perform for BanPerson {
     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban);
     blocking(context.pool(), ban_person)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_user", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_user"))?;
 
     // Remove their data if that's desired
     if data.remove_data.unwrap_or(false) {
@@ -471,6 +484,7 @@ impl Perform for BanPerson {
 impl Perform for BlockPerson {
   type Response = BlockPersonResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -485,7 +499,7 @@ impl Perform for BlockPerson {
 
     // Don't let a person block themselves
     if target_id == person_id {
-      return Err(ApiError::err_plain("cant_block_yourself").into());
+      return Err(LemmyError::from_message("cant_block_yourself"));
     }
 
     let person_block_form = PersonBlockForm {
@@ -497,12 +511,14 @@ impl Perform for BlockPerson {
       let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
       blocking(context.pool(), block)
         .await?
-        .map_err(|e| ApiError::err("person_block_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("person_block_already_exists"))?;
     } else {
       let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
       blocking(context.pool(), unblock)
         .await?
-        .map_err(|e| ApiError::err("person_block_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("person_block_already_exists"))?;
     }
 
     // TODO does any federated stuff need to be done here?
@@ -525,6 +541,7 @@ impl Perform for BlockPerson {
 impl Perform for GetReplies {
   type Response = GetRepliesResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -563,6 +580,7 @@ impl Perform for GetReplies {
 impl Perform for GetPersonMentions {
   type Response = GetPersonMentionsResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -598,6 +616,7 @@ impl Perform for GetPersonMentions {
 impl Perform for MarkPersonMentionAsRead {
   type Response = PersonMentionResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -614,7 +633,7 @@ impl Perform for MarkPersonMentionAsRead {
     .await??;
 
     if local_user_view.person.id != read_person_mention.recipient_id {
-      return Err(ApiError::err_plain("couldnt_update_comment").into());
+      return Err(LemmyError::from_message("couldnt_update_comment"));
     }
 
     let person_mention_id = read_person_mention.id;
@@ -623,7 +642,8 @@ impl Perform for MarkPersonMentionAsRead {
       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
     blocking(context.pool(), update_mention)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     let person_mention_id = read_person_mention.id;
     let person_id = local_user_view.person.id;
@@ -642,6 +662,7 @@ impl Perform for MarkPersonMentionAsRead {
 impl Perform for MarkAllAsRead {
   type Response = GetRepliesResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -671,7 +692,8 @@ impl Perform for MarkAllAsRead {
       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
       blocking(context.pool(), mark_as_read)
         .await?
-        .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_update_comment"))?;
     }
 
     // Mark all user mentions as read
@@ -679,13 +701,15 @@ impl Perform for MarkAllAsRead {
       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
     blocking(context.pool(), update_person_mentions)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     // Mark all private_messages as read
     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
     blocking(context.pool(), update_pm)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_private_message", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_private_message"))?;
 
     Ok(GetRepliesResponse { replies: vec![] })
   }
@@ -695,6 +719,7 @@ impl Perform for MarkAllAsRead {
 impl Perform for PasswordReset {
   type Response = PasswordResetResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -708,7 +733,8 @@ impl Perform for PasswordReset {
       LocalUserView::find_by_email(conn, &email)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_that_username_or_email", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
 
     // Generate a random token
     let token = generate_random_string();
@@ -734,7 +760,9 @@ impl Perform for PasswordReset {
       html,
       &context.settings(),
     )
-    .map_err(|e| ApiError::err("email_send_failed", e))?;
+    .map_err(|e| anyhow::anyhow!("{}", e))
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("email_send_failed"))?;
 
     Ok(PasswordResetResponse {})
   }
@@ -744,6 +772,7 @@ impl Perform for PasswordReset {
 impl Perform for PasswordChange {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -762,7 +791,7 @@ impl Perform for PasswordChange {
 
     // Make sure passwords match
     if data.password != data.password_verify {
-      return Err(ApiError::err_plain("passwords_dont_match").into());
+      return Err(LemmyError::from_message("passwords_dont_match"));
     }
 
     // Update the user with the new password
@@ -771,7 +800,8 @@ impl Perform for PasswordChange {
       LocalUser::update_password(conn, local_user_id, &password)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_user", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_user"))?;
 
     // Return the jwt
     Ok(LoginResponse {
@@ -779,7 +809,8 @@ impl Perform for PasswordChange {
         updated_local_user.id.0,
         &context.secret().jwt_secret,
         &context.settings().hostname,
-      )?,
+      )?
+      .into(),
     })
   }
 }
@@ -788,6 +819,7 @@ impl Perform for PasswordChange {
 impl Perform for GetReportCount {
   type Response = GetReportCountResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -825,6 +857,7 @@ impl Perform for GetReportCount {
 impl Perform for GetUnreadCount {
   type Response = GetUnreadCountResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api/src/post.rs b/crates/api/src/post.rs
index 60a580b8..64c3a930 100644
--- a/crates/api/src/post.rs
+++ b/crates/api/src/post.rs
@@ -29,7 +29,7 @@ use lemmy_db_schema::{
   traits::{Crud, Likeable, Saveable},
 };
 use lemmy_db_views::post_view::PostView;
-use lemmy_utils::{request::fetch_site_metadata, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{request::fetch_site_metadata, ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperation};
 use std::convert::TryInto;
 
@@ -37,6 +37,7 @@ use std::convert::TryInto;
 impl Perform for CreatePostLike {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -83,7 +84,8 @@ impl Perform for CreatePostLike {
       let like = move |conn: &'_ _| PostLike::like(conn, &like_form2);
       blocking(context.pool(), like)
         .await?
-        .map_err(|e| ApiError::err("couldnt_like_post", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_like_post"))?;
 
       Vote::send(
         &object,
@@ -123,6 +125,7 @@ impl Perform for CreatePostLike {
 impl Perform for MarkPostAsRead {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -158,6 +161,7 @@ impl Perform for MarkPostAsRead {
 impl Perform for LockPost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -227,6 +231,7 @@ impl Perform for LockPost {
 impl Perform for StickyPost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -300,6 +305,7 @@ impl Perform for StickyPost {
 impl Perform for SavePost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -318,12 +324,14 @@ impl Perform for SavePost {
       let save = move |conn: &'_ _| PostSaved::save(conn, &post_saved_form);
       blocking(context.pool(), save)
         .await?
-        .map_err(|e| ApiError::err("couldnt_save_post", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_save_post"))?;
     } else {
       let unsave = move |conn: &'_ _| PostSaved::unsave(conn, &post_saved_form);
       blocking(context.pool(), unsave)
         .await?
-        .map_err(|e| ApiError::err("couldnt_save_post", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_save_post"))?;
     }
 
     let post_id = data.post_id;
@@ -344,6 +352,7 @@ impl Perform for SavePost {
 impl Perform for GetSiteMetadata {
   type Response = GetSiteMetadataResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api/src/post_report.rs b/crates/api/src/post_report.rs
index 0e47f592..02afc795 100644
--- a/crates/api/src/post_report.rs
+++ b/crates/api/src/post_report.rs
@@ -23,7 +23,7 @@ use lemmy_db_views::{
   post_report_view::{PostReportQueryBuilder, PostReportView},
   post_view::PostView,
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
 
 /// Creates a post report and notifies the moderators of the community
@@ -31,6 +31,7 @@ use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}
 impl Perform for CreatePostReport {
   type Response = PostReportResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -43,10 +44,10 @@ impl Perform for CreatePostReport {
     // check size of report and check for whitespace
     let reason = data.reason.trim();
     if reason.is_empty() {
-      return Err(ApiError::err_plain("report_reason_required").into());
+      return Err(LemmyError::from_message("report_reason_required"));
     }
     if reason.chars().count() > 1000 {
-      return Err(ApiError::err_plain("report_too_long").into());
+      return Err(LemmyError::from_message("report_too_long"));
     }
 
     let person_id = local_user_view.person.id;
@@ -71,7 +72,8 @@ impl Perform for CreatePostReport {
       PostReport::report(conn, &report_form)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_create_report", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_create_report"))?;
 
     let post_report_view = blocking(context.pool(), move |conn| {
       PostReportView::read(conn, report.id, person_id)
@@ -105,6 +107,7 @@ impl Perform for CreatePostReport {
 impl Perform for ResolvePostReport {
   type Response = PostReportResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -135,7 +138,8 @@ impl Perform for ResolvePostReport {
 
     blocking(context.pool(), resolve_fun)
       .await?
-      .map_err(|e| ApiError::err("couldnt_resolve_report", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_resolve_report"))?;
 
     let post_report_view = blocking(context.pool(), move |conn| {
       PostReportView::read(conn, report_id, person_id)
@@ -161,6 +165,7 @@ impl Perform for ResolvePostReport {
 impl Perform for ListPostReports {
   type Response = ListPostReportsResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api/src/private_message.rs b/crates/api/src/private_message.rs
index 1c80f44f..b0d04731 100644
--- a/crates/api/src/private_message.rs
+++ b/crates/api/src/private_message.rs
@@ -6,13 +6,14 @@ use lemmy_api_common::{
   person::{MarkPrivateMessageAsRead, PrivateMessageResponse},
 };
 use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperation};
 
 #[async_trait::async_trait(?Send)]
 impl Perform for MarkPrivateMessageAsRead {
   type Response = PrivateMessageResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -29,7 +30,7 @@ impl Perform for MarkPrivateMessageAsRead {
     })
     .await??;
     if local_user_view.person.id != orig_private_message.recipient_id {
-      return Err(ApiError::err_plain("couldnt_update_private_message").into());
+      return Err(LemmyError::from_message("couldnt_update_private_message"));
     }
 
     // Doing the update
@@ -39,7 +40,8 @@ impl Perform for MarkPrivateMessageAsRead {
       PrivateMessage::update_read(conn, private_message_id, read)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_private_message", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_private_message"))?;
 
     // No need to send an apub update
     let op = UserOperation::MarkPrivateMessageAsRead;
diff --git a/crates/api/src/site.rs b/crates/api/src/site.rs
index be9dc5da..99906bf6 100644
--- a/crates/api/src/site.rs
+++ b/crates/api/src/site.rs
@@ -49,20 +49,14 @@ use lemmy_db_views_moderator::{
   mod_sticky_post_view::ModStickyPostView,
   mod_transfer_community_view::ModTransferCommunityView,
 };
-use lemmy_utils::{
-  location_info,
-  settings::structs::Settings,
-  version,
-  ApiError,
-  ConnectionId,
-  LemmyError,
-};
+use lemmy_utils::{location_info, settings::structs::Settings, version, ConnectionId, LemmyError};
 use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl Perform for GetModlog {
   type Response = GetModlogResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -143,6 +137,7 @@ impl Perform for GetModlog {
 impl Perform for Search {
   type Response = SearchResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -151,7 +146,8 @@ impl Perform for Search {
     let data: &Search = self;
 
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
     let show_bot_accounts = local_user_view
@@ -383,19 +379,23 @@ impl Perform for Search {
 impl Perform for ResolveObject {
   type Response = ResolveObjectResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<ResolveObjectResponse, LemmyError> {
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&self.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
+        .await?;
     let res = search_by_apub_id(&self.q, context)
       .await
-      .map_err(|e| ApiError::err("couldnt_find_object", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_find_object"))?;
     convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
       .await
-      .map_err(|e| ApiError::err("couldnt_find_object", e).into())
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_find_object"))
   }
 }
 
@@ -442,6 +442,7 @@ async fn convert_response(
 impl Perform for TransferSite {
   type Response = GetSiteResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -457,14 +458,15 @@ impl Perform for TransferSite {
 
     // Make sure user is the creator
     if read_site.creator_id != local_user_view.person.id {
-      return Err(ApiError::err_plain("not_an_admin").into());
+      return Err(LemmyError::from_message("not_an_admin"));
     }
 
     let new_creator_id = data.person_id;
     let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
     blocking(context.pool(), transfer_site)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_site", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_site"))?;
 
     // Mod tables
     let form = ModAddForm {
@@ -509,6 +511,7 @@ impl Perform for TransferSite {
 impl Perform for GetSiteConfig {
   type Response = GetSiteConfigResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -531,6 +534,7 @@ impl Perform for GetSiteConfig {
 impl Perform for SaveSiteConfig {
   type Response = GetSiteConfigResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -545,7 +549,8 @@ impl Perform for SaveSiteConfig {
 
     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
     let config_hjson = Settings::save_config_file(&data.config_hjson)
-      .map_err(|e| ApiError::err("couldnt_update_site", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_site"))?;
 
     Ok(GetSiteConfigResponse { config_hjson })
   }
diff --git a/crates/api/src/websocket.rs b/crates/api/src/websocket.rs
index fa03e4ec..00a7e686 100644
--- a/crates/api/src/websocket.rs
+++ b/crates/api/src/websocket.rs
@@ -11,6 +11,7 @@ use lemmy_websocket::{
 impl Perform for UserJoin {
   type Response = UserJoinResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -35,6 +36,7 @@ impl Perform for UserJoin {
 impl Perform for CommunityJoin {
   type Response = CommunityJoinResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -57,6 +59,7 @@ impl Perform for CommunityJoin {
 impl Perform for ModJoin {
   type Response = ModJoinResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -79,6 +82,7 @@ impl Perform for ModJoin {
 impl Perform for PostJoin {
   type Response = PostJoinResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml
index 078987eb..553ccb5b 100644
--- a/crates/api_common/Cargo.toml
+++ b/crates/api_common/Cargo.toml
@@ -23,4 +23,5 @@ diesel = "1.4.8"
 actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] }
 chrono = { version = "0.4.19", features = ["serde"] }
 serde_json = { version = "1.0.68", features = ["preserve_order"] }
+tracing = "0.1.29"
 url = "2.2.2"
diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs
index afe76a99..84717800 100644
--- a/crates/api_common/src/comment.rs
+++ b/crates/api_common/src/comment.rs
@@ -1,74 +1,75 @@
 use lemmy_db_schema::newtypes::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId};
 use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView};
+use lemmy_utils::Sensitive;
 use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreateComment {
   pub content: String,
   pub post_id: PostId,
   pub parent_id: Option<CommentId>,
   pub form_id: Option<String>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetComment {
   pub id: CommentId,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct EditComment {
   pub content: String,
   pub comment_id: CommentId,
   pub form_id: Option<String>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct DeleteComment {
   pub comment_id: CommentId,
   pub deleted: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct RemoveComment {
   pub comment_id: CommentId,
   pub removed: bool,
   pub reason: Option<String>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MarkCommentAsRead {
   pub comment_id: CommentId,
   pub read: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct SaveComment {
   pub comment_id: CommentId,
   pub save: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct CommentResponse {
   pub comment_view: CommentView,
   pub recipient_ids: Vec<LocalUserId>,
   pub form_id: Option<String>, // An optional front end ID, to tell which is coming back
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreateCommentLike {
   pub comment_id: CommentId,
   pub score: i16,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetComments {
   pub type_: Option<String>,
   pub sort: Option<String>,
@@ -77,34 +78,34 @@ pub struct GetComments {
   pub community_id: Option<CommunityId>,
   pub community_name: Option<String>,
   pub saved_only: Option<bool>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetCommentsResponse {
   pub comments: Vec<CommentView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreateCommentReport {
   pub comment_id: CommentId,
   pub reason: String,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct CommentReportResponse {
   pub comment_report_view: CommentReportView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ResolveCommentReport {
   pub report_id: CommentReportId,
   pub resolved: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ListCommentReports {
   pub page: Option<i64>,
   pub limit: Option<i64>,
@@ -112,10 +113,10 @@ pub struct ListCommentReports {
   pub unresolved_only: Option<bool>,
   /// if no community is given, it returns reports for all communities moderated by the auth user
   pub community_id: Option<CommunityId>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ListCommentReportsResponse {
   pub comment_reports: Vec<CommentReportView>,
 }
diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs
index 1f32267f..9686172a 100644
--- a/crates/api_common/src/community.rs
+++ b/crates/api_common/src/community.rs
@@ -4,24 +4,25 @@ use lemmy_db_views_actor::{
   community_view::CommunityView,
   person_view::PersonViewSafe,
 };
+use lemmy_utils::Sensitive;
 use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetCommunity {
   pub id: Option<CommunityId>,
   /// Example: star_trek , or star_trek@xyz.tld
   pub name: Option<String>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetCommunityResponse {
   pub community_view: CommunityView,
   pub moderators: Vec<CommunityModeratorView>,
   pub online: usize,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreateCommunity {
   pub name: String,
   pub title: String,
@@ -29,10 +30,10 @@ pub struct CreateCommunity {
   pub icon: Option<String>,
   pub banner: Option<String>,
   pub nsfw: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct CommunityResponse {
   pub community_view: CommunityView,
 }
@@ -43,7 +44,7 @@ pub struct ListCommunities {
   pub sort: Option<String>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -51,7 +52,7 @@ pub struct ListCommunitiesResponse {
   pub communities: Vec<CommunityView>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct BanFromCommunity {
   pub community_id: CommunityId,
   pub person_id: PersonId,
@@ -59,29 +60,29 @@ pub struct BanFromCommunity {
   pub remove_data: Option<bool>,
   pub reason: Option<String>,
   pub expires: Option<i64>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct BanFromCommunityResponse {
   pub person_view: PersonViewSafe,
   pub banned: bool,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct AddModToCommunity {
   pub community_id: CommunityId,
   pub person_id: PersonId,
   pub added: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct AddModToCommunityResponse {
   pub moderators: Vec<CommunityModeratorView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct EditCommunity {
   pub community_id: CommunityId,
   pub title: Option<String>,
@@ -89,48 +90,48 @@ pub struct EditCommunity {
   pub icon: Option<String>,
   pub banner: Option<String>,
   pub nsfw: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct DeleteCommunity {
   pub community_id: CommunityId,
   pub deleted: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct RemoveCommunity {
   pub community_id: CommunityId,
   pub removed: bool,
   pub reason: Option<String>,
   pub expires: Option<i64>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct FollowCommunity {
   pub community_id: CommunityId,
   pub follow: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct BlockCommunity {
   pub community_id: CommunityId,
   pub block: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct BlockCommunityResponse {
   pub community_view: CommunityView,
   pub blocked: bool,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct TransferCommunity {
   pub community_id: CommunityId,
   pub person_id: PersonId,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs
index f5b6ebad..29e26e1e 100644
--- a/crates/api_common/src/lib.rs
+++ b/crates/api_common/src/lib.rs
@@ -23,7 +23,7 @@ use lemmy_db_views_actor::{
   community_person_ban_view::CommunityPersonBanView,
   community_view::CommunityView,
 };
-use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, ApiError, LemmyError};
+use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, LemmyError, Sensitive};
 use url::Url;
 
 pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
@@ -32,9 +32,12 @@ where
   T: Send + 'static,
 {
   let pool = pool.clone();
+  let blocking_span = tracing::info_span!("blocking operation");
   let res = actix_web::web::block(move || {
+    let entered = blocking_span.enter();
     let conn = pool.get()?;
     let res = (f)(&conn);
+    drop(entered);
     Ok(res) as Result<T, LemmyError>
   })
   .await?;
@@ -52,14 +55,14 @@ pub async fn is_mod_or_admin(
   })
   .await?;
   if !is_mod_or_admin {
-    return Err(ApiError::err_plain("not_a_mod_or_admin").into());
+    return Err(LemmyError::from_message("not_a_mod_or_admin"));
   }
   Ok(())
 }
 
 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
   if !local_user_view.person.admin {
-    return Err(ApiError::err_plain("not_an_admin").into());
+    return Err(LemmyError::from_message("not_an_admin"));
   }
   Ok(())
 }
@@ -67,7 +70,8 @@ pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
 pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
   blocking(pool, move |conn| Post::read(conn, post_id))
     .await?
-    .map_err(|_| ApiError::err_plain("couldnt_find_post").into())
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_post"))
 }
 
 pub async fn mark_post_as_read(
@@ -81,7 +85,8 @@ pub async fn mark_post_as_read(
     PostRead::mark_as_read(conn, &post_read_form)
   })
   .await?
-  .map_err(|e| ApiError::err("couldnt_mark_post_as_read", e).into())
+  .map_err(LemmyError::from)
+  .map_err(|e| e.with_message("couldnt_mark_post_as_read"))
 }
 
 pub async fn mark_post_as_unread(
@@ -95,7 +100,8 @@ pub async fn mark_post_as_unread(
     PostRead::mark_as_unread(conn, &post_read_form)
   })
   .await?
-  .map_err(|e| ApiError::err("couldnt_mark_post_as_read", e).into())
+  .map_err(LemmyError::from)
+  .map_err(|e| e.with_message("couldnt_mark_post_as_read"))
 }
 
 pub async fn get_local_user_view_from_jwt(
@@ -104,19 +110,20 @@ pub async fn get_local_user_view_from_jwt(
   secret: &Secret,
 ) -> Result<LocalUserView, LemmyError> {
   let claims = Claims::decode(jwt, &secret.jwt_secret)
-    .map_err(|e| ApiError::err("not_logged_in", e))?
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("not_logged_in"))?
     .claims;
   let local_user_id = LocalUserId(claims.sub);
   let local_user_view =
     blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
   // Check for a site ban
   if local_user_view.person.banned {
-    return Err(ApiError::err_plain("site_ban").into());
+    return Err(LemmyError::from_message("site_ban"));
   }
 
   // Check for user deletion
   if local_user_view.person.deleted {
-    return Err(ApiError::err_plain("deleted").into());
+    return Err(LemmyError::from_message("deleted"));
   }
 
   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
@@ -131,14 +138,14 @@ pub fn check_validator_time(
 ) -> Result<(), LemmyError> {
   let user_validation_time = validator_time.timestamp();
   if user_validation_time > claims.iat {
-    Err(ApiError::err_plain("not_logged_in").into())
+    Err(LemmyError::from_message("not_logged_in"))
   } else {
     Ok(())
   }
 }
 
 pub async fn get_local_user_view_from_jwt_opt(
-  jwt: &Option<String>,
+  jwt: Option<&Sensitive<String>>,
   pool: &DbPool,
   secret: &Secret,
 ) -> Result<Option<LocalUserView>, LemmyError> {
@@ -149,12 +156,13 @@ pub async fn get_local_user_view_from_jwt_opt(
 }
 
 pub async fn get_local_user_settings_view_from_jwt(
-  jwt: &str,
+  jwt: &Sensitive<String>,
   pool: &DbPool,
   secret: &Secret,
 ) -> Result<LocalUserSettingsView, LemmyError> {
-  let claims = Claims::decode(jwt, &secret.jwt_secret)
-    .map_err(|e| ApiError::err("not_logged_in", e))?
+  let claims = Claims::decode(jwt.as_ref(), &secret.jwt_secret)
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("not_logged_in"))?
     .claims;
   let local_user_id = LocalUserId(claims.sub);
   let local_user_view = blocking(pool, move |conn| {
@@ -163,7 +171,7 @@ pub async fn get_local_user_settings_view_from_jwt(
   .await??;
   // Check for a site ban
   if local_user_view.person.banned {
-    return Err(ApiError::err_plain("site_ban").into());
+    return Err(LemmyError::from_message("site_ban"));
   }
 
   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
@@ -172,7 +180,7 @@ pub async fn get_local_user_settings_view_from_jwt(
 }
 
 pub async fn get_local_user_settings_view_from_jwt_opt(
-  jwt: &Option<String>,
+  jwt: Option<&Sensitive<String>>,
   pool: &DbPool,
   secret: &Secret,
 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
@@ -192,7 +200,7 @@ pub async fn check_community_ban(
   let is_banned =
     move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
   if blocking(pool, is_banned).await? {
-    Err(ApiError::err_plain("community_ban").into())
+    Err(LemmyError::from_message("community_ban"))
   } else {
     Ok(())
   }
@@ -204,9 +212,10 @@ pub async fn check_community_deleted_or_removed(
 ) -> Result<(), LemmyError> {
   let community = blocking(pool, move |conn| Community::read(conn, community_id))
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
   if community.deleted || community.removed {
-    Err(ApiError::err_plain("deleted").into())
+    Err(LemmyError::from_message("deleted"))
   } else {
     Ok(())
   }
@@ -214,7 +223,7 @@ pub async fn check_community_deleted_or_removed(
 
 pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
   if post.deleted || post.removed {
-    Err(ApiError::err_plain("deleted").into())
+    Err(LemmyError::from_message("deleted"))
   } else {
     Ok(())
   }
@@ -227,7 +236,7 @@ pub async fn check_person_block(
 ) -> Result<(), LemmyError> {
   let is_blocked = move |conn: &'_ _| PersonBlock::read(conn, potential_blocker_id, my_id).is_ok();
   if blocking(pool, is_blocked).await? {
-    Err(ApiError::err_plain("person_block").into())
+    Err(LemmyError::from_message("person_block"))
   } else {
     Ok(())
   }
@@ -237,7 +246,7 @@ pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), Le
   if score == -1 {
     let site = blocking(pool, Site::read_simple).await??;
     if !site.enable_downvotes {
-      return Err(ApiError::err_plain("downvotes_disabled").into());
+      return Err(LemmyError::from_message("downvotes_disabled"));
     }
   }
   Ok(())
@@ -288,7 +297,7 @@ pub async fn build_federated_instances(
 /// Checks the password length
 pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
   if !(10..=60).contains(&pass.len()) {
-    Err(ApiError::err_plain("invalid_password").into())
+    Err(LemmyError::from_message("invalid_password"))
   } else {
     Ok(())
   }
@@ -297,7 +306,7 @@ pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
 /// Checks the site description length
 pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
   if description.len() > 150 {
-    Err(ApiError::err_plain("site_description_length_overflow").into())
+    Err(LemmyError::from_message("site_description_length_overflow"))
   } else {
     Ok(())
   }
@@ -306,7 +315,7 @@ pub fn site_description_length_check(description: &str) -> Result<(), LemmyError
 /// Checks for a honeypot. If this field is filled, fail the rest of the function
 pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
   if honeypot.is_some() {
-    Err(ApiError::err_plain("honeypot_fail").into())
+    Err(LemmyError::from_message("honeypot_fail"))
   } else {
     Ok(())
   }
diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs
index b93d47ea..5dddc5dc 100644
--- a/crates/api_common/src/person.rs
+++ b/crates/api_common/src/person.rs
@@ -8,43 +8,44 @@ use lemmy_db_views_actor::{
   person_mention_view::PersonMentionView,
   person_view::PersonViewSafe,
 };
+use lemmy_utils::Sensitive;
 use serde::{Deserialize, Serialize};
 
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct Login {
-  pub username_or_email: String,
-  pub password: String,
+  pub username_or_email: Sensitive<String>,
+  pub password: Sensitive<String>,
 }
 use lemmy_db_schema::newtypes::{CommunityId, PersonId, PersonMentionId, PrivateMessageId};
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct Register {
   pub username: String,
-  pub password: String,
-  pub password_verify: String,
+  pub password: Sensitive<String>,
+  pub password_verify: Sensitive<String>,
   pub show_nsfw: bool,
-  pub email: Option<String>,
+  pub email: Option<Sensitive<String>>,
   pub captcha_uuid: Option<String>,
   pub captcha_answer: Option<String>,
   pub honeypot: Option<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetCaptcha {}
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetCaptchaResponse {
   pub ok: Option<CaptchaResponse>, // Will be None if captchas are disabled
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CaptchaResponse {
   pub png: String, // A Base64 encoded png
   pub wav: String, // A Base64 encoded wav audio
   pub uuid: String,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct SaveUserSettings {
   pub show_nsfw: Option<bool>,
   pub show_scores: Option<bool>,
@@ -55,7 +56,7 @@ pub struct SaveUserSettings {
   pub avatar: Option<String>,
   pub banner: Option<String>,
   pub display_name: Option<String>,
-  pub email: Option<String>,
+  pub email: Option<Sensitive<String>>,
   pub bio: Option<String>,
   pub matrix_user_id: Option<String>,
   pub show_avatars: Option<bool>,
@@ -64,23 +65,23 @@ pub struct SaveUserSettings {
   pub show_bot_accounts: Option<bool>,
   pub show_read_posts: Option<bool>,
   pub show_new_post_notifs: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ChangePassword {
-  pub new_password: String,
-  pub new_password_verify: String,
-  pub old_password: String,
-  pub auth: String,
+  pub new_password: Sensitive<String>,
+  pub new_password_verify: Sensitive<String>,
+  pub old_password: Sensitive<String>,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct LoginResponse {
-  pub jwt: String,
+  pub jwt: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPersonDetails {
   pub person_id: Option<PersonId>, // One of these two are required
   /// Example: dessalines , or dessalines@xyz.tld
@@ -90,10 +91,10 @@ pub struct GetPersonDetails {
   pub limit: Option<i64>,
   pub community_id: Option<CommunityId>,
   pub saved_only: Option<bool>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPersonDetailsResponse {
   pub person_view: PersonViewSafe,
   pub comments: Vec<CommentView>,
@@ -101,178 +102,178 @@ pub struct GetPersonDetailsResponse {
   pub moderates: Vec<CommunityModeratorView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetRepliesResponse {
   pub replies: Vec<CommentView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPersonMentionsResponse {
   pub mentions: Vec<PersonMentionView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MarkAllAsRead {
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct AddAdmin {
   pub person_id: PersonId,
   pub added: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct AddAdminResponse {
   pub admins: Vec<PersonViewSafe>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct BanPerson {
   pub person_id: PersonId,
   pub ban: bool,
   pub remove_data: Option<bool>,
   pub reason: Option<String>,
   pub expires: Option<i64>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct BanPersonResponse {
   pub person_view: PersonViewSafe,
   pub banned: bool,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct BlockPerson {
   pub person_id: PersonId,
   pub block: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct BlockPersonResponse {
   pub person_view: PersonViewSafe,
   pub blocked: bool,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetReplies {
   pub sort: Option<String>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
   pub unread_only: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPersonMentions {
   pub sort: Option<String>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
   pub unread_only: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MarkPersonMentionAsRead {
   pub person_mention_id: PersonMentionId,
   pub read: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PersonMentionResponse {
   pub person_mention_view: PersonMentionView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct DeleteAccount {
-  pub password: String,
-  pub auth: String,
+  pub password: Sensitive<String>,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct PasswordReset {
-  pub email: String,
+  pub email: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PasswordResetResponse {}
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct PasswordChange {
-  pub token: String,
-  pub password: String,
-  pub password_verify: String,
+  pub token: Sensitive<String>,
+  pub password: Sensitive<String>,
+  pub password_verify: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreatePrivateMessage {
   pub content: String,
   pub recipient_id: PersonId,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct EditPrivateMessage {
   pub private_message_id: PrivateMessageId,
   pub content: String,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct DeletePrivateMessage {
   pub private_message_id: PrivateMessageId,
   pub deleted: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MarkPrivateMessageAsRead {
   pub private_message_id: PrivateMessageId,
   pub read: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPrivateMessages {
   pub unread_only: Option<bool>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PrivateMessagesResponse {
   pub private_messages: Vec<PrivateMessageView>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PrivateMessageResponse {
   pub private_message_view: PrivateMessageView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetReportCount {
   pub community_id: Option<CommunityId>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct GetReportCountResponse {
   pub community_id: Option<CommunityId>,
   pub comment_reports: i64,
   pub post_reports: i64,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetUnreadCount {
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct GetUnreadCountResponse {
   pub replies: i64,
   pub mentions: i64,
diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs
index 6e2c3179..7f8ea319 100644
--- a/crates/api_common/src/post.rs
+++ b/crates/api_common/src/post.rs
@@ -8,7 +8,7 @@ use lemmy_db_views_actor::{
   community_moderator_view::CommunityModeratorView,
   community_view::CommunityView,
 };
-use lemmy_utils::request::SiteMetadata;
+use lemmy_utils::{request::SiteMetadata, Sensitive};
 use serde::{Deserialize, Serialize};
 use url::Url;
 
@@ -20,21 +20,21 @@ pub struct CreatePost {
   pub body: Option<String>,
   pub honeypot: Option<String>,
   pub nsfw: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PostResponse {
   pub post_view: PostView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPost {
   pub id: PostId,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetPostResponse {
   pub post_view: PostView,
   pub community_view: CommunityView,
@@ -52,7 +52,7 @@ pub struct GetPosts {
   pub community_id: Option<CommunityId>,
   pub community_name: Option<String>,
   pub saved_only: Option<bool>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -60,86 +60,86 @@ pub struct GetPostsResponse {
   pub posts: Vec<PostView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreatePostLike {
   pub post_id: PostId,
   pub score: i16,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct EditPost {
   pub post_id: PostId,
   pub name: Option<String>,
   pub url: Option<Url>,
   pub body: Option<String>,
   pub nsfw: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct DeletePost {
   pub post_id: PostId,
   pub deleted: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct RemovePost {
   pub post_id: PostId,
   pub removed: bool,
   pub reason: Option<String>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MarkPostAsRead {
   pub post_id: PostId,
   pub read: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct LockPost {
   pub post_id: PostId,
   pub locked: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct StickyPost {
   pub post_id: PostId,
   pub stickied: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct SavePost {
   pub post_id: PostId,
   pub save: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreatePostReport {
   pub post_id: PostId,
   pub reason: String,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PostReportResponse {
   pub post_report_view: PostReportView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ResolvePostReport {
   pub report_id: PostReportId,
   pub resolved: bool,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ListPostReports {
   pub page: Option<i64>,
   pub limit: Option<i64>,
@@ -147,10 +147,10 @@ pub struct ListPostReports {
   pub unresolved_only: Option<bool>,
   /// if no community is given, it returns reports for all communities moderated by the auth user
   pub community_id: Option<CommunityId>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct ListPostReportsResponse {
   pub post_reports: Vec<PostReportView>,
 }
diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs
index db81fce4..ff53cb51 100644
--- a/crates/api_common/src/site.rs
+++ b/crates/api_common/src/site.rs
@@ -25,6 +25,7 @@ use lemmy_db_views_moderator::{
   mod_sticky_post_view::ModStickyPostView,
   mod_transfer_community_view::ModTransferCommunityView,
 };
+use lemmy_utils::Sensitive;
 use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -38,7 +39,7 @@ pub struct Search {
   pub listing_type: Option<String>,
   pub page: Option<i64>,
   pub limit: Option<i64>,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
 #[derive(Serialize, Deserialize, Debug)]
@@ -53,10 +54,10 @@ pub struct SearchResponse {
 #[derive(Serialize, Deserialize, Debug)]
 pub struct ResolveObject {
   pub q: String,
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize, Default)]
+#[derive(Debug, Serialize, Deserialize, Default)]
 pub struct ResolveObjectResponse {
   pub comment: Option<CommentView>,
   pub post: Option<PostView>,
@@ -64,7 +65,7 @@ pub struct ResolveObjectResponse {
   pub person: Option<PersonViewSafe>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetModlog {
   pub mod_person_id: Option<PersonId>,
   pub community_id: Option<CommunityId>,
@@ -72,7 +73,7 @@ pub struct GetModlog {
   pub limit: Option<i64>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetModlogResponse {
   pub removed_posts: Vec<ModRemovePostView>,
   pub locked_posts: Vec<ModLockPostView>,
@@ -86,7 +87,7 @@ pub struct GetModlogResponse {
   pub added: Vec<ModAddView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct CreateSite {
   pub name: String,
   pub sidebar: Option<String>,
@@ -97,10 +98,10 @@ pub struct CreateSite {
   pub open_registration: Option<bool>,
   pub enable_nsfw: Option<bool>,
   pub community_creation_admin_only: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct EditSite {
   pub name: Option<String>,
   pub sidebar: Option<String>,
@@ -111,20 +112,20 @@ pub struct EditSite {
   pub open_registration: Option<bool>,
   pub enable_nsfw: Option<bool>,
   pub community_creation_admin_only: Option<bool>,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetSite {
-  pub auth: Option<String>,
+  pub auth: Option<Sensitive<String>>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct SiteResponse {
   pub site_view: SiteView,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetSiteResponse {
   pub site_view: Option<SiteView>, // Because the site might not be set up yet
   pub admins: Vec<PersonViewSafe>,
@@ -135,7 +136,7 @@ pub struct GetSiteResponse {
   pub federated_instances: Option<FederatedInstances>, // Federation may be disabled
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MyUserInfo {
   pub local_user_view: LocalUserSettingsView,
   pub follows: Vec<CommunityFollowerView>,
@@ -144,29 +145,29 @@ pub struct MyUserInfo {
   pub person_blocks: Vec<PersonBlockView>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct TransferSite {
   pub person_id: PersonId,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetSiteConfig {
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct GetSiteConfigResponse {
   pub config_hjson: String,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct SaveSiteConfig {
   pub config_hjson: String,
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct FederatedInstances {
   pub linked: Vec<String>,
   pub allowed: Option<Vec<String>>,
diff --git a/crates/api_common/src/websocket.rs b/crates/api_common/src/websocket.rs
index 5ab94d92..06665512 100644
--- a/crates/api_common/src/websocket.rs
+++ b/crates/api_common/src/websocket.rs
@@ -1,12 +1,13 @@
 use lemmy_db_schema::newtypes::{CommunityId, PostId};
+use lemmy_utils::Sensitive;
 use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize, Debug)]
 pub struct UserJoin {
-  pub auth: String,
+  pub auth: Sensitive<String>,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct UserJoinResponse {
   pub joined: bool,
 }
@@ -16,7 +17,7 @@ pub struct CommunityJoin {
   pub community_id: CommunityId,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct CommunityJoinResponse {
   pub joined: bool,
 }
@@ -26,7 +27,7 @@ pub struct ModJoin {
   pub community_id: CommunityId,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct ModJoinResponse {
   pub joined: bool,
 }
@@ -36,7 +37,7 @@ pub struct PostJoin {
   pub post_id: PostId,
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
 pub struct PostJoinResponse {
   pub joined: bool,
 }
diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs
index 025e12c8..1b0289b4 100644
--- a/crates/api_crud/src/comment/create.rs
+++ b/crates/api_crud/src/comment/create.rs
@@ -30,7 +30,6 @@ use lemmy_db_schema::{
 use lemmy_db_views::comment_view::CommentView;
 use lemmy_utils::{
   utils::{remove_slurs, scrape_text_for_mentions},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -44,6 +43,7 @@ use lemmy_websocket::{
 impl PerformCrud for CreateComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -67,7 +67,7 @@ impl PerformCrud for CreateComment {
 
     // Check if post is locked, no new comments
     if post.locked {
-      return Err(ApiError::err_plain("locked").into());
+      return Err(LemmyError::from_message("locked"));
     }
 
     // If there's a parent_id, check to make sure that comment is in that post
@@ -75,11 +75,12 @@ impl PerformCrud for CreateComment {
       // Make sure the parent comment exists
       let parent = blocking(context.pool(), move |conn| Comment::read(conn, parent_id))
         .await?
-        .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_create_comment"))?;
 
       // Strange issue where sometimes the post ID is incorrect
       if parent.post_id != post_id {
-        return Err(ApiError::err_plain("couldnt_create_comment").into());
+        return Err(LemmyError::from_message("couldnt_create_comment"));
       }
     }
 
@@ -97,7 +98,8 @@ impl PerformCrud for CreateComment {
       Comment::create(conn, &comment_form2)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_create_comment"))?;
 
     // Necessary to update the ap_id
     let inserted_comment_id = inserted_comment.id;
@@ -113,7 +115,8 @@ impl PerformCrud for CreateComment {
         Ok(Comment::update_ap_id(conn, inserted_comment_id, apub_id)?)
       })
       .await?
-      .map_err(|e| ApiError::err("couldnt_create_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_create_comment"))?;
 
     // Scan the comment for user mentions, add those rows
     let post_id = post.id;
@@ -139,7 +142,8 @@ impl PerformCrud for CreateComment {
     let like = move |conn: &'_ _| CommentLike::like(conn, &like_form);
     blocking(context.pool(), like)
       .await?
-      .map_err(|e| ApiError::err("couldnt_like_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_like_comment"))?;
 
     let apub_comment: ApubComment = updated_comment.into();
     CreateOrUpdateComment::send(
@@ -174,7 +178,8 @@ impl PerformCrud for CreateComment {
         Comment::update_read(conn, comment_id, true)
       })
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_comment"))?;
     }
     // If its a reply, mark the parent as read
     if let Some(parent_id) = data.parent_id {
@@ -187,7 +192,8 @@ impl PerformCrud for CreateComment {
           Comment::update_read(conn, parent_id, true)
         })
         .await?
-        .map_err(|e| ApiError::err("couldnt_update_parent_comment", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_update_parent_comment"))?;
       }
       // If the parent has PersonMentions mark them as read too
       let person_id = local_user_view.person.id;
@@ -200,7 +206,8 @@ impl PerformCrud for CreateComment {
           PersonMention::update_read(conn, mention.id, true)
         })
         .await?
-        .map_err(|e| ApiError::err("couldnt_update_person_mentions", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("couldnt_update_person_mentions"))?;
       }
     }
 
diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs
index 7e920882..84be30fa 100644
--- a/crates/api_crud/src/comment/delete.rs
+++ b/crates/api_crud/src/comment/delete.rs
@@ -18,7 +18,7 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views::comment_view::CommentView;
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{
   send::{send_comment_ws_message, send_local_notifs},
   LemmyContext,
@@ -29,6 +29,7 @@ use lemmy_websocket::{
 impl PerformCrud for DeleteComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -46,7 +47,7 @@ impl PerformCrud for DeleteComment {
 
     // Dont delete it if its already been deleted.
     if orig_comment.comment.deleted == data.deleted {
-      return Err(ApiError::err_plain("couldnt_update_comment").into());
+      return Err(LemmyError::from_message("couldnt_update_comment"));
     }
 
     check_community_ban(
@@ -58,7 +59,7 @@ impl PerformCrud for DeleteComment {
 
     // Verify that only the creator can delete
     if local_user_view.person.id != orig_comment.creator.id {
-      return Err(ApiError::err_plain("no_comment_edit_allowed").into());
+      return Err(LemmyError::from_message("no_comment_edit_allowed"));
     }
 
     // Do the delete
@@ -67,7 +68,8 @@ impl PerformCrud for DeleteComment {
       Comment::update_deleted(conn, comment_id, deleted)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     let post_id = updated_comment.post_id;
     let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
@@ -112,6 +114,7 @@ impl PerformCrud for DeleteComment {
 impl PerformCrud for RemoveComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -148,7 +151,8 @@ impl PerformCrud for RemoveComment {
       Comment::update_removed(conn, comment_id, removed)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     // Mod tables
     let form = ModRemoveCommentForm {
diff --git a/crates/api_crud/src/comment/read.rs b/crates/api_crud/src/comment/read.rs
index 4789b84f..bd1f475f 100644
--- a/crates/api_crud/src/comment/read.rs
+++ b/crates/api_crud/src/comment/read.rs
@@ -13,13 +13,14 @@ use lemmy_db_schema::{
   SortType,
 };
 use lemmy_db_views::comment_view::{CommentQueryBuilder, CommentView};
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for GetComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -27,7 +28,8 @@ impl PerformCrud for GetComment {
   ) -> Result<Self::Response, LemmyError> {
     let data = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let person_id = local_user_view.map(|u| u.person.id);
     let id = data.id;
@@ -35,7 +37,8 @@ impl PerformCrud for GetComment {
       CommentView::read(conn, id, person_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_comment", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_comment"))?;
 
     Ok(Self::Response {
       comment_view,
@@ -49,6 +52,7 @@ impl PerformCrud for GetComment {
 impl PerformCrud for GetComments {
   type Response = GetCommentsResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -56,7 +60,8 @@ impl PerformCrud for GetComments {
   ) -> Result<GetCommentsResponse, LemmyError> {
     let data: &GetComments = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let show_bot_accounts = local_user_view
       .as_ref()
@@ -91,7 +96,8 @@ impl PerformCrud for GetComments {
         .list()
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_get_comments", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_get_comments"))?;
 
     // Blank out deleted or removed info
     for cv in comments
diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs
index 7b293792..334181f2 100644
--- a/crates/api_crud/src/comment/update.rs
+++ b/crates/api_crud/src/comment/update.rs
@@ -16,7 +16,6 @@ use lemmy_db_schema::source::comment::Comment;
 use lemmy_db_views::comment_view::CommentView;
 use lemmy_utils::{
   utils::{remove_slurs, scrape_text_for_mentions},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -32,6 +31,7 @@ use crate::PerformCrud;
 impl PerformCrud for EditComment {
   type Response = CommentResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -59,7 +59,7 @@ impl PerformCrud for EditComment {
 
     // Verify that only the creator can edit
     if local_user_view.person.id != orig_comment.creator.id {
-      return Err(ApiError::err_plain("no_comment_edit_allowed").into());
+      return Err(LemmyError::from_message("no_comment_edit_allowed"));
     }
 
     // Do the update
@@ -70,7 +70,8 @@ impl PerformCrud for EditComment {
       Comment::update_content(conn, comment_id, &content_slurs_removed)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     // Do the mentions / recipients
     let updated_comment_content = updated_comment.content.to_owned();
diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs
index 19c6fc6b..fb090b86 100644
--- a/crates/api_crud/src/community/create.rs
+++ b/crates/api_crud/src/community/create.rs
@@ -34,7 +34,6 @@ use lemmy_db_views_actor::community_view::CommunityView;
 use lemmy_utils::{
   apub::generate_actor_keypair,
   utils::{check_slurs, check_slurs_opt, is_valid_actor_name},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -44,6 +43,7 @@ use lemmy_websocket::LemmyContext;
 impl PerformCrud for CreateCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -55,7 +55,9 @@ impl PerformCrud for CreateCommunity {
 
     let site = blocking(context.pool(), move |conn| Site::read(conn, 0)).await??;
     if site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
-      return Err(ApiError::err_plain("only_admins_can_create_communities").into());
+      return Err(LemmyError::from_message(
+        "only_admins_can_create_communities",
+      ));
     }
 
     check_slurs(&data.name, &context.settings().slur_regex())?;
@@ -63,7 +65,7 @@ impl PerformCrud for CreateCommunity {
     check_slurs_opt(&data.description, &context.settings().slur_regex())?;
 
     if !is_valid_actor_name(&data.name, context.settings().actor_name_max_length) {
-      return Err(ApiError::err_plain("invalid_community_name").into());
+      return Err(LemmyError::from_message("invalid_community_name"));
     }
 
     // Double check for duplicate community actor_ids
@@ -75,7 +77,7 @@ impl PerformCrud for CreateCommunity {
     let community_actor_id_wrapped = ObjectId::<ApubCommunity>::new(community_actor_id.clone());
     let community_dupe = community_actor_id_wrapped.dereference_local(context).await;
     if community_dupe.is_ok() {
-      return Err(ApiError::err_plain("community_already_exists").into());
+      return Err(LemmyError::from_message("community_already_exists"));
     }
 
     // Check to make sure the icon and banners are urls
@@ -105,7 +107,8 @@ impl PerformCrud for CreateCommunity {
       Community::create(conn, &community_form)
     })
     .await?
-    .map_err(|e| ApiError::err("community_already_exists", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("community_already_exists"))?;
 
     // The community creator becomes a moderator
     let community_moderator_form = CommunityModeratorForm {
@@ -115,7 +118,9 @@ impl PerformCrud for CreateCommunity {
 
     let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
     if blocking(context.pool(), join).await?.is_err() {
-      return Err(ApiError::err_plain("community_moderator_already_exists").into());
+      return Err(LemmyError::from_message(
+        "community_moderator_already_exists",
+      ));
     }
 
     // Follow your own community
@@ -127,7 +132,9 @@ impl PerformCrud for CreateCommunity {
 
     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
     if blocking(context.pool(), follow).await?.is_err() {
-      return Err(ApiError::err_plain("community_follower_already_exists").into());
+      return Err(LemmyError::from_message(
+        "community_follower_already_exists",
+      ));
     }
 
     let person_id = local_user_view.person.id;
diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs
index aec45192..5769bc53 100644
--- a/crates/api_crud/src/community/delete.rs
+++ b/crates/api_crud/src/community/delete.rs
@@ -10,13 +10,14 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
-use lemmy_utils::{utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{utils::naive_from_unix, ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeleteCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -35,7 +36,7 @@ impl PerformCrud for DeleteCommunity {
 
     // Make sure deleter is the top mod
     if local_user_view.person.id != community_mods[0].moderator.id {
-      return Err(ApiError::err_plain("no_community_edit_allowed").into());
+      return Err(LemmyError::from_message("no_community_edit_allowed"));
     }
 
     // Do the delete
@@ -45,7 +46,8 @@ impl PerformCrud for DeleteCommunity {
       Community::update_deleted(conn, community_id, deleted)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_community"))?;
 
     // Send apub messages
     send_apub_delete(
@@ -72,6 +74,7 @@ impl PerformCrud for DeleteCommunity {
 impl PerformCrud for RemoveCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -91,7 +94,8 @@ impl PerformCrud for RemoveCommunity {
       Community::update_removed(conn, community_id, removed)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_community"))?;
 
     // Mod tables
     let expires = data.expires.map(naive_from_unix);
diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs
index 54f27851..7a6d4354 100644
--- a/crates/api_crud/src/community/read.rs
+++ b/crates/api_crud/src/community/read.rs
@@ -17,13 +17,14 @@ use lemmy_db_views_actor::{
   community_moderator_view::CommunityModeratorView,
   community_view::{CommunityQueryBuilder, CommunityView},
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{messages::GetCommunityUsersOnline, LemmyContext};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for GetCommunity {
   type Response = GetCommunityResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -31,7 +32,8 @@ impl PerformCrud for GetCommunity {
   ) -> Result<GetCommunityResponse, LemmyError> {
     let data: &GetCommunity = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
     let person_id = local_user_view.map(|u| u.person.id);
 
     let community_id = match data.id {
@@ -45,7 +47,8 @@ impl PerformCrud for GetCommunity {
         ObjectId::<ApubCommunity>::new(community_actor_id)
           .dereference(context, &mut 0)
           .await
-          .map_err(|e| ApiError::err("couldnt_find_community", e))?
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("couldnt_find_community"))?
           .id
       }
     };
@@ -54,7 +57,8 @@ impl PerformCrud for GetCommunity {
       CommunityView::read(conn, community_id, person_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     // Blank out deleted or removed info for non-logged in users
     if person_id.is_none() && (community_view.community.deleted || community_view.community.removed)
@@ -66,7 +70,8 @@ impl PerformCrud for GetCommunity {
       CommunityModeratorView::for_community(conn, community_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     let online = context
       .chat_server()
@@ -89,6 +94,7 @@ impl PerformCrud for GetCommunity {
 impl PerformCrud for ListCommunities {
   type Response = ListCommunitiesResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -96,7 +102,8 @@ impl PerformCrud for ListCommunities {
   ) -> Result<ListCommunitiesResponse, LemmyError> {
     let data: &ListCommunities = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let person_id = local_user_view.to_owned().map(|l| l.person.id);
 
diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs
index a4f877d6..324b90eb 100644
--- a/crates/api_crud/src/community/update.rs
+++ b/crates/api_crud/src/community/update.rs
@@ -14,13 +14,14 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
-use lemmy_utils::{utils::check_slurs_opt, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{utils::check_slurs_opt, ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditCommunity {
   type Response = CommunityResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -41,7 +42,7 @@ impl PerformCrud for EditCommunity {
     })
     .await??;
     if !mods.contains(&local_user_view.person.id) {
-      return Err(ApiError::err_plain("not_a_moderator").into());
+      return Err(LemmyError::from_message("not_a_moderator"));
     }
 
     let community_id = data.community_id;
@@ -70,7 +71,8 @@ impl PerformCrud for EditCommunity {
       Community::update(conn, community_id, &community_form)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_community"))?;
 
     UpdateCommunity::send(
       updated_community.into(),
diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs
index a4a92f25..bc2f29f7 100644
--- a/crates/api_crud/src/post/create.rs
+++ b/crates/api_crud/src/post/create.rs
@@ -27,7 +27,6 @@ use lemmy_db_schema::{
 use lemmy_utils::{
   request::fetch_site_data,
   utils::{check_slurs, check_slurs_opt, clean_url_params, is_valid_post_title},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -40,6 +39,7 @@ use webmention::{Webmention, WebmentionError};
 impl PerformCrud for CreatePost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -55,7 +55,7 @@ impl PerformCrud for CreatePost {
     honeypot_check(&data.honeypot)?;
 
     if !is_valid_post_title(&data.name) {
-      return Err(ApiError::err_plain("invalid_post_title").into());
+      return Err(LemmyError::from_message("invalid_post_title"));
     }
 
     check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
@@ -93,7 +93,7 @@ impl PerformCrud for CreatePost {
             "couldnt_create_post"
           };
 
-          return Err(ApiError::err(err_type, e).into());
+          return Err(LemmyError::from(e).with_message(err_type));
         }
       };
 
@@ -108,7 +108,8 @@ impl PerformCrud for CreatePost {
       Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_create_post", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_create_post"))?;
 
     // They like their own post by default
     let person_id = local_user_view.person.id;
@@ -121,7 +122,7 @@ impl PerformCrud for CreatePost {
 
     let like = move |conn: &'_ _| PostLike::like(conn, &like_form);
     if blocking(context.pool(), like).await?.is_err() {
-      return Err(ApiError::err_plain("couldnt_like_post").into());
+      return Err(LemmyError::from_message("couldnt_like_post"));
     }
 
     // Mark the post as read
diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs
index a974feb1..0777153b 100644
--- a/crates/api_crud/src/post/delete.rs
+++ b/crates/api_crud/src/post/delete.rs
@@ -17,13 +17,14 @@ use lemmy_db_schema::{
   },
   traits::Crud,
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeletePost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -38,7 +39,7 @@ impl PerformCrud for DeletePost {
 
     // Dont delete it if its already been deleted.
     if orig_post.deleted == data.deleted {
-      return Err(ApiError::err_plain("couldnt_update_post").into());
+      return Err(LemmyError::from_message("couldnt_update_post"));
     }
 
     check_community_ban(
@@ -51,7 +52,7 @@ impl PerformCrud for DeletePost {
 
     // Verify that only the creator can delete
     if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
-      return Err(ApiError::err_plain("no_post_edit_allowed").into());
+      return Err(LemmyError::from_message("no_post_edit_allowed"));
     }
 
     // Update the post
@@ -91,6 +92,7 @@ impl PerformCrud for DeletePost {
 impl PerformCrud for RemovePost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs
index 720a18a4..42e84254 100644
--- a/crates/api_crud/src/post/read.rs
+++ b/crates/api_crud/src/post/read.rs
@@ -20,13 +20,14 @@ use lemmy_db_views_actor::{
   community_moderator_view::CommunityModeratorView,
   community_view::CommunityView,
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{messages::GetPostUsersOnline, LemmyContext};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for GetPost {
   type Response = GetPostResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -34,7 +35,8 @@ impl PerformCrud for GetPost {
   ) -> Result<GetPostResponse, LemmyError> {
     let data: &GetPost = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let show_bot_accounts = local_user_view
       .as_ref()
@@ -46,7 +48,8 @@ impl PerformCrud for GetPost {
       PostView::read(conn, id, person_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_post", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_post"))?;
 
     // Mark the post as read
     if let Some(person_id) = person_id {
@@ -70,7 +73,8 @@ impl PerformCrud for GetPost {
       CommunityView::read(conn, community_id, person_id)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_find_community", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_find_community"))?;
 
     // Blank out deleted or removed info for non-logged in users
     if person_id.is_none() {
@@ -115,6 +119,7 @@ impl PerformCrud for GetPost {
 impl PerformCrud for GetPosts {
   type Response = GetPostsResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -122,7 +127,8 @@ impl PerformCrud for GetPosts {
   ) -> Result<GetPostsResponse, LemmyError> {
     let data: &GetPosts = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let person_id = local_user_view.to_owned().map(|l| l.person.id);
 
@@ -165,7 +171,8 @@ impl PerformCrud for GetPosts {
         .list()
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_get_posts", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_get_posts"))?;
 
     // Blank out deleted or removed info for non-logged in users
     if person_id.is_none() {
diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs
index 2cfc67d4..97dab1cb 100644
--- a/crates/api_crud/src/post/update.rs
+++ b/crates/api_crud/src/post/update.rs
@@ -19,7 +19,6 @@ use lemmy_db_schema::{
 use lemmy_utils::{
   request::fetch_site_data,
   utils::{check_slurs_opt, clean_url_params, is_valid_post_title},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -31,6 +30,7 @@ use crate::PerformCrud;
 impl PerformCrud for EditPost {
   type Response = PostResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -46,7 +46,7 @@ impl PerformCrud for EditPost {
 
     if let Some(name) = &data.name {
       if !is_valid_post_title(name) {
-        return Err(ApiError::err_plain("invalid_post_title").into());
+        return Err(LemmyError::from_message("invalid_post_title"));
       }
     }
 
@@ -63,7 +63,7 @@ impl PerformCrud for EditPost {
 
     // Verify that only the creator can edit
     if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
-      return Err(ApiError::err_plain("no_post_edit_allowed").into());
+      return Err(LemmyError::from_message("no_post_edit_allowed"));
     }
 
     // Fetch post links and Pictrs cached image
@@ -103,7 +103,7 @@ impl PerformCrud for EditPost {
           "couldnt_update_post"
         };
 
-        return Err(ApiError::err(err_type, e).into());
+        return Err(LemmyError::from(e).with_message(err_type));
       }
     };
 
diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs
index 0c637826..ae75d9ea 100644
--- a/crates/api_crud/src/private_message/create.rs
+++ b/crates/api_crud/src/private_message/create.rs
@@ -19,7 +19,7 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views::local_user_view::LocalUserView;
-use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{utils::remove_slurs, ConnectionId, LemmyError};
 use lemmy_websocket::{
   send::{send_email_to_user, send_pm_ws_message},
   LemmyContext,
@@ -30,6 +30,7 @@ use lemmy_websocket::{
 impl PerformCrud for CreatePrivateMessage {
   type Response = PrivateMessageResponse;
 
+  #[tracing::instrument(skip(self, context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -58,7 +59,7 @@ impl PerformCrud for CreatePrivateMessage {
     {
       Ok(private_message) => private_message,
       Err(e) => {
-        return Err(ApiError::err("couldnt_create_private_message", e).into());
+        return Err(LemmyError::from(e).with_message("couldnt_create_private_message"));
       }
     };
 
@@ -80,7 +81,8 @@ impl PerformCrud for CreatePrivateMessage {
       },
     )
     .await?
-    .map_err(|e| ApiError::err("couldnt_create_private_message", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_create_private_message"))?;
 
     CreateOrUpdatePrivateMessage::send(
       updated_private_message.into(),
diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs
index 06bc22ed..e28712cf 100644
--- a/crates/api_crud/src/private_message/delete.rs
+++ b/crates/api_crud/src/private_message/delete.rs
@@ -13,13 +13,14 @@ use lemmy_db_schema::{
   source::private_message::PrivateMessage,
   traits::{Crud, DeleteableOrRemoveable},
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeletePrivateMessage {
   type Response = PrivateMessageResponse;
 
+  #[tracing::instrument(skip(self, context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -36,7 +37,7 @@ impl PerformCrud for DeletePrivateMessage {
     })
     .await??;
     if local_user_view.person.id != orig_private_message.creator_id {
-      return Err(ApiError::err_plain("no_private_message_edit_allowed").into());
+      return Err(LemmyError::from_message("no_private_message_edit_allowed"));
     }
 
     // Doing the update
@@ -46,7 +47,8 @@ impl PerformCrud for DeletePrivateMessage {
       PrivateMessage::update_deleted(conn, private_message_id, deleted)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_private_message", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_private_message"))?;
 
     // Send the apub update
     if data.deleted {
diff --git a/crates/api_crud/src/private_message/read.rs b/crates/api_crud/src/private_message/read.rs
index ae2dc203..48ff239c 100644
--- a/crates/api_crud/src/private_message/read.rs
+++ b/crates/api_crud/src/private_message/read.rs
@@ -14,6 +14,7 @@ use lemmy_websocket::LemmyContext;
 impl PerformCrud for GetPrivateMessages {
   type Response = PrivateMessagesResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -21,7 +22,7 @@ impl PerformCrud for GetPrivateMessages {
   ) -> Result<PrivateMessagesResponse, LemmyError> {
     let data: &GetPrivateMessages = self;
     let local_user_view =
-      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
     let person_id = local_user_view.person.id;
 
     let page = data.page;
diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs
index 95cc5cb3..83e75687 100644
--- a/crates/api_crud/src/private_message/update.rs
+++ b/crates/api_crud/src/private_message/update.rs
@@ -10,13 +10,14 @@ use lemmy_apub::protocol::activities::{
   CreateOrUpdateType,
 };
 use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
-use lemmy_utils::{utils::remove_slurs, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{utils::remove_slurs, ConnectionId, LemmyError};
 use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditPrivateMessage {
   type Response = PrivateMessageResponse;
 
+  #[tracing::instrument(skip(self, context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -33,7 +34,7 @@ impl PerformCrud for EditPrivateMessage {
     })
     .await??;
     if local_user_view.person.id != orig_private_message.creator_id {
-      return Err(ApiError::err_plain("no_private_message_edit_allowed").into());
+      return Err(LemmyError::from_message("no_private_message_edit_allowed"));
     }
 
     // Doing the update
@@ -43,7 +44,8 @@ impl PerformCrud for EditPrivateMessage {
       PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
     })
     .await?
-    .map_err(|e| ApiError::err("couldnt_update_private_message", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("couldnt_update_private_message"))?;
 
     // Send the apub update
     CreateOrUpdatePrivateMessage::send(
diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs
index 865ade77..f638eb7f 100644
--- a/crates/api_crud/src/site/create.rs
+++ b/crates/api_crud/src/site/create.rs
@@ -16,7 +16,6 @@ use lemmy_db_schema::{
 use lemmy_db_views::site_view::SiteView;
 use lemmy_utils::{
   utils::{check_slurs, check_slurs_opt},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -26,6 +25,7 @@ use lemmy_websocket::LemmyContext;
 impl PerformCrud for CreateSite {
   type Response = SiteResponse;
 
+  #[tracing::instrument(skip(context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -35,7 +35,7 @@ impl PerformCrud for CreateSite {
 
     let read_site = Site::read_simple;
     if blocking(context.pool(), read_site).await?.is_ok() {
-      return Err(ApiError::err_plain("site_already_exists").into());
+      return Err(LemmyError::from_message("site_already_exists"));
     };
 
     let local_user_view =
@@ -72,7 +72,7 @@ impl PerformCrud for CreateSite {
 
     let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
     if blocking(context.pool(), create_site).await?.is_err() {
-      return Err(ApiError::err_plain("site_already_exists").into());
+      return Err(LemmyError::from_message("site_already_exists"));
     }
 
     let site_view = blocking(context.pool(), SiteView::read).await??;
diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs
index 837acc83..4410d66a 100644
--- a/crates/api_crud/src/site/read.rs
+++ b/crates/api_crud/src/site/read.rs
@@ -15,7 +15,7 @@ use lemmy_db_views_actor::{
   person_block_view::PersonBlockView,
   person_view::PersonViewSafe,
 };
-use lemmy_utils::{version, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{version, ConnectionId, LemmyError};
 use lemmy_websocket::{messages::GetUsersOnline, LemmyContext};
 use tracing::info;
 
@@ -23,6 +23,7 @@ use tracing::info;
 impl PerformCrud for GetSite {
   type Response = GetSiteResponse;
 
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -37,9 +38,9 @@ impl PerformCrud for GetSite {
         if let Some(setup) = context.settings().setup.as_ref() {
           let register = Register {
             username: setup.admin_username.to_owned(),
-            email: setup.admin_email.to_owned(),
-            password: setup.admin_password.to_owned(),
-            password_verify: setup.admin_password.to_owned(),
+            email: setup.admin_email.clone().map(|s| s.into()),
+            password: setup.admin_password.clone().into(),
+            password_verify: setup.admin_password.clone().into(),
             show_nsfw: true,
             captcha_uuid: None,
             captcha_answer: None,
@@ -91,36 +92,43 @@ impl PerformCrud for GetSite {
       .unwrap_or(1);
 
     // Build the local user
-    let my_user = if let Some(local_user_view) =
-      get_local_user_settings_view_from_jwt_opt(&data.auth, context.pool(), context.secret())
-        .await?
+    let my_user = if let Some(local_user_view) = get_local_user_settings_view_from_jwt_opt(
+      data.auth.as_ref(),
+      context.pool(),
+      context.secret(),
+    )
+    .await?
     {
       let person_id = local_user_view.person.id;
       let follows = blocking(context.pool(), move |conn| {
         CommunityFollowerView::for_person(conn, person_id)
       })
       .await?
-      .map_err(|e| ApiError::err("system_err_login", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("system_err_login"))?;
 
       let person_id = local_user_view.person.id;
       let community_blocks = blocking(context.pool(), move |conn| {
         CommunityBlockView::for_person(conn, person_id)
       })
       .await?
-      .map_err(|e| ApiError::err("system_err_login", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("system_err_login"))?;
 
       let person_id = local_user_view.person.id;
       let person_blocks = blocking(context.pool(), move |conn| {
         PersonBlockView::for_person(conn, person_id)
       })
       .await?
-      .map_err(|e| ApiError::err("system_err_login", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("system_err_login"))?;
 
       let moderates = blocking(context.pool(), move |conn| {
         CommunityModeratorView::for_person(conn, person_id)
       })
       .await?
-      .map_err(|e| ApiError::err("system_err_login", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("system_err_login"))?;
 
       Some(MyUserInfo {
         local_user_view,
diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs
index b6f06e3a..21dc50a2 100644
--- a/crates/api_crud/src/site/update.rs
+++ b/crates/api_crud/src/site/update.rs
@@ -15,12 +15,14 @@ use lemmy_db_schema::{
   traits::Crud,
 };
 use lemmy_db_views::site_view::SiteView;
-use lemmy_utils::{utils::check_slurs_opt, ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{utils::check_slurs_opt, ConnectionId, LemmyError};
 use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperationCrud};
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for EditSite {
   type Response = SiteResponse;
+
+  #[tracing::instrument(skip(context, websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -64,7 +66,8 @@ impl PerformCrud for EditSite {
     let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
     blocking(context.pool(), update_site)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_site", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_site"))?;
 
     let site_view = blocking(context.pool(), SiteView::read).await??;
 
diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs
index 2ce2fa36..a9ea9861 100644
--- a/crates/api_crud/src/user/create.rs
+++ b/crates/api_crud/src/user/create.rs
@@ -32,7 +32,6 @@ use lemmy_utils::{
   apub::generate_actor_keypair,
   claims::Claims,
   utils::{check_slurs, is_valid_actor_name},
-  ApiError,
   ConnectionId,
   LemmyError,
 };
@@ -42,6 +41,7 @@ use lemmy_websocket::{messages::CheckCaptcha, LemmyContext};
 impl PerformCrud for Register {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -52,7 +52,7 @@ impl PerformCrud for Register {
     // Make sure site has open registration
     if let Ok(site) = blocking(context.pool(), Site::read_simple).await? {
       if !site.open_registration {
-        return Err(ApiError::err_plain("registration_closed").into());
+        return Err(LemmyError::from_message("registration_closed"));
       }
     }
 
@@ -61,7 +61,7 @@ impl PerformCrud for Register {
 
     // Make sure passwords match
     if data.password != data.password_verify {
-      return Err(ApiError::err_plain("passwords_dont_match").into());
+      return Err(LemmyError::from_message("passwords_dont_match"));
     }
 
     // Check if there are admins. False if admins exist
@@ -86,7 +86,7 @@ impl PerformCrud for Register {
         })
         .await?;
       if !check {
-        return Err(ApiError::err_plain("captcha_incorrect").into());
+        return Err(LemmyError::from_message("captcha_incorrect"));
       }
     }
 
@@ -94,7 +94,7 @@ impl PerformCrud for Register {
 
     let actor_keypair = generate_actor_keypair()?;
     if !is_valid_actor_name(&data.username, context.settings().actor_name_max_length) {
-      return Err(ApiError::err_plain("invalid_username").into());
+      return Err(LemmyError::from_message("invalid_username"));
     }
     let actor_id = generate_local_apub_endpoint(
       EndpointType::Person,
@@ -121,14 +121,15 @@ impl PerformCrud for Register {
       Person::create(conn, &person_form)
     })
     .await?
-    .map_err(|e| ApiError::err("user_already_exists", e))?;
+    .map_err(LemmyError::from)
+    .map_err(|e| e.with_message("user_already_exists"))?;
 
     // Create the local user
     // TODO some of these could probably use the DB defaults
     let local_user_form = LocalUserForm {
       person_id: inserted_person.id,
-      email: Some(data.email.to_owned()),
-      password_encrypted: data.password.to_owned(),
+      email: Some(data.email.as_deref().map(|s| s.to_owned())),
+      password_encrypted: data.password.to_string(),
       show_nsfw: Some(data.show_nsfw),
       show_bot_accounts: Some(true),
       theme: Some("browser".into()),
@@ -163,7 +164,7 @@ impl PerformCrud for Register {
         })
         .await??;
 
-        return Err(ApiError::err(err_type, e).into());
+        return Err(LemmyError::from(e).with_message(err_type));
       }
     };
 
@@ -213,7 +214,8 @@ impl PerformCrud for Register {
     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
     blocking(context.pool(), follow)
       .await?
-      .map_err(|e| ApiError::err("community_follower_already_exists", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("community_follower_already_exists"))?;
 
     // If its an admin, add them as a mod and follower to main
     if no_admins {
@@ -225,7 +227,8 @@ impl PerformCrud for Register {
       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
       blocking(context.pool(), join)
         .await?
-        .map_err(|e| ApiError::err("community_moderator_already_exists", e))?;
+        .map_err(LemmyError::from)
+        .map_err(|e| e.with_message("community_moderator_already_exists"))?;
     }
 
     // Return the jwt
@@ -234,7 +237,8 @@ impl PerformCrud for Register {
         inserted_local_user.id.0,
         &context.secret().jwt_secret,
         &context.settings().hostname,
-      )?,
+      )?
+      .into(),
     })
   }
 }
diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs
index 28f42831..0400ffe0 100644
--- a/crates/api_crud/src/user/delete.rs
+++ b/crates/api_crud/src/user/delete.rs
@@ -3,13 +3,14 @@ use actix_web::web::Data;
 use bcrypt::verify;
 use lemmy_api_common::{blocking, get_local_user_view_from_jwt, person::*};
 use lemmy_db_schema::source::{comment::Comment, person::Person, post::Post};
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for DeleteAccount {
   type Response = LoginResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -17,7 +18,7 @@ impl PerformCrud for DeleteAccount {
   ) -> Result<LoginResponse, LemmyError> {
     let data: &DeleteAccount = self;
     let local_user_view =
-      get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
 
     // Verify the password
     let valid: bool = verify(
@@ -26,7 +27,7 @@ impl PerformCrud for DeleteAccount {
     )
     .unwrap_or(false);
     if !valid {
-      return Err(ApiError::err_plain("password_incorrect").into());
+      return Err(LemmyError::from_message("password_incorrect"));
     }
 
     // Comments
@@ -34,13 +35,15 @@ impl PerformCrud for DeleteAccount {
     let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
     blocking(context.pool(), permadelete)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_comment", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_comment"))?;
 
     // Posts
     let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
     blocking(context.pool(), permadelete)
       .await?
-      .map_err(|e| ApiError::err("couldnt_update_post", e))?;
+      .map_err(LemmyError::from)
+      .map_err(|e| e.with_message("couldnt_update_post"))?;
 
     blocking(context.pool(), move |conn| {
       Person::delete_account(conn, person_id)
@@ -48,7 +51,7 @@ impl PerformCrud for DeleteAccount {
     .await??;
 
     Ok(LoginResponse {
-      jwt: data.auth.to_owned(),
+      jwt: data.auth.clone(),
     })
   }
 }
diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs
index e2084860..b63d95c8 100644
--- a/crates/api_crud/src/user/read.rs
+++ b/crates/api_crud/src/user/read.rs
@@ -13,13 +13,14 @@ use lemmy_db_views_actor::{
   community_moderator_view::CommunityModeratorView,
   person_view::PersonViewSafe,
 };
-use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_utils::{ConnectionId, LemmyError};
 use lemmy_websocket::LemmyContext;
 
 #[async_trait::async_trait(?Send)]
 impl PerformCrud for GetPersonDetails {
   type Response = GetPersonDetailsResponse;
 
+  #[tracing::instrument(skip(self, context, _websocket_id))]
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
@@ -27,7 +28,8 @@ impl PerformCrud for GetPersonDetails {
   ) -> Result<GetPersonDetailsResponse, LemmyError> {
     let data: &GetPersonDetails = self;
     let local_user_view =
-      get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
+      get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
+        .await?;
 
     let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
     let show_bot_accounts = local_user_view
@@ -53,7 +55,8 @@ impl PerformCrud for GetPersonDetails {
           .dereference(context, &mut 0)
           .await;
         person
-          .map_err(|e| ApiError::err("couldnt_find_that_username_or_email", e))?
+          .map_err(LemmyError::from)
+          .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?
           .id
       }
     };
diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs
index 649f566c..3b1ee393 100644
--- a/crates/apub/src/activities/comment/create_or_update.rs
+++ b/crates/apub/src/activities/comment/create_or_update.rs
@@ -28,6 +28,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
 
 impl CreateOrUpdateComment {
+  #[tracing::instrument(skip(comment, actor, kind, context))]
   pub async fn send(
     comment: ApubComment,
     actor: &ApubPerson,
@@ -83,6 +84,7 @@ impl CreateOrUpdateComment {
 impl ActivityHandler for CreateOrUpdateComment {
   type DataType = LemmyContext;
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -102,6 +104,7 @@ impl ActivityHandler for CreateOrUpdateComment {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -131,6 +134,7 @@ impl ActivityHandler for CreateOrUpdateComment {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for CreateOrUpdateComment {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/comment/mod.rs b/crates/apub/src/activities/comment/mod.rs
index 17b4f50c..29c24952 100644
--- a/crates/apub/src/activities/comment/mod.rs
+++ b/crates/apub/src/activities/comment/mod.rs
@@ -11,6 +11,7 @@ use lemmy_websocket::{send::send_local_notifs, LemmyContext};
 
 pub mod create_or_update;
 
+#[tracing::instrument(skip_all)]
 async fn get_notif_recipients(
   actor: &ObjectId<ApubPerson>,
   comment: &Comment,
diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs
index c244f652..3daedaa2 100644
--- a/crates/apub/src/activities/community/add_mod.rs
+++ b/crates/apub/src/activities/community/add_mod.rs
@@ -32,6 +32,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl AddMod {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     community: &ApubCommunity,
     added_mod: &ApubPerson,
@@ -63,6 +64,7 @@ impl AddMod {
 impl ActivityHandler for AddMod {
   type DataType = LemmyContext;
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -77,6 +79,7 @@ impl ActivityHandler for AddMod {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -109,6 +112,7 @@ impl ActivityHandler for AddMod {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for AddMod {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs
index cc30a89f..0ece90b7 100644
--- a/crates/apub/src/activities/community/announce.rs
+++ b/crates/apub/src/activities/community/announce.rs
@@ -44,6 +44,7 @@ impl AnnounceActivity {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     object: AnnouncableActivities,
     community: &ApubCommunity,
@@ -85,6 +86,8 @@ impl AnnounceActivity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for AnnounceActivity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -96,6 +99,7 @@ impl ActivityHandler for AnnounceActivity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs
index 0442fcc4..140b9a86 100644
--- a/crates/apub/src/activities/community/block_user.rs
+++ b/crates/apub/src/activities/community/block_user.rs
@@ -52,6 +52,7 @@ impl BlockUserFromCommunity {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     community: &ApubCommunity,
     target: &ApubPerson,
@@ -70,6 +71,8 @@ impl BlockUserFromCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for BlockUserFromCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -83,6 +86,7 @@ impl ActivityHandler for BlockUserFromCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -119,6 +123,7 @@ impl ActivityHandler for BlockUserFromCommunity {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for BlockUserFromCommunity {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs
index 23f8f8e3..5961fa04 100644
--- a/crates/apub/src/activities/community/mod.rs
+++ b/crates/apub/src/activities/community/mod.rs
@@ -17,6 +17,7 @@ pub mod report;
 pub mod undo_block_user;
 pub mod update;
 
+#[tracing::instrument(skip_all)]
 pub(crate) async fn send_activity_in_community<T: ActorType>(
   activity: AnnouncableActivities,
   activity_id: &Url,
@@ -35,6 +36,7 @@ pub(crate) async fn send_activity_in_community<T: ActorType>(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
 async fn get_community_from_moderators_url(
   moderators: &Url,
   context: &LemmyContext,
diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs
index 36312070..b6a565f1 100644
--- a/crates/apub/src/activities/community/remove_mod.rs
+++ b/crates/apub/src/activities/community/remove_mod.rs
@@ -32,6 +32,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl RemoveMod {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     community: &ApubCommunity,
     removed_mod: &ApubPerson,
@@ -62,6 +63,8 @@ impl RemoveMod {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for RemoveMod {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -76,6 +79,7 @@ impl ActivityHandler for RemoveMod {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -99,6 +103,7 @@ impl ActivityHandler for RemoveMod {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for RemoveMod {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/community/report.rs b/crates/apub/src/activities/community/report.rs
index 7207af61..35a42667 100644
--- a/crates/apub/src/activities/community/report.rs
+++ b/crates/apub/src/activities/community/report.rs
@@ -28,6 +28,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
 
 impl Report {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     object_id: ObjectId<PostOrComment>,
     actor: &ApubPerson,
@@ -65,6 +66,8 @@ impl Report {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for Report {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -76,6 +79,7 @@ impl ActivityHandler for Report {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs
index dbd6c5d5..2a7a2b15 100644
--- a/crates/apub/src/activities/community/undo_block_user.rs
+++ b/crates/apub/src/activities/community/undo_block_user.rs
@@ -29,6 +29,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl UndoBlockUserFromCommunity {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     community: &ApubCommunity,
     target: &ApubPerson,
@@ -60,6 +61,8 @@ impl UndoBlockUserFromCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UndoBlockUserFromCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -74,6 +77,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -102,6 +106,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for UndoBlockUserFromCommunity {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs
index 7b2af3c4..e1c39241 100644
--- a/crates/apub/src/activities/community/update.rs
+++ b/crates/apub/src/activities/community/update.rs
@@ -26,6 +26,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
 
 impl UpdateCommunity {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     community: ApubCommunity,
     actor: &ApubPerson,
@@ -53,6 +54,8 @@ impl UpdateCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UpdateCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -73,6 +76,7 @@ impl ActivityHandler for UpdateCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -110,6 +114,7 @@ impl ActivityHandler for UpdateCommunity {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for UpdateCommunity {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs
index e50cfd85..9f53a8bf 100644
--- a/crates/apub/src/activities/deletion/delete.rs
+++ b/crates/apub/src/activities/deletion/delete.rs
@@ -11,7 +11,6 @@ use crate::{
   protocol::activities::deletion::delete::Delete,
 };
 use activitystreams_kinds::{activity::DeleteType, public};
-use anyhow::anyhow;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
@@ -45,6 +44,8 @@ use url::Url;
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for Delete {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -65,6 +66,7 @@ impl ActivityHandler for Delete {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -112,6 +114,8 @@ impl Delete {
       unparsed: Default::default(),
     })
   }
+
+  #[tracing::instrument(skip_all)]
   pub(in crate::activities::deletion) async fn send(
     actor: &ApubPerson,
     community: &ApubCommunity,
@@ -127,6 +131,7 @@ impl Delete {
   }
 }
 
+#[tracing::instrument(skip_all)]
 pub(in crate::activities) async fn receive_remove_action(
   actor: &ObjectId<ApubPerson>,
   object: &Url,
@@ -139,7 +144,9 @@ pub(in crate::activities) async fn receive_remove_action(
   match DeletableObjects::read_from_db(object, context).await? {
     DeletableObjects::Community(community) => {
       if community.local {
-        return Err(anyhow!("Only local admin can remove community").into());
+        return Err(LemmyError::from_message(
+          "Only local admin can remove community",
+        ));
       }
       let form = ModRemoveCommunityForm {
         mod_person_id: actor.id,
@@ -201,6 +208,7 @@ pub(in crate::activities) async fn receive_remove_action(
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for Delete {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs
index ab2e7a0b..a9c72a3a 100644
--- a/crates/apub/src/activities/deletion/mod.rs
+++ b/crates/apub/src/activities/deletion/mod.rs
@@ -20,6 +20,7 @@ use url::Url;
 pub mod delete;
 pub mod undo_delete;
 
+#[tracing::instrument(skip_all)]
 pub async fn send_apub_delete(
   actor: &ApubPerson,
   community: &ApubCommunity,
@@ -36,6 +37,7 @@ pub async fn send_apub_delete(
 
 // TODO: remove reason is actually optional in lemmy. we set an empty string in that case, but its
 //       ugly
+#[tracing::instrument(skip_all)]
 pub async fn send_apub_remove(
   actor: &ApubPerson,
   community: &ApubCommunity,
@@ -58,6 +60,7 @@ pub enum DeletableObjects {
 }
 
 impl DeletableObjects {
+  #[tracing::instrument(skip_all)]
   pub(crate) async fn read_from_db(
     ap_id: &Url,
     context: &LemmyContext,
@@ -83,6 +86,7 @@ impl DeletableObjects {
   }
 }
 
+#[tracing::instrument(skip_all)]
 pub(in crate::activities) async fn verify_delete_activity(
   object: &Url,
   actor: &ObjectId<ApubPerson>,
@@ -128,6 +132,7 @@ pub(in crate::activities) async fn verify_delete_activity(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
 async fn verify_delete_activity_post_or_comment(
   actor: &ObjectId<ApubPerson>,
   object_id: &Url,
@@ -149,6 +154,7 @@ async fn verify_delete_activity_post_or_comment(
 /// Write deletion or restoring of an object to the database, and send websocket message.
 /// TODO: we should do something similar for receive_remove_action(), but its much more complicated
 ///       because of the mod log
+#[tracing::instrument(skip_all)]
 async fn receive_delete_action(
   object: &Url,
   actor: &ObjectId<ApubPerson>,
diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs
index 8a867fbc..167f8961 100644
--- a/crates/apub/src/activities/deletion/undo_delete.rs
+++ b/crates/apub/src/activities/deletion/undo_delete.rs
@@ -11,7 +11,6 @@ use crate::{
   protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
 };
 use activitystreams_kinds::{activity::UndoType, public};
-use anyhow::anyhow;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
@@ -30,6 +29,8 @@ use url::Url;
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UndoDelete {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -51,6 +52,7 @@ impl ActivityHandler for UndoDelete {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -72,6 +74,7 @@ impl ActivityHandler for UndoDelete {
 }
 
 impl UndoDelete {
+  #[tracing::instrument(skip_all)]
   pub(in crate::activities::deletion) async fn send(
     actor: &ApubPerson,
     community: &ApubCommunity,
@@ -99,6 +102,7 @@ impl UndoDelete {
     send_activity_in_community(activity, &id, actor, community, vec![], context).await
   }
 
+  #[tracing::instrument(skip_all)]
   pub(in crate::activities) async fn receive_undo_remove_action(
     object: &Url,
     context: &LemmyContext,
@@ -107,7 +111,9 @@ impl UndoDelete {
     match DeletableObjects::read_from_db(object, context).await? {
       DeletableObjects::Community(community) => {
         if community.local {
-          return Err(anyhow!("Only local admin can restore community").into());
+          return Err(LemmyError::from_message(
+            "Only local admin can restore community",
+          ));
         }
         let deleted_community = blocking(context.pool(), move |conn| {
           Community::update_removed(conn, community.id, false)
@@ -136,6 +142,7 @@ impl UndoDelete {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for UndoDelete {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs
index 6febd7d4..a45dcf3c 100644
--- a/crates/apub/src/activities/following/accept.rs
+++ b/crates/apub/src/activities/following/accept.rs
@@ -15,6 +15,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl AcceptFollowCommunity {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     follow: FollowCommunity,
     context: &LemmyContext,
@@ -45,6 +46,8 @@ impl AcceptFollowCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for AcceptFollowCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -56,6 +59,7 @@ impl ActivityHandler for AcceptFollowCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs
index a5c2e77f..877df61f 100644
--- a/crates/apub/src/activities/following/follow.rs
+++ b/crates/apub/src/activities/following/follow.rs
@@ -40,6 +40,8 @@ impl FollowCommunity {
       unparsed: Default::default(),
     })
   }
+
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     actor: &ApubPerson,
     community: &ApubCommunity,
@@ -64,6 +66,8 @@ impl FollowCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for FollowCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -76,6 +80,7 @@ impl ActivityHandler for FollowCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/following/undo_follow.rs b/crates/apub/src/activities/following/undo_follow.rs
index c7b926a2..8644cbc5 100644
--- a/crates/apub/src/activities/following/undo_follow.rs
+++ b/crates/apub/src/activities/following/undo_follow.rs
@@ -19,6 +19,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl UndoFollowCommunity {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     actor: &ApubPerson,
     community: &ApubCommunity,
@@ -43,6 +44,8 @@ impl UndoFollowCommunity {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UndoFollowCommunity {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -55,6 +58,7 @@ impl ActivityHandler for UndoFollowCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs
index 1a3d01bb..8a4c4ffe 100644
--- a/crates/apub/src/activities/mod.rs
+++ b/crates/apub/src/activities/mod.rs
@@ -6,7 +6,6 @@ use crate::{
   objects::{community::ApubCommunity, person::ApubPerson},
 };
 use activitystreams_kinds::public;
-use anyhow::anyhow;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   activity_queue::send_activity,
@@ -36,6 +35,7 @@ pub mod voting;
 
 /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
 /// doesn't have a site ban.
+#[tracing::instrument(skip_all)]
 async fn verify_person(
   person_id: &ObjectId<ApubPerson>,
   context: &LemmyContext,
@@ -43,13 +43,15 @@ async fn verify_person(
 ) -> Result<(), LemmyError> {
   let person = person_id.dereference(context, request_counter).await?;
   if person.banned {
-    return Err(anyhow!("Person {} is banned", person_id).into());
+    let error = LemmyError::from(anyhow::anyhow!("Person {} is banned", person_id));
+    return Err(error.with_message("banned"));
   }
   Ok(())
 }
 
 /// Fetches the person and community to verify their type, then checks if person is banned from site
 /// or community.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn verify_person_in_community(
   person_id: &ObjectId<ApubPerson>,
   community: &ApubCommunity,
@@ -58,14 +60,14 @@ pub(crate) async fn verify_person_in_community(
 ) -> Result<(), LemmyError> {
   let person = person_id.dereference(context, request_counter).await?;
   if person.banned {
-    return Err(anyhow!("Person is banned from site").into());
+    return Err(LemmyError::from_message("Person is banned from site"));
   }
   let person_id = person.id;
   let community_id = community.id;
   let is_banned =
     move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
   if blocking(context.pool(), is_banned).await? {
-    return Err(anyhow!("Person is banned from community").into());
+    return Err(LemmyError::from_message("Person is banned from community"));
   }
 
   Ok(())
@@ -80,6 +82,7 @@ fn verify_activity(id: &Url, actor: &Url, settings: &Settings) -> Result<(), Lem
 /// Verify that the actor is a community mod. This check is only run if the community is local,
 /// because in case of remote communities, admins can also perform mod actions. As admin status
 /// is not federated, we cant verify their actions remotely.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn verify_mod_action(
   actor_id: &ObjectId<ApubPerson>,
   community: &ApubCommunity,
@@ -98,7 +101,7 @@ pub(crate) async fn verify_mod_action(
     })
     .await?;
     if !is_mod_or_admin {
-      return Err(anyhow!("Not a mod").into());
+      return Err(LemmyError::from_message("Not a mod"));
     }
   }
   Ok(())
@@ -111,21 +114,23 @@ fn verify_add_remove_moderator_target(
   community: &ApubCommunity,
 ) -> Result<(), LemmyError> {
   if target != &generate_moderators_url(&community.actor_id)?.into() {
-    return Err(anyhow!("Unkown target url").into());
+    return Err(LemmyError::from_message("Unkown target url"));
   }
   Ok(())
 }
 
 pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> Result<(), LemmyError> {
   if ![to, cc].iter().any(|set| set.contains(&public())) {
-    return Err(anyhow!("Object is not public").into());
+    return Err(LemmyError::from_message("Object is not public"));
   }
   Ok(())
 }
 
 pub(crate) fn check_community_deleted_or_removed(community: &Community) -> Result<(), LemmyError> {
   if community.deleted || community.removed {
-    Err(anyhow!("New post or comment cannot be created in deleted or removed community").into())
+    Err(LemmyError::from_message(
+      "New post or comment cannot be created in deleted or removed community",
+    ))
   } else {
     Ok(())
   }
@@ -146,6 +151,7 @@ where
   Url::parse(&id)
 }
 
+#[tracing::instrument(skip_all)]
 async fn send_lemmy_activity<T: Serialize>(
   context: &LemmyContext,
   activity: &T,
diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs
index 2e9c8071..24263776 100644
--- a/crates/apub/src/activities/post/create_or_update.rs
+++ b/crates/apub/src/activities/post/create_or_update.rs
@@ -13,7 +13,6 @@ use crate::{
   protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
 };
 use activitystreams_kinds::public;
-use anyhow::anyhow;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{
   data::Data,
@@ -47,6 +46,8 @@ impl CreateOrUpdatePost {
       unparsed: Default::default(),
     })
   }
+
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     post: ApubPost,
     actor: &ApubPerson,
@@ -70,6 +71,8 @@ impl CreateOrUpdatePost {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for CreateOrUpdatePost {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -92,7 +95,9 @@ impl ActivityHandler for CreateOrUpdatePost {
         let is_stickied_or_locked =
           self.object.stickied == Some(true) || self.object.comments_enabled == Some(false);
         if community.local && is_stickied_or_locked {
-          return Err(anyhow!("New post cannot be stickied or locked").into());
+          return Err(LemmyError::from_message(
+            "New post cannot be stickied or locked",
+          ));
         }
       }
       CreateOrUpdateType::Update => {
@@ -109,6 +114,7 @@ impl ActivityHandler for CreateOrUpdatePost {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -127,6 +133,7 @@ impl ActivityHandler for CreateOrUpdatePost {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for CreateOrUpdatePost {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs
index e3d72f54..3374f224 100644
--- a/crates/apub/src/activities/private_message/create_or_update.rs
+++ b/crates/apub/src/activities/private_message/create_or_update.rs
@@ -18,6 +18,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
 
 impl CreateOrUpdatePrivateMessage {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     private_message: ApubPrivateMessage,
     actor: &ApubPerson,
@@ -46,9 +47,12 @@ impl CreateOrUpdatePrivateMessage {
     send_lemmy_activity(context, &create_or_update, &id, actor, inbox, true).await
   }
 }
+
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for CreateOrUpdatePrivateMessage {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -61,6 +65,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs
index 7269078a..af81db0e 100644
--- a/crates/apub/src/activities/private_message/delete.rs
+++ b/crates/apub/src/activities/private_message/delete.rs
@@ -36,6 +36,8 @@ impl DeletePrivateMessage {
       unparsed: Default::default(),
     })
   }
+
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     actor: &ApubPerson,
     pm: &ApubPrivateMessage,
@@ -57,6 +59,8 @@ impl DeletePrivateMessage {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for DeletePrivateMessage {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -68,6 +72,7 @@ impl ActivityHandler for DeletePrivateMessage {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs
index ce3cc49c..dbd84bd5 100644
--- a/crates/apub/src/activities/private_message/undo_delete.rs
+++ b/crates/apub/src/activities/private_message/undo_delete.rs
@@ -22,6 +22,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
 
 impl UndoDeletePrivateMessage {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     actor: &ApubPerson,
     pm: &ApubPrivateMessage,
@@ -54,6 +55,8 @@ impl UndoDeletePrivateMessage {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UndoDeletePrivateMessage {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -67,6 +70,7 @@ impl ActivityHandler for UndoDeletePrivateMessage {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs
index 0a2a8fd1..c82b4efd 100644
--- a/crates/apub/src/activities/voting/mod.rs
+++ b/crates/apub/src/activities/voting/mod.rs
@@ -21,6 +21,7 @@ use crate::{
 pub mod undo_vote;
 pub mod vote;
 
+#[tracing::instrument(skip_all)]
 async fn vote_comment(
   vote_type: &VoteType,
   actor: ApubPerson,
@@ -45,6 +46,7 @@ async fn vote_comment(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
 async fn vote_post(
   vote_type: &VoteType,
   actor: ApubPerson,
@@ -68,6 +70,7 @@ async fn vote_post(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
 async fn undo_vote_comment(
   actor: ApubPerson,
   comment: &ApubComment,
@@ -84,6 +87,7 @@ async fn undo_vote_comment(
   Ok(())
 }
 
+#[tracing::instrument(skip_all)]
 async fn undo_vote_post(
   actor: ApubPerson,
   post: &ApubPost,
diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs
index f913ce5f..3e840391 100644
--- a/crates/apub/src/activities/voting/undo_vote.rs
+++ b/crates/apub/src/activities/voting/undo_vote.rs
@@ -28,6 +28,7 @@ use lemmy_utils::LemmyError;
 use lemmy_websocket::LemmyContext;
 
 impl UndoVote {
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     object: &PostOrComment,
     actor: &ApubPerson,
@@ -63,6 +64,8 @@ impl UndoVote {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for UndoVote {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -77,6 +80,7 @@ impl ActivityHandler for UndoVote {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -97,6 +101,7 @@ impl ActivityHandler for UndoVote {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for UndoVote {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs
index 65a92800..283b0435 100644
--- a/crates/apub/src/activities/voting/vote.rs
+++ b/crates/apub/src/activities/voting/vote.rs
@@ -46,6 +46,7 @@ impl Vote {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   pub async fn send(
     object: &PostOrComment,
     actor: &ApubPerson,
@@ -69,6 +70,8 @@ impl Vote {
 #[async_trait::async_trait(?Send)]
 impl ActivityHandler for Vote {
   type DataType = LemmyContext;
+
+  #[tracing::instrument(skip_all)]
   async fn verify(
     &self,
     context: &Data<LemmyContext>,
@@ -81,6 +84,7 @@ impl ActivityHandler for Vote {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn receive(
     self,
     context: &Data<LemmyContext>,
@@ -97,6 +101,7 @@ impl ActivityHandler for Vote {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for Vote {
+  #[tracing::instrument(skip_all)]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs
index d9b97649..0512ed62 100644
--- a/crates/apub/src/activity_lists.rs
+++ b/crates/apub/src/activity_lists.rs
@@ -88,6 +88,7 @@ pub enum AnnouncableActivities {
 
 #[async_trait::async_trait(?Send)]
 impl GetCommunity for AnnouncableActivities {
+  #[tracing::instrument(skip(self, context))]
   async fn get_community(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs
index 9b8d26d9..1f761917 100644
--- a/crates/apub/src/collections/community_moderators.rs
+++ b/crates/apub/src/collections/community_moderators.rs
@@ -29,6 +29,7 @@ impl ApubObject for ApubCommunityModerators {
     None
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     _object_id: Url,
     data: &Self::DataType,
@@ -46,10 +47,12 @@ impl ApubObject for ApubCommunityModerators {
     }
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, _data: &Self::DataType) -> Result<(), LemmyError> {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
     let ordered_items = self
       .0
@@ -67,6 +70,7 @@ impl ApubObject for ApubCommunityModerators {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     group_moderators: &GroupModerators,
     expected_domain: &Url,
@@ -77,6 +81,7 @@ impl ApubObject for ApubCommunityModerators {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     apub: Self::ApubType,
     data: &Self::DataType,
diff --git a/crates/apub/src/collections/community_outbox.rs b/crates/apub/src/collections/community_outbox.rs
index de55ffa6..eaa9acd9 100644
--- a/crates/apub/src/collections/community_outbox.rs
+++ b/crates/apub/src/collections/community_outbox.rs
@@ -33,6 +33,7 @@ impl ApubObject for ApubCommunityOutbox {
     None
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     _object_id: Url,
     data: &Self::DataType,
@@ -58,6 +59,7 @@ impl ApubObject for ApubCommunityOutbox {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, data: &Self::DataType) -> Result<Self::ApubType, LemmyError> {
     let mut ordered_items = vec![];
     for post in self.0 {
@@ -80,6 +82,7 @@ impl ApubObject for ApubCommunityOutbox {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     group_outbox: &GroupOutbox,
     expected_domain: &Url,
@@ -90,6 +93,7 @@ impl ApubObject for ApubCommunityOutbox {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     apub: Self::ApubType,
     data: &Self::DataType,
diff --git a/crates/apub/src/fetcher/post_or_comment.rs b/crates/apub/src/fetcher/post_or_comment.rs
index 55d49481..2702385c 100644
--- a/crates/apub/src/fetcher/post_or_comment.rs
+++ b/crates/apub/src/fetcher/post_or_comment.rs
@@ -33,6 +33,7 @@ impl ApubObject for PostOrComment {
   }
 
   // TODO: this can probably be implemented using a single sql query
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     data: &Self::DataType,
@@ -46,6 +47,7 @@ impl ApubObject for PostOrComment {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> {
     match self {
       PostOrComment::Post(p) => p.delete(data).await,
@@ -61,6 +63,7 @@ impl ApubObject for PostOrComment {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     apub: &Self::ApubType,
     expected_domain: &Url,
@@ -73,6 +76,7 @@ impl ApubObject for PostOrComment {
     }
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     apub: PageOrNote,
     context: &LemmyContext,
diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs
index f8784a5e..816a0516 100644
--- a/crates/apub/src/fetcher/search.rs
+++ b/crates/apub/src/fetcher/search.rs
@@ -4,7 +4,6 @@ use crate::{
   protocol::objects::{group::Group, note::Note, page::Page, person::Person},
   EndpointType,
 };
-use anyhow::anyhow;
 use chrono::NaiveDateTime;
 use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
 use lemmy_utils::LemmyError;
@@ -19,6 +18,7 @@ use url::Url;
 /// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
 /// http://lemmy_gamma:8561/post/3
 /// http://lemmy_delta:8571/comment/2
+#[tracing::instrument(skip_all)]
 pub async fn search_by_apub_id(
   query: &str,
   context: &LemmyContext,
@@ -61,7 +61,7 @@ pub async fn search_by_apub_id(
               .await?,
           ))
         }
-        _ => Err(anyhow!("invalid query").into()),
+        _ => Err(LemmyError::from_message("invalid query")),
       }
     }
   }
@@ -105,6 +105,7 @@ impl ApubObject for SearchableObjects {
   //       a single query.
   //       we could skip this and always return an error, but then it would always fetch objects
   //       over http, and not be able to mark objects as deleted that were deleted by remote server.
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -128,6 +129,7 @@ impl ApubObject for SearchableObjects {
     Ok(None)
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> {
     match self {
       SearchableObjects::Person(p) => p.delete(data).await,
@@ -145,6 +147,7 @@ impl ApubObject for SearchableObjects {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     apub: &Self::ApubType,
     expected_domain: &Url,
@@ -167,6 +170,7 @@ impl ApubObject for SearchableObjects {
     }
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     apub: Self::ApubType,
     context: &LemmyContext,
diff --git a/crates/apub/src/fetcher/user_or_community.rs b/crates/apub/src/fetcher/user_or_community.rs
index 00612cbb..9e38e3dc 100644
--- a/crates/apub/src/fetcher/user_or_community.rs
+++ b/crates/apub/src/fetcher/user_or_community.rs
@@ -35,6 +35,7 @@ impl ApubObject for UserOrCommunity {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     data: &Self::DataType,
@@ -48,6 +49,7 @@ impl ApubObject for UserOrCommunity {
     })
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, data: &Self::DataType) -> Result<(), LemmyError> {
     match self {
       UserOrCommunity::User(p) => p.delete(data).await,
@@ -63,6 +65,7 @@ impl ApubObject for UserOrCommunity {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     apub: &Self::ApubType,
     expected_domain: &Url,
@@ -79,6 +82,7 @@ impl ApubObject for UserOrCommunity {
     }
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     apub: Self::ApubType,
     data: &Self::DataType,
diff --git a/crates/apub/src/fetcher/webfinger.rs b/crates/apub/src/fetcher/webfinger.rs
index b44030b0..426ec2d6 100644
--- a/crates/apub/src/fetcher/webfinger.rs
+++ b/crates/apub/src/fetcher/webfinger.rs
@@ -1,5 +1,4 @@
 use crate::{generate_local_apub_endpoint, EndpointType};
-use anyhow::anyhow;
 use itertools::Itertools;
 use lemmy_apub_lib::{
   object_id::ObjectId,
@@ -34,6 +33,7 @@ pub struct WebfingerResponse {
 ///
 /// TODO: later provide a method in ApubObject to generate the endpoint, so that we dont have to
 ///       pass in EndpointType
+#[tracing::instrument(skip_all)]
 pub async fn webfinger_resolve<Kind>(
   identifier: &str,
   endpoint_type: EndpointType,
@@ -61,6 +61,7 @@ where
 
 /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`,
 /// using webfinger.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn webfinger_resolve_actor<Kind>(
   identifier: &str,
   context: &LemmyContext,
@@ -114,5 +115,9 @@ where
       return object.map(|o| o.actor_id().into());
     }
   }
-  Err(anyhow!("Failed to resolve actor for {}", identifier).into())
+  let error = LemmyError::from(anyhow::anyhow!(
+    "Failed to resolve actor for {}",
+    identifier
+  ));
+  Err(error.with_message("failed_to_resolve"))
 }
diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs
index f62ff36e..562905ad 100644
--- a/crates/apub/src/http/comment.rs
+++ b/crates/apub/src/http/comment.rs
@@ -2,7 +2,7 @@ use crate::{
   http::{create_apub_response, create_apub_tombstone_response},
   objects::comment::ApubComment,
 };
-use actix_web::{body::Body, web, web::Path, HttpResponse};
+use actix_web::{body::AnyBody, web, web::Path, HttpResponse};
 use diesel::result::Error::NotFound;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::traits::ApubObject;
@@ -17,10 +17,11 @@ pub(crate) struct CommentQuery {
 }
 
 /// Return the ActivityPub json representation of a local comment over HTTP.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_comment(
   info: Path<CommentQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let id = CommentId(info.comment_id.parse::<i32>()?);
   let comment: ApubComment = blocking(context.pool(), move |conn| Comment::read(conn, id))
     .await??
diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs
index b040a4a8..d974e31d 100644
--- a/crates/apub/src/http/community.rs
+++ b/crates/apub/src/http/community.rs
@@ -21,7 +21,7 @@ use crate::{
     collections::group_followers::GroupFollowers,
   },
 };
-use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
+use actix_web::{body::AnyBody, web, web::Payload, HttpRequest, HttpResponse};
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
 use lemmy_db_schema::source::community::Community;
@@ -36,10 +36,11 @@ pub(crate) struct CommunityQuery {
 }
 
 /// Return the ActivityPub json representation of a local community over HTTP.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_community_http(
   info: web::Path<CommunityQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let community: ApubCommunity = blocking(context.pool(), move |conn| {
     Community::read_from_name(conn, &info.community_name)
   })
@@ -56,6 +57,7 @@ pub(crate) async fn get_apub_community_http(
 }
 
 /// Handler for all incoming receive to community inboxes.
+#[tracing::instrument(skip_all)]
 pub async fn community_inbox(
   request: HttpRequest,
   payload: Payload,
@@ -96,7 +98,7 @@ pub(in crate::http) async fn receive_group_inbox(
 pub(crate) async fn get_apub_community_followers(
   info: web::Path<CommunityQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let community = blocking(context.pool(), move |conn| {
     Community::read_from_name(conn, &info.community_name)
   })
@@ -110,7 +112,7 @@ pub(crate) async fn get_apub_community_followers(
 pub(crate) async fn get_apub_community_outbox(
   info: web::Path<CommunityQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let community = blocking(context.pool(), move |conn| {
     Community::read_from_name(conn, &info.community_name)
   })
@@ -121,10 +123,11 @@ pub(crate) async fn get_apub_community_outbox(
   Ok(create_apub_response(&outbox.into_apub(&outbox_data).await?))
 }
 
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_community_moderators(
   info: web::Path<CommunityQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let community: ApubCommunity = blocking(context.pool(), move |conn| {
     Community::read_from_name(conn, &info.community_name)
   })
diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs
index 0d484e8f..5e40b9d6 100644
--- a/crates/apub/src/http/mod.rs
+++ b/crates/apub/src/http/mod.rs
@@ -7,13 +7,13 @@ use crate::{
   insert_activity,
 };
 use actix_web::{
-  body::Body,
+  body::AnyBody,
   web,
   web::{Bytes, BytesMut, Payload},
   HttpRequest,
   HttpResponse,
 };
-use anyhow::{anyhow, Context};
+use anyhow::Context;
 use futures::StreamExt;
 use http::StatusCode;
 use lemmy_api_common::blocking;
@@ -38,6 +38,7 @@ mod person;
 mod post;
 pub mod routes;
 
+#[tracing::instrument(skip_all)]
 pub async fn shared_inbox(
   request: HttpRequest,
   payload: Payload,
@@ -75,6 +76,7 @@ pub(crate) struct ActivityCommonFields {
 }
 
 // TODO: move most of this code to library
+#[tracing::instrument(skip_all)]
 async fn receive_activity<'a, T>(
   request: HttpRequest,
   activity: T,
@@ -117,7 +119,7 @@ where
 
 /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
 /// headers.
-fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
+fn create_apub_response<T>(data: &T) -> HttpResponse<AnyBody>
 where
   T: Serialize,
 {
@@ -126,13 +128,13 @@ where
     .json(WithContext::new(data))
 }
 
-fn create_json_apub_response(data: serde_json::Value) -> HttpResponse<Body> {
+fn create_json_apub_response(data: serde_json::Value) -> HttpResponse<AnyBody> {
   HttpResponse::Ok()
     .content_type(APUB_JSON_CONTENT_TYPE)
     .json(data)
 }
 
-fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
+fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<AnyBody>
 where
   T: Serialize,
 {
@@ -149,10 +151,11 @@ pub struct ActivityQuery {
 }
 
 /// Return the ActivityPub json representation of a local activity over HTTP.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_activity(
   info: web::Path<ActivityQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let settings = context.settings();
   let activity_id = Url::parse(&format!(
     "{}/activities/{}/{}",
@@ -178,13 +181,11 @@ fn assert_activity_not_local(id: &Url, hostname: &str) -> Result<(), LemmyError>
   let activity_domain = id.domain().context(location_info!())?;
 
   if activity_domain == hostname {
-    return Err(
-      anyhow!(
-        "Error: received activity which was sent by local instance: {:?}",
-        id
-      )
-      .into(),
-    );
+    let error = LemmyError::from(anyhow::anyhow!(
+      "Error: received activity which was sent by local instance: {:?}",
+      id
+    ));
+    return Err(error.with_message("received_local_activity"));
   }
   Ok(())
 }
diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs
index b98f189c..a58ae725 100644
--- a/crates/apub/src/http/person.rs
+++ b/crates/apub/src/http/person.rs
@@ -11,7 +11,7 @@ use crate::{
   objects::person::ApubPerson,
   protocol::collections::person_outbox::PersonOutbox,
 };
-use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
+use actix_web::{body::AnyBody, web, web::Payload, HttpRequest, HttpResponse};
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::traits::ApubObject;
 use lemmy_db_schema::source::person::Person;
@@ -26,10 +26,11 @@ pub struct PersonQuery {
 }
 
 /// Return the ActivityPub json representation of a local person over HTTP.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_person_http(
   info: web::Path<PersonQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let user_name = info.into_inner().user_name;
   // TODO: this needs to be able to read deleted persons, so that it can send tombstones
   let person: ApubPerson = blocking(context.pool(), move |conn| {
@@ -47,6 +48,7 @@ pub(crate) async fn get_apub_person_http(
   }
 }
 
+#[tracing::instrument(skip_all)]
 pub async fn person_inbox(
   request: HttpRequest,
   payload: Payload,
@@ -69,10 +71,11 @@ pub(in crate::http) async fn receive_person_inbox(
   receive_activity(request, activity, activity_data, context).await
 }
 
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_person_outbox(
   info: web::Path<PersonQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let person = blocking(context.pool(), move |conn| {
     Person::find_by_name(conn, &info.user_name)
   })
diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs
index 52fe002a..0b1d79e1 100644
--- a/crates/apub/src/http/post.rs
+++ b/crates/apub/src/http/post.rs
@@ -2,7 +2,7 @@ use crate::{
   http::{create_apub_response, create_apub_tombstone_response},
   objects::post::ApubPost,
 };
-use actix_web::{body::Body, web, HttpResponse};
+use actix_web::{body::AnyBody, web, HttpResponse};
 use diesel::result::Error::NotFound;
 use lemmy_api_common::blocking;
 use lemmy_apub_lib::traits::ApubObject;
@@ -17,10 +17,11 @@ pub(crate) struct PostQuery {
 }
 
 /// Return the ActivityPub json representation of a local post over HTTP.
+#[tracing::instrument(skip_all)]
 pub(crate) async fn get_apub_post(
   info: web::Path<PostQuery>,
   context: web::Data<LemmyContext>,
-) -> Result<HttpResponse<Body>, LemmyError> {
+) -> Result<HttpResponse<AnyBody>, LemmyError> {
   let id = PostId(info.post_id.parse::<i32>()?);
   let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, id))
     .await??
diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs
index b3c7eff7..44c2d0d5 100644
--- a/crates/apub/src/lib.rs
+++ b/crates/apub/src/lib.rs
@@ -1,5 +1,5 @@
 use crate::fetcher::post_or_comment::PostOrComment;
-use anyhow::{anyhow, Context};
+use anyhow::Context;
 use lemmy_api_common::blocking;
 use lemmy_db_schema::{newtypes::DbUrl, source::activity::Activity, DbPool};
 use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
@@ -28,6 +28,7 @@ pub mod protocol;
 ///
 /// `use_strict_allowlist` should be true only when parsing a remote community, or when parsing a
 /// post/comment in a local community.
+#[tracing::instrument(skip(settings))]
 pub(crate) fn check_is_apub_id_valid(
   apub_id: &Url,
   use_strict_allowlist: bool,
@@ -40,24 +41,28 @@ pub(crate) fn check_is_apub_id_valid(
     return if domain == local_instance {
       Ok(())
     } else {
-      Err(
-        anyhow!(
-          "Trying to connect with {}, but federation is disabled",
-          domain
-        )
-        .into(),
-      )
+      let error = LemmyError::from(anyhow::anyhow!(
+        "Trying to connect with {}, but federation is disabled",
+        domain
+      ));
+      Err(error.with_message("federation_disabled"))
     };
   }
 
   let host = apub_id.host_str().context(location_info!())?;
   let host_as_ip = host.parse::<IpAddr>();
   if host == "localhost" || host_as_ip.is_ok() {
-    return Err(anyhow!("invalid hostname {}: {}", host, apub_id).into());
+    let error = LemmyError::from(anyhow::anyhow!("invalid hostname {}: {}", host, apub_id));
+    return Err(error.with_message("invalid_hostname"));
   }
 
   if apub_id.scheme() != settings.get_protocol_string() {
-    return Err(anyhow!("invalid apub id scheme {}: {}", apub_id.scheme(), apub_id).into());
+    let error = LemmyError::from(anyhow::anyhow!(
+      "invalid apub id scheme {}: {}",
+      apub_id.scheme(),
+      apub_id
+    ));
+    return Err(error.with_message("invalid_scheme"));
   }
 
   // TODO: might be good to put the part above in one method, and below in another
@@ -65,7 +70,8 @@ pub(crate) fn check_is_apub_id_valid(
   //        -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist
   if let Some(blocked) = settings.to_owned().federation.blocked_instances {
     if blocked.contains(&domain) {
-      return Err(anyhow!("{} is in federation blocklist", domain).into());
+      let error = LemmyError::from(anyhow::anyhow!("{} is in federation blocklist", domain));
+      return Err(error.with_message("federation_blocked"));
     }
   }
 
@@ -78,7 +84,8 @@ pub(crate) fn check_is_apub_id_valid(
       allowed.push(local_instance);
 
       if !allowed.contains(&domain) {
-        return Err(anyhow!("{} not in federation allowlist", domain).into());
+        let error = LemmyError::from(anyhow::anyhow!("{} not in federation allowlist", domain));
+        return Err(error.with_message("federation_not_allowed"));
       }
     }
   }
@@ -163,6 +170,7 @@ fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
 
 /// Store a sent or received activity in the database, for logging purposes. These records are not
 /// persistent.
+#[tracing::instrument(skip(pool))]
 async fn insert_activity(
   ap_id: &Url,
   activity: serde_json::Value,
diff --git a/crates/apub/src/mentions.rs b/crates/apub/src/mentions.rs
index f8de4385..5912d808 100644
--- a/crates/apub/src/mentions.rs
+++ b/crates/apub/src/mentions.rs
@@ -34,6 +34,7 @@ pub struct MentionsAndAddresses {
 /// This takes a comment, and builds a list of to_addresses, inboxes,
 /// and mention tags, so they know where to be sent to.
 /// Addresses are the persons / addresses that go in the cc field.
+#[tracing::instrument(skip(comment, community_id, context))]
 pub async fn collect_non_local_mentions(
   comment: &ApubComment,
   community_id: ObjectId<ApubCommunity>,
@@ -88,6 +89,7 @@ pub async fn collect_non_local_mentions(
 
 /// Returns the apub ID of the person this comment is responding to. Meaning, in case this is a
 /// top-level comment, the creator of the post, otherwise the creator of the parent comment.
+#[tracing::instrument(skip(pool, comment))]
 async fn get_comment_parent_creator(
   pool: &DbPool,
   comment: &Comment,
diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs
index 41ea12b9..57bbbe12 100644
--- a/crates/apub/src/objects/comment.rs
+++ b/crates/apub/src/objects/comment.rs
@@ -12,7 +12,6 @@ use crate::{
   PostOrComment,
 };
 use activitystreams_kinds::{object::NoteType, public};
-use anyhow::anyhow;
 use chrono::NaiveDateTime;
 use html2md::parse_html;
 use lemmy_api_common::blocking;
@@ -65,6 +64,7 @@ impl ApubObject for ApubComment {
     None
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -78,6 +78,7 @@ impl ApubObject for ApubComment {
     )
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
     if !self.deleted {
       blocking(context.pool(), move |conn| {
@@ -88,6 +89,7 @@ impl ApubObject for ApubComment {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, context: &LemmyContext) -> Result<Note, LemmyError> {
     let creator_id = self.creator_id;
     let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
@@ -136,6 +138,7 @@ impl ApubObject for ApubComment {
     Ok(Tombstone::new(self.ap_id.clone().into()))
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     note: &Note,
     expected_domain: &Url,
@@ -160,7 +163,7 @@ impl ApubObject for ApubComment {
     )
     .await?;
     if post.locked {
-      return Err(anyhow!("Post is locked").into());
+      return Err(LemmyError::from_message("Post is locked"));
     }
     Ok(())
   }
@@ -168,6 +171,7 @@ impl ApubObject for ApubComment {
   /// Converts a `Note` to `Comment`.
   ///
   /// If the parent community, post and comment(s) are not known locally, these are also fetched.
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     note: Note,
     context: &LemmyContext,
diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs
index 7bb57ce6..c560278e 100644
--- a/crates/apub/src/objects/community.rs
+++ b/crates/apub/src/objects/community.rs
@@ -55,6 +55,7 @@ impl ApubObject for ApubCommunity {
     Some(self.last_refreshed_at)
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -68,6 +69,7 @@ impl ApubObject for ApubCommunity {
     )
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
     blocking(context.pool(), move |conn| {
       Community::update_deleted(conn, self.id, true)
@@ -76,6 +78,7 @@ impl ApubObject for ApubCommunity {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, _context: &LemmyContext) -> Result<Group, LemmyError> {
     let source = self.description.clone().map(|bio| Source {
       content: bio,
@@ -115,6 +118,7 @@ impl ApubObject for ApubCommunity {
     Ok(Tombstone::new(self.actor_id()))
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     group: &Group,
     expected_domain: &Url,
@@ -125,6 +129,7 @@ impl ApubObject for ApubCommunity {
   }
 
   /// Converts a `Group` to `Community`, inserts it into the database and updates moderators.
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     group: Group,
     context: &LemmyContext,
@@ -181,6 +186,7 @@ impl ActorType for ApubCommunity {
 
 impl ApubCommunity {
   /// For a given community, returns the inboxes of all followers.
+  #[tracing::instrument(skip_all)]
   pub(crate) async fn get_follower_inboxes(
     &self,
     context: &LemmyContext,
diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs
index 9c93e76d..a4f858c0 100644
--- a/crates/apub/src/objects/person.rs
+++ b/crates/apub/src/objects/person.rs
@@ -57,6 +57,7 @@ impl ApubObject for ApubPerson {
     Some(self.last_refreshed_at)
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -70,6 +71,7 @@ impl ApubObject for ApubPerson {
     )
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
     blocking(context.pool(), move |conn| {
       DbPerson::update_deleted(conn, self.id, true)
@@ -78,6 +80,7 @@ impl ApubObject for ApubPerson {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, _pool: &LemmyContext) -> Result<Person, LemmyError> {
     let kind = if self.bot_account {
       UserTypes::Service
@@ -118,6 +121,7 @@ impl ApubObject for ApubPerson {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     person: &Person,
     expected_domain: &Url,
@@ -135,6 +139,7 @@ impl ApubObject for ApubPerson {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     person: Person,
     context: &LemmyContext,
diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs
index cb86b97d..37c0fb3f 100644
--- a/crates/apub/src/objects/post.rs
+++ b/crates/apub/src/objects/post.rs
@@ -60,6 +60,7 @@ impl ApubObject for ApubPost {
     None
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -73,6 +74,7 @@ impl ApubObject for ApubPost {
     )
   }
 
+  #[tracing::instrument(skip_all)]
   async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
     if !self.deleted {
       blocking(context.pool(), move |conn| {
@@ -84,6 +86,7 @@ impl ApubObject for ApubPost {
   }
 
   // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, context: &LemmyContext) -> Result<Page, LemmyError> {
     let creator_id = self.creator_id;
     let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
@@ -125,6 +128,7 @@ impl ApubObject for ApubPost {
     Ok(Tombstone::new(self.ap_id.clone().into()))
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     page: &Page,
     expected_domain: &Url,
@@ -146,6 +150,7 @@ impl ApubObject for ApubPost {
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     page: Page,
     context: &LemmyContext,
diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs
index d624e133..a96a14e8 100644
--- a/crates/apub/src/objects/private_message.rs
+++ b/crates/apub/src/objects/private_message.rs
@@ -2,7 +2,6 @@ use crate::protocol::{
   objects::chat_message::{ChatMessage, ChatMessageType},
   Source,
 };
-use anyhow::anyhow;
 use chrono::NaiveDateTime;
 use html2md::parse_html;
 use lemmy_api_common::blocking;
@@ -53,6 +52,7 @@ impl ApubObject for ApubPrivateMessage {
     None
   }
 
+  #[tracing::instrument(skip_all)]
   async fn read_from_apub_id(
     object_id: Url,
     context: &LemmyContext,
@@ -71,6 +71,7 @@ impl ApubObject for ApubPrivateMessage {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn into_apub(self, context: &LemmyContext) -> Result<ChatMessage, LemmyError> {
     let creator_id = self.creator_id;
     let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
@@ -101,6 +102,7 @@ impl ApubObject for ApubPrivateMessage {
     unimplemented!()
   }
 
+  #[tracing::instrument(skip_all)]
   async fn verify(
     note: &ChatMessage,
     expected_domain: &Url,
@@ -114,11 +116,12 @@ impl ApubObject for ApubPrivateMessage {
       .dereference(context, request_counter)
       .await?;
     if person.banned {
-      return Err(anyhow!("Person is banned from site").into());
+      return Err(LemmyError::from_message("Person is banned from site"));
     }
     Ok(())
   }
 
+  #[tracing::instrument(skip_all)]
   async fn from_apub(
     note: ChatMessage,
     context: &LemmyContext,
diff --git a/crates/apub/src/protocol/activities/voting/vote.rs b/crates/apub/src/protocol/activities/voting/vote.rs
index ba5c629c..1d66b5be 100644
--- a/crates/apub/src/protocol/activities/voting/vote.rs
+++ b/crates/apub/src/protocol/activities/voting/vote.rs
@@ -3,7 +3,6 @@ use crate::{
   objects::person::ApubPerson,
   protocol::Unparsed,
 };
-use anyhow::anyhow;
 use lemmy_apub_lib::object_id::ObjectId;
 use lemmy_utils::LemmyError;
 use serde::{Deserialize, Serialize};
@@ -40,7 +39,7 @@ impl TryFrom<i16> for VoteType {
     match value {
       1 => Ok(VoteType::Like),
       -1 => Ok(VoteType::Dislike),
-      _ => Err(anyhow!("invalid vote value").into()),
+      _ => Err(LemmyError::from_message("invalid vote value")),
     }
   }
 }
diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs
index 1fca51e4..bac85600 100644
--- a/crates/apub/src/protocol/objects/page.rs
+++ b/crates/apub/src/protocol/objects/page.rs
@@ -3,7 +3,6 @@ use crate::{
   protocol::{ImageObject, Source, Unparsed},
 };
 use activitystreams_kinds::object::PageType;
-use anyhow::anyhow;
 use chrono::{DateTime, FixedOffset};
 use lemmy_apub_lib::{
   data::Data,
@@ -75,7 +74,7 @@ impl Page {
           break Ok(c);
         }
       } else {
-        return Err(anyhow!("No community found in cc").into());
+        return Err(LemmyError::from_message("No community found in cc"));
       }
     }
   }
diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs
index ddff1375..257abe4d 100644
--- a/crates/db_schema/src/lib.rs
+++ b/crates/db_schema/src/lib.rs
@@ -21,7 +21,7 @@ pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgC
 use crate::newtypes::DbUrl;
 use chrono::NaiveDateTime;
 use diesel::{Connection, PgConnection};
-use lemmy_utils::ApiError;
+use lemmy_utils::LemmyError;
 use once_cell::sync::Lazy;
 use regex::Regex;
 use serde::{Deserialize, Serialize};
@@ -100,13 +100,13 @@ pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
 
 pub fn diesel_option_overwrite_to_url(
   opt: &Option<String>,
-) -> Result<Option<Option<DbUrl>>, ApiError> {
+) -> Result<Option<Option<DbUrl>>, LemmyError> {
   match opt.as_ref().map(|s| s.as_str()) {
     // An empty string is an erase
     Some("") => Ok(Some(None)),
     Some(str_url) => match Url::parse(str_url) {
       Ok(url) => Ok(Some(Some(url.into()))),
-      Err(e) => Err(ApiError::err("invalid_url", e)),
+      Err(e) => Err(LemmyError::from(e).with_message("invalid_url")),
     },
     None => Ok(None),
   }
diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml
index c187ead6..25ab77f7 100644
--- a/crates/routes/Cargo.toml
+++ b/crates/routes/Cargo.toml
@@ -32,3 +32,4 @@ awc = { version = "3.0.0-beta.8", default-features = false }
 url = { version = "2.2.2", features = ["serde"] }
 strum = "0.21.0"
 once_cell = "1.8.0"
+tracing = "0.1.29"
diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs
index 534ec9fb..ce95c93a 100644
--- a/crates/routes/src/feeds.rs
+++ b/crates/routes/src/feeds.rs
@@ -58,6 +58,7 @@ static RSS_NAMESPACE: Lazy<HashMap<String, String>> = Lazy::new(|| {
   h
 });
 
+#[tracing::instrument(skip_all)]
 async fn get_all_feed(
   info: web::Query<Params>,
   context: web::Data<LemmyContext>,
@@ -66,6 +67,7 @@ async fn get_all_feed(
   Ok(get_feed_data(&context, ListingType::All, sort_type).await?)
 }
 
+#[tracing::instrument(skip_all)]
 async fn get_local_feed(
   info: web::Query<Params>,
   context: web::Data<LemmyContext>,
@@ -74,6 +76,7 @@ async fn get_local_feed(
   Ok(get_feed_data(&context, ListingType::Local, sort_type).await?)
 }
 
+#[tracing::instrument(skip_all)]
 async fn get_feed_data(
   context: &LemmyContext,
   listing_type: ListingType,
@@ -114,6 +117,7 @@ async fn get_feed_data(
   )
 }
 
+#[tracing::instrument(skip_all)]
 async fn get_feed(
   req: HttpRequest,
   info: web::Query<Params>,
@@ -167,6 +171,7 @@ fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
   SortType::from_str(&sort_query)
 }
 
+#[tracing::instrument(skip_all)]
 fn get_feed_user(
   conn: &PgConnection,
   sort_type: &SortType,
@@ -194,6 +199,7 @@ fn get_feed_user(
   Ok(channel_builder)
 }
 
+#[tracing::instrument(skip_all)]
 fn get_feed_community(
   conn: &PgConnection,
   sort_type: &SortType,
@@ -225,6 +231,7 @@ fn get_feed_community(
   Ok(channel_builder)
 }
 
+#[tracing::instrument(skip_all)]
 fn get_feed_front(
   conn: &PgConnection,
   jwt_secret: &str,
@@ -260,6 +267,7 @@ fn get_feed_front(
   Ok(channel_builder)
 }
 
+#[tracing::instrument(skip_all)]
 fn get_feed_inbox(
   conn: &PgConnection,
   jwt_secret: &str,
@@ -303,6 +311,7 @@ fn get_feed_inbox(
   Ok(channel_builder)
 }
 
+#[tracing::instrument(skip_all)]
 fn create_reply_and_mention_items(
   replies: Vec<CommentView>,
   mentions: Vec<PersonMentionView>,
@@ -346,6 +355,7 @@ fn create_reply_and_mention_items(
   Ok(reply_items)
 }
 
+#[tracing::instrument(skip_all)]
 fn build_item(
   creator_name: &str,
   published: &NaiveDateTime,
@@ -376,6 +386,7 @@ fn build_item(
   Ok(i.build().map_err(|e| anyhow!(e))?)
 }
 
+#[tracing::instrument(skip_all)]
 fn create_post_items(
   posts: Vec<PostView>,
   protocol_and_hostname: &str,
diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs
index 5eaff30e..04c60e3f 100644
--- a/crates/utils/src/lib.rs
+++ b/crates/utils/src/lib.rs
@@ -15,10 +15,13 @@ mod test;
 pub mod utils;
 pub mod version;
 
+mod sensitive;
+
+pub use sensitive::Sensitive;
+
+use actix_web::HttpResponse;
 use http::StatusCode;
 use std::{fmt, fmt::Display};
-use thiserror::Error;
-use tracing::warn;
 use tracing_error::SpanTrace;
 
 pub type ConnectionId = usize;
@@ -44,30 +47,40 @@ macro_rules! location_info {
   };
 }
 
-#[derive(Debug, Error)]
-#[error("{{\"error\":\"{message}\"}}")]
-pub struct ApiError {
-  message: String,
+#[derive(serde::Serialize)]
+struct ApiError {
+  error: &'static str,
+}
+
+pub struct LemmyError {
+  pub message: Option<&'static str>,
+  pub inner: anyhow::Error,
+  pub context: SpanTrace,
 }
 
-impl ApiError {
-  pub fn err_plain(msg: &str) -> Self {
-    ApiError {
-      message: msg.to_string(),
+impl LemmyError {
+  pub fn from_message(message: &'static str) -> Self {
+    let inner = anyhow::anyhow!("{}", message);
+    LemmyError {
+      message: Some(message),
+      inner,
+      context: SpanTrace::capture(),
     }
   }
-  pub fn err<E: Display>(msg: &str, original_error: E) -> Self {
-    warn!("{}", original_error);
-    ApiError {
-      message: msg.to_string(),
+  pub fn with_message(self, message: &'static str) -> Self {
+    LemmyError {
+      message: Some(message),
+      ..self
     }
   }
-}
+  pub fn to_json(&self) -> Result<String, Self> {
+    let api_error = match self.message {
+      Some(error) => ApiError { error },
+      None => ApiError { error: "Unknown" },
+    };
 
-#[derive(Debug)]
-pub struct LemmyError {
-  pub inner: anyhow::Error,
-  pub context: SpanTrace,
+    Ok(serde_json::to_string(&api_error)?)
+  }
 }
 
 impl<T> From<T> for LemmyError
@@ -76,15 +89,29 @@ where
 {
   fn from(t: T) -> Self {
     LemmyError {
+      message: None,
       inner: t.into(),
       context: SpanTrace::capture(),
     }
   }
 }
 
+impl std::fmt::Debug for LemmyError {
+  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    f.debug_struct("LemmyError")
+      .field("message", &self.message)
+      .field("inner", &self.inner)
+      .field("context", &"SpanTrace")
+      .finish()
+  }
+}
+
 impl Display for LemmyError {
   fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-    self.inner.fmt(f)?;
+    if let Some(message) = self.message {
+      write!(f, "{}: ", message)?;
+    }
+    writeln!(f, "{}", self.inner)?;
     self.context.fmt(f)
   }
 }
@@ -93,7 +120,17 @@ impl actix_web::error::ResponseError for LemmyError {
   fn status_code(&self) -> StatusCode {
     match self.inner.downcast_ref::<diesel::result::Error>() {
       Some(diesel::result::Error::NotFound) => StatusCode::NOT_FOUND,
-      _ => StatusCode::INTERNAL_SERVER_ERROR,
+      _ => StatusCode::BAD_REQUEST,
+    }
+  }
+
+  fn error_response(&self) -> HttpResponse {
+    if let Some(message) = &self.message {
+      HttpResponse::build(self.status_code()).json(ApiError { error: message })
+    } else {
+      HttpResponse::build(self.status_code())
+        .content_type("text/plain")
+        .body(self.inner.to_string())
     }
   }
 }
diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs
index 46819915..580ed3e7 100644
--- a/crates/utils/src/rate_limit/rate_limiter.rs
+++ b/crates/utils/src/rate_limit/rate_limiter.rs
@@ -1,4 +1,4 @@
-use crate::{ApiError, IpAddr, LemmyError};
+use crate::{IpAddr, LemmyError};
 use std::{collections::HashMap, time::SystemTime};
 use strum::IntoEnumIterator;
 use tracing::debug;
@@ -79,18 +79,14 @@ impl RateLimiter {
             time_passed,
             rate_limit.allowance
           );
-          Err(
-            ApiError {
-              message: format!(
-                "Too many requests. type: {}, IP: {}, {} per {} seconds",
-                type_.as_ref(),
-                ip,
-                rate,
-                per
-              ),
-            }
-            .into(),
-          )
+          let error = LemmyError::from(anyhow::anyhow!(
+            "Too many requests. type: {}, IP: {}, {} per {} seconds",
+            type_.as_ref(),
+            ip,
+            rate,
+            per
+          ));
+          Err(error.with_message("too_many_requests"))
         } else {
           if !check_only {
             rate_limit.allowance -= 1.0;
diff --git a/crates/utils/src/sensitive.rs b/crates/utils/src/sensitive.rs
new file mode 100644
index 00000000..c71f357e
--- /dev/null
+++ b/crates/utils/src/sensitive.rs
@@ -0,0 +1,98 @@
+use std::{
+  borrow::Borrow,
+  ops::{Deref, DerefMut},
+};
+
+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
+#[serde(transparent)]
+pub struct Sensitive<T>(T);
+
+impl<T> Sensitive<T> {
+  pub fn new(item: T) -> Self {
+    Sensitive(item)
+  }
+
+  pub fn into_inner(this: Self) -> T {
+    this.0
+  }
+}
+
+impl<T> std::fmt::Debug for Sensitive<T> {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    f.debug_struct("Sensitive").finish()
+  }
+}
+
+impl<T> AsRef<T> for Sensitive<T> {
+  fn as_ref(&self) -> &T {
+    &self.0
+  }
+}
+
+impl AsRef<str> for Sensitive<String> {
+  fn as_ref(&self) -> &str {
+    &self.0
+  }
+}
+
+impl AsRef<[u8]> for Sensitive<String> {
+  fn as_ref(&self) -> &[u8] {
+    self.0.as_ref()
+  }
+}
+
+impl AsRef<[u8]> for Sensitive<Vec<u8>> {
+  fn as_ref(&self) -> &[u8] {
+    self.0.as_ref()
+  }
+}
+
+impl<T> AsMut<T> for Sensitive<T> {
+  fn as_mut(&mut self) -> &mut T {
+    &mut self.0
+  }
+}
+
+impl AsMut<str> for Sensitive<String> {
+  fn as_mut(&mut self) -> &mut str {
+    &mut self.0
+  }
+}
+
+impl Deref for Sensitive<String> {
+  type Target = str;
+
+  fn deref(&self) -> &Self::Target {
+    &self.0
+  }
+}
+
+impl DerefMut for Sensitive<String> {
+  fn deref_mut(&mut self) -> &mut Self::Target {
+    &mut self.0
+  }
+}
+
+impl<T> From<T> for Sensitive<T> {
+  fn from(t: T) -> Self {
+    Sensitive(t)
+  }
+}
+
+impl From<&str> for Sensitive<String> {
+  fn from(s: &str) -> Self {
+    Sensitive(s.into())
+  }
+}
+
+impl<T> Borrow<T> for Sensitive<T> {
+  fn borrow(&self) -> &T {
+    &self.0
+  }
+}
+
+impl Borrow<str> for Sensitive<String> {
+  fn borrow(&self) -> &str {
+    &self.0
+  }
+}
diff --git a/crates/utils/src/utils.rs b/crates/utils/src/utils.rs
index 1c33b4c5..90f780e2 100644
--- a/crates/utils/src/utils.rs
+++ b/crates/utils/src/utils.rs
@@ -1,4 +1,4 @@
-use crate::{ApiError, IpAddr};
+use crate::{IpAddr, LemmyError};
 use actix_web::dev::ConnectionInfo;
 use chrono::{DateTime, FixedOffset, NaiveDateTime};
 use itertools::Itertools;
@@ -60,15 +60,19 @@ pub(crate) fn slur_check<'a>(
   }
 }
 
-pub fn check_slurs(text: &str, slur_regex: &Option<Regex>) -> Result<(), ApiError> {
+pub fn check_slurs(text: &str, slur_regex: &Option<Regex>) -> Result<(), LemmyError> {
   if let Err(slurs) = slur_check(text, slur_regex) {
-    Err(ApiError::err_plain(&slurs_vec_to_str(slurs)))
+    let error = LemmyError::from(anyhow::anyhow!("{}", slurs_vec_to_str(slurs)));
+    Err(error.with_message("slurs"))
   } else {
     Ok(())
   }
 }
 
-pub fn check_slurs_opt(text: &Option<String>, slur_regex: &Option<Regex>) -> Result<(), ApiError> {
+pub fn check_slurs_opt(
+  text: &Option<String>,
+  slur_regex: &Option<Regex>,
+) -> Result<(), LemmyError> {
   match text {
     Some(t) => check_slurs(t, slur_regex),
     None => Ok(()),
diff --git a/crates/websocket/src/chat_server.rs b/crates/websocket/src/chat_server.rs
index 2b58b2c1..5d91800e 100644
--- a/crates/websocket/src/chat_server.rs
+++ b/crates/websocket/src/chat_server.rs
@@ -22,7 +22,6 @@ use lemmy_utils::{
   location_info,
   rate_limit::RateLimit,
   settings::structs::Settings,
-  ApiError,
   ConnectionId,
   IpAddr,
   LemmyError,
@@ -477,7 +476,7 @@ impl ChatServer {
       let data = &json["data"].to_string();
       let op = &json["op"]
         .as_str()
-        .ok_or_else(|| ApiError::err_plain("missing op"))?;
+        .ok_or_else(|| LemmyError::from_message("missing op"))?;
 
       if let Ok(user_operation_crud) = UserOperationCrud::from_str(op) {
         let fut = (message_handler_crud)(context, msg.id, user_operation_crud.clone(), data);
diff --git a/crates/websocket/src/handlers.rs b/crates/websocket/src/handlers.rs
index 1e67cdb3..f35e8b31 100644
--- a/crates/websocket/src/handlers.rs
+++ b/crates/websocket/src/handlers.rs
@@ -76,7 +76,10 @@ impl Handler<StandardMessage> for ChatServer {
         }
         Err(e) => {
           error!("Error during message handling {}", e);
-          Ok(e.to_string())
+          Ok(
+            e.to_json()
+              .unwrap_or_else(|_| String::from(r#"{"error":"failed to serialize json"}"#)),
+          )
         }
       }
     })
diff --git a/crates/websocket/src/routes.rs b/crates/websocket/src/routes.rs
index e5551b41..71df5515 100644
--- a/crates/websocket/src/routes.rs
+++ b/crates/websocket/src/routes.rs
@@ -115,7 +115,6 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsSession {
       }
       ws::Message::Text(text) => {
         let m = text.trim().to_owned();
-        info!("Message received: {:?} from id: {}", &m, self.id);
 
         self
           .cs_addr
diff --git a/src/api_routes.rs b/src/api_routes.rs
index ae5fa40c..3fbb7f05 100644
--- a/src/api_routes.rs
+++ b/src/api_routes.rs
@@ -1,4 +1,4 @@
-use actix_web::{error::ErrorBadRequest, *};
+use actix_web::*;
 use lemmy_api::Perform;
 use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
 use lemmy_api_crud::PerformCrud;
@@ -232,8 +232,7 @@ where
   let res = data
     .perform(&context, None)
     .await
-    .map(|json| HttpResponse::Ok().json(json))
-    .map_err(ErrorBadRequest)?;
+    .map(|json| HttpResponse::Ok().json(json))?;
   Ok(res)
 }
 
@@ -268,8 +267,7 @@ where
   let res = data
     .perform(&context, None)
     .await
-    .map(|json| HttpResponse::Ok().json(json))
-    .map_err(ErrorBadRequest)?;
+    .map(|json| HttpResponse::Ok().json(json))?;
   Ok(res)
 }
 
diff --git a/src/lib.rs b/src/lib.rs
index 98c2320d..62d07166 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,21 +1,20 @@
 #![recursion_limit = "512"]
 pub mod api_routes;
 pub mod code_migrations;
+pub mod root_span_builder;
 pub mod scheduled_tasks;
 
 use lemmy_utils::LemmyError;
 use tracing::subscriber::set_global_default;
 use tracing_error::ErrorLayer;
 use tracing_log::LogTracer;
-use tracing_subscriber::{fmt::format::FmtSpan, layer::SubscriberExt, EnvFilter, Registry};
+use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
 
 pub fn init_tracing() -> Result<(), LemmyError> {
   LogTracer::init()?;
 
   let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
-  let format_layer = tracing_subscriber::fmt::layer()
-    .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
-    .pretty();
+  let format_layer = tracing_subscriber::fmt::layer();
 
   let subscriber = Registry::default()
     .with(env_filter)
diff --git a/src/main.rs b/src/main.rs
index 8a0b37f3..8f130fde 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,6 +18,7 @@ use lemmy_server::{
   api_routes,
   code_migrations::run_advanced_migrations,
   init_tracing,
+  root_span_builder::QuieterRootSpanBuilder,
   scheduled_tasks,
 };
 use lemmy_utils::{
@@ -123,7 +124,8 @@ async fn main() -> Result<(), LemmyError> {
     );
     let rate_limiter = rate_limiter.clone();
     App::new()
-      .wrap(TracingLogger::default())
+      .wrap(actix_web::middleware::Logger::default())
+      .wrap(TracingLogger::<QuieterRootSpanBuilder>::new())
       .app_data(Data::new(context))
       // The routes
       .configure(|cfg| api_routes::config(cfg, &rate_limiter))
diff --git a/src/root_span_builder.rs b/src/root_span_builder.rs
new file mode 100644
index 00000000..84a7817c
--- /dev/null
+++ b/src/root_span_builder.rs
@@ -0,0 +1,85 @@
+use actix_web::{http::StatusCode, ResponseError};
+use tracing::Span;
+use tracing_actix_web::RootSpanBuilder;
+
+// Code in this module adapted from DefaultRootSpanBuilder
+// https://github.com/LukeMathWalker/tracing-actix-web/blob/main/src/root_span_builder.rs
+// and root_span!
+// https://github.com/LukeMathWalker/tracing-actix-web/blob/main/src/root_span_macro.rs
+
+pub struct QuieterRootSpanBuilder;
+
+impl RootSpanBuilder for QuieterRootSpanBuilder {
+  fn on_request_start(request: &actix_web::dev::ServiceRequest) -> Span {
+    let request_id = tracing_actix_web::root_span_macro::private::get_request_id(request);
+
+    tracing::info_span!(
+        "HTTP request",
+        http.method = %request.method(),
+        http.scheme = request.connection_info().scheme(),
+        http.host = %request.connection_info().host(),
+        http.target = %request.uri().path(),
+        http.status_code = tracing::field::Empty,
+        otel.kind = "server",
+        otel.status_code = tracing::field::Empty,
+        trace_id = tracing::field::Empty,
+        request_id = %request_id,
+        exception.message = tracing::field::Empty,
+        // Not proper OpenTelemetry, but their terminology is fairly exception-centric
+        exception.details = tracing::field::Empty,
+    )
+  }
+
+  fn on_request_end<B>(
+    span: tracing::Span,
+    outcome: &Result<actix_web::dev::ServiceResponse<B>, actix_web::Error>,
+  ) {
+    match &outcome {
+      Ok(response) => {
+        if let Some(error) = response.response().error() {
+          // use the status code already constructed for the outgoing HTTP response
+          handle_error(span, response.status(), error.as_response_error());
+        } else {
+          let code: i32 = response.response().status().as_u16().into();
+          span.record("http.status_code", &code);
+          span.record("otel.status_code", &"OK");
+        }
+      }
+      Err(error) => {
+        let response_error = error.as_response_error();
+        handle_error(span, response_error.status_code(), response_error);
+      }
+    };
+  }
+}
+
+fn handle_error(span: Span, status_code: StatusCode, response_error: &dyn ResponseError) {
+  let code: i32 = status_code.as_u16().into();
+
+  span.record("http.status_code", &code);
+
+  if status_code.is_client_error() {
+    span.record("otel.status_code", &"OK");
+  } else {
+    span.record("otel.status_code", &"ERROR");
+  }
+
+  // pre-formatting errors is a workaround for https://github.com/tokio-rs/tracing/issues/1565
+  let display_error = format!("{}", response_error);
+  let debug_error = format!("{:?}", response_error);
+
+  tracing::info_span!(
+    parent: None,
+    "Error encountered while processing the incoming HTTP request"
+  )
+  .in_scope(|| {
+    if status_code.is_client_error() {
+      tracing::warn!("{}\n{}", display_error, debug_error);
+    } else {
+      tracing::error!("{}\n{}", display_error, debug_error);
+    }
+  });
+
+  span.record("exception.message", &tracing::field::display(display_error));
+  span.record("exception.details", &tracing::field::display(debug_error));
+}
-- 
2.44.1