fix(backtrace): Fix backtrace logging and stdout (#988)
* fix(backtrace): Fix backtrace logging and stdout - Add manual implementation for panic that logs backtrace to a file. - Remove all manual output to stdout. - Fix new clippy warnings from Rust 1.65. * Update docs Co-authored-by: Henrik Friedrichsen <henrik@affekt.org>
This commit is contained in:
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -30,9 +30,14 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
- Installed from: [e.g. AUR, brew, cargo]
|
- Installed from: [e.g. AUR, brew, cargo]
|
||||||
|
|
||||||
**Backtrace/Debug log**
|
**Backtrace/Debug log**
|
||||||
Instructions on how to capture debug logs: https://github.com/hrkfdn/ncspot#usage
|
Please attach a debug log and backtrace if ncspot has crashed.
|
||||||
|
|
||||||
To debug crashes a backtrace is very helpful. Make sure you run a debug build of ncspot, e.g. by running the command mentioned in the link above.
|
Instructions on how to capture debug logs: https://github.com/hrkfdn/ncspot#debugging
|
||||||
|
|
||||||
|
For backtraces, make sure you run a debug build of ncspot, e.g. by running the
|
||||||
|
command mentioned in the [compilation
|
||||||
|
instructions](https://github.com/hrkfdn/ncspot#compiling). You can find the
|
||||||
|
latest backtrace at `~/.cache/ncspot/backtrace.log`.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -37,6 +37,7 @@ You **must** have an existing premium Spotify subscription to use `ncspot`.
|
|||||||
- [On Linux](#on-linux)
|
- [On Linux](#on-linux)
|
||||||
- [Build](#build)
|
- [Build](#build)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Debugging](#debugging)
|
||||||
- [Compiling](#compiling)
|
- [Compiling](#compiling)
|
||||||
- [Building a Debian Package](#building-a-debian-package)
|
- [Building a Debian Package](#building-a-debian-package)
|
||||||
- [Audio Backends](#audio-backends)
|
- [Audio Backends](#audio-backends)
|
||||||
@@ -126,6 +127,16 @@ On Linux, you also need:
|
|||||||
sudo pacman -S dbus libpulse libxcb ncurses openssl
|
sudo pacman -S dbus libpulse libxcb ncurses openssl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
|
||||||
|
For debugging, you can pass a debug log filename:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
RUST_BACKTRACE=full cargo run -- -d debug.log
|
||||||
|
```
|
||||||
|
|
||||||
|
If ncspot has crashed you can find the latest backtrace at `~/.cache/ncspot/backtrace.log`.
|
||||||
|
|
||||||
### Compiling
|
### Compiling
|
||||||
|
|
||||||
Compile and install the latest release with `cargo-install`:
|
Compile and install the latest release with `cargo-install`:
|
||||||
@@ -144,12 +155,6 @@ cargo build --release
|
|||||||
**You may need to manually set the audio backend on non-Linux OSes.** See
|
**You may need to manually set the audio backend on non-Linux OSes.** See
|
||||||
[Audio Backends](#audio-backends).
|
[Audio Backends](#audio-backends).
|
||||||
|
|
||||||
For debugging, you can pass a debug log filename and pipe `stderr` to a file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
RUST_BACKTRACE=full cargo run -- -d debug.log 2> stderr.log
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Building a Debian Package
|
#### Building a Debian Package
|
||||||
|
|
||||||
You can also use `cargo-deb` to build a Debian package
|
You can also use `cargo-deb` to build a Debian package
|
||||||
|
|||||||
@@ -242,14 +242,24 @@ fn load(filename: &str) -> Result<ConfigValues, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn proj_dirs() -> AppDirs {
|
fn proj_dirs() -> AppDirs {
|
||||||
match *BASE_PATH.read().expect("can't readlock BASE_PATH") {
|
try_proj_dirs().unwrap()
|
||||||
Some(ref basepath) => AppDirs {
|
}
|
||||||
|
|
||||||
|
/// Returns the plaform app directories for ncspot if they could be determined,
|
||||||
|
/// or an error otherwise.
|
||||||
|
pub fn try_proj_dirs() -> Result<AppDirs, String> {
|
||||||
|
match *BASE_PATH
|
||||||
|
.read()
|
||||||
|
.map_err(|_| String::from("Poisoned RWLock"))?
|
||||||
|
{
|
||||||
|
Some(ref basepath) => Ok(AppDirs {
|
||||||
cache_dir: basepath.join(".cache"),
|
cache_dir: basepath.join(".cache"),
|
||||||
config_dir: basepath.join(".config"),
|
config_dir: basepath.join(".config"),
|
||||||
data_dir: basepath.join(".local/share"),
|
data_dir: basepath.join(".local/share"),
|
||||||
state_dir: basepath.join(".local/state"),
|
state_dir: basepath.join(".local/state"),
|
||||||
},
|
}),
|
||||||
None => AppDirs::new(Some("ncspot"), true).expect("can't determine project paths"),
|
None => AppDirs::new(Some("ncspot"), true)
|
||||||
|
.ok_or_else(|| String::from("Couldn't determine platform standard directories")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@@ -5,7 +5,9 @@ extern crate lazy_static;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
||||||
use std::fs;
|
use std::backtrace;
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -91,6 +93,25 @@ fn credentials_prompt(error_message: Option<String>) -> Result<Credentials, Stri
|
|||||||
authentication::create_credentials()
|
authentication::create_credentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn register_backtrace_panic_handler() {
|
||||||
|
// During most of the program, Cursive is responsible for drawing to the
|
||||||
|
// tty. Since stdout probably doesn't work as expected during a panic, the
|
||||||
|
// backtrace is written to a file at $USER_CACHE_DIR/ncspot/backtrace.log.
|
||||||
|
std::panic::set_hook(Box::new(|_panic_info| {
|
||||||
|
// A panic hook will prevent the default panic handler from being
|
||||||
|
// called. An unwrap in this part would cause a hard crash of ncspot.
|
||||||
|
// Don't unwrap/expect/panic in here!
|
||||||
|
if let Ok(backtrace_log) = config::try_proj_dirs() {
|
||||||
|
let mut path = backtrace_log.cache_dir;
|
||||||
|
path.push("backtrace.log");
|
||||||
|
if let Ok(mut file) = File::create(path) {
|
||||||
|
writeln!(file, "{}", backtrace::Backtrace::force_capture()).unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
type UserData = Arc<UserDataInner>;
|
type UserData = Arc<UserDataInner>;
|
||||||
struct UserDataInner {
|
struct UserDataInner {
|
||||||
pub cmd: CommandManager,
|
pub cmd: CommandManager,
|
||||||
@@ -98,6 +119,8 @@ struct UserDataInner {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), String> {
|
async fn main() -> Result<(), String> {
|
||||||
|
register_backtrace_panic_handler();
|
||||||
|
|
||||||
let backends = {
|
let backends = {
|
||||||
let backends: Vec<&str> = audio_backend::BACKENDS.iter().map(|b| b.0).collect();
|
let backends: Vec<&str> = audio_backend::BACKENDS.iter().map(|b| b.0).collect();
|
||||||
format!("Audio backends: {}", backends.join(", "))
|
format!("Audio backends: {}", backends.join(", "))
|
||||||
@@ -168,6 +191,7 @@ async fn main() -> Result<(), String> {
|
|||||||
credentials = credentials_prompt(Some(error_msg))?;
|
credentials = credentials_prompt(Some(error_msg))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DON'T USE STDOUT AFTER THIS CALL!
|
||||||
let backend = cursive::backends::try_default().map_err(|e| e.to_string())?;
|
let backend = cursive::backends::try_default().map_err(|e| e.to_string())?;
|
||||||
let buffered_backend = Box::new(cursive_buffered_backend::BufferedBackend::new(backend));
|
let buffered_backend = Box::new(cursive_buffered_backend::BufferedBackend::new(backend));
|
||||||
|
|
||||||
@@ -179,7 +203,6 @@ async fn main() -> Result<(), String> {
|
|||||||
|
|
||||||
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
||||||
|
|
||||||
println!("Connecting to Spotify..");
|
|
||||||
let spotify = spotify::Spotify::new(event_manager.clone(), credentials, cfg.clone());
|
let spotify = spotify::Spotify::new(event_manager.clone(), credentials, cfg.clone());
|
||||||
|
|
||||||
let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone()));
|
let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone()));
|
||||||
@@ -317,7 +340,7 @@ async fn main() -> Result<(), String> {
|
|||||||
cursive.add_fullscreen_layer(layout.with_name("main"));
|
cursive.add_fullscreen_layer(layout.with_name("main"));
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let mut signals = Signals::new(&[SIGTERM, SIGHUP]).expect("could not register signal handler");
|
let mut signals = Signals::new([SIGTERM, SIGHUP]).expect("could not register signal handler");
|
||||||
|
|
||||||
// cursive event loop
|
// cursive event loop
|
||||||
while cursive.is_running() {
|
while cursive.is_running() {
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ pub trait Serializer {
|
|||||||
// Nothing exists so just write the default and return it
|
// Nothing exists so just write the default and return it
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
let value = default()?;
|
let value = default()?;
|
||||||
return self.write(&path, value);
|
return self.write(path, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = self.load(&path);
|
let result = self.load(path);
|
||||||
if default_on_parse_failure && result.is_err() {
|
if default_on_parse_failure && result.is_err() {
|
||||||
let value = default()?;
|
let value = default()?;
|
||||||
return self.write(&path, value);
|
return self.write(path, value);
|
||||||
}
|
}
|
||||||
result.map_err(|e| format!("Unable to parse {}: {}", path.to_string_lossy(), e))
|
result.map_err(|e| format!("Unable to parse {}: {}", path.to_string_lossy(), e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ impl View for Layout {
|
|||||||
let result = self.get_result();
|
let result = self.get_result();
|
||||||
|
|
||||||
let cmdline_visible = self.cmdline.get_content().len() > 0;
|
let cmdline_visible = self.cmdline.get_content().len() > 0;
|
||||||
let mut cmdline_height = if cmdline_visible { 1 } else { 0 };
|
let mut cmdline_height = usize::from(cmdline_visible);
|
||||||
if result.as_ref().map(Option::is_some).unwrap_or(true) {
|
if result.as_ref().map(Option::is_some).unwrap_or(true) {
|
||||||
cmdline_height += 1;
|
cmdline_height += 1;
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ impl View for Layout {
|
|||||||
let result = self.get_result();
|
let result = self.get_result();
|
||||||
|
|
||||||
let cmdline_visible = self.cmdline.get_content().len() > 0;
|
let cmdline_visible = self.cmdline.get_content().len() > 0;
|
||||||
let mut cmdline_height = if cmdline_visible { 1 } else { 0 };
|
let mut cmdline_height = usize::from(cmdline_visible);
|
||||||
if result.as_ref().map(Option::is_some).unwrap_or(true) {
|
if result.as_ref().map(Option::is_some).unwrap_or(true) {
|
||||||
cmdline_height += 1;
|
cmdline_height += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user