An error from .join() means that the child thread panicked. Before returning the error, the child thread will also call the panic handler, which by default prints the panic message to stderr.
If you don't have access to stderr because of where your program is running, you could use set_hook to install a panic handler that logs its information elsewhere instead.
Or the parent thread could cast the Box<dyn Any> to a string like this:
if let Err(err) = std::thread::spawn(...).join() {
let msg = match err.downcast_ref::<&'static str>() {
Some(s) => *s,
None => match err.downcast_ref::<String>() {
Some(s) => &s[..],
None => "Sorry, unknown payload type",
},
};
// ... log the message here ...
}