log: info level into file, crash report for android

This commit is contained in:
ardocrat
2026-03-24 13:18:04 +03:00
parent 7bbe637414
commit 6b05a2177e
8 changed files with 189 additions and 72 deletions
Generated
+2
View File
@@ -4009,6 +4009,7 @@ dependencies = [
"arti-client",
"async-std",
"backtrace",
"built",
"bytes 1.11.1",
"chrono",
"curve25519-dalek 4.1.3",
@@ -4048,6 +4049,7 @@ dependencies = [
"lazy_static",
"local-ip-address",
"log",
"log4rs",
"nokhwa",
"num-bigint 0.4.6",
"parking_lot 0.12.5",
+5 -1
View File
@@ -52,6 +52,7 @@ egui-async = "0.3.4"
rust-i18n = "3.1.5"
## other
log4rs = "1.4.0"
anyhow = "1.0.97"
pin-project = "1.1.10"
backtrace = "0.3.76"
@@ -136,4 +137,7 @@ android_logger = "0.15.0"
jni = "0.21.1"
android-activity = { version = "0.6.0", features = ["game-activity"] }
winit = { version = "0.30.12", features = ["android-game-activity"] }
eframe = { version = "0.33.2", default-features = false, features = ["glow", "android-game-activity"] }
eframe = { version = "0.33.2", default-features = false, features = ["glow", "android-game-activity"] }
[build-dependencies]
built = "0.8.0"
+2
View File
@@ -2,6 +2,8 @@ use std::process::Command;
use std::{env, fs};
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
let out_dir = env::var("OUT_DIR").unwrap();
let tor_out_dir = format!("{}/tor", out_dir);
let mut webtunnel_file = format!("{}/webtunnel", tor_out_dir);
+6 -6
View File
@@ -117,7 +117,7 @@ impl ContentContainer for Content {
if self.first_draw {
// Show crash report or integrated node Android warning.
if Settings::crash_report_path().exists() {
if Settings::crash_check_path().exists() {
Modal::new(CRASH_REPORT_MODAL)
.closeable(false)
.position(ModalPosition::Center)
@@ -276,14 +276,14 @@ impl Content {
.size(16.0)
.color(Colors::text(false)));
ui.add_space(6.0);
// Draw button to share crash report.
// Draw button to share log file.
let text = format!("{} {}", FILE_X, t!("share"));
View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || {
if let Ok(data) = fs::read_to_string(Settings::crash_report_path()) {
let name = Settings::CRASH_REPORT_FILE_NAME.to_string();
if let Ok(data) = fs::read_to_string(Settings::log_path()) {
let name = Settings::LOG_FILE_NAME.to_string();
let _ = cb.share_data(name, data.as_bytes().to_vec());
}
Settings::delete_crash_report();
Settings::delete_crash_check();
Modal::close();
});
});
@@ -292,7 +292,7 @@ impl Content {
ui.add_space(8.0);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
Settings::delete_crash_report();
Settings::delete_crash_check();
Modal::close();
});
});
+5 -9
View File
@@ -19,8 +19,8 @@ rust_i18n::i18n!("locales");
use eframe::NativeOptions;
use egui::{Context, Stroke, Theme};
use lazy_static::lazy_static;
use std::sync::Arc;
use parking_lot::RwLock;
use std::sync::Arc;
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
@@ -28,9 +28,9 @@ use winit::platform::android::activity::AndroidApp;
pub use settings::AppConfig;
pub use settings::Settings;
use crate::gui::{Colors, App};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::{App, Colors};
use crate::node::Node;
mod node;
@@ -39,6 +39,7 @@ mod tor;
mod settings;
mod http;
pub mod gui;
pub mod logger;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -47,13 +48,8 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[cfg(target_os = "android")]
#[no_mangle]
fn android_main(app: AndroidApp) {
{
std::env::set_var("RUST_BACKTRACE", "full");
let log_config = android_logger::Config::default()
.with_max_level(log::LevelFilter::Info)
.with_tag("grim");
android_logger::init_once(log_config);
}
// Setup logger.
logger::init_logger();
use gui::platform::Android;
let platform = Android::new(app.clone());
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2026 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{panic, thread};
use std::fs::File;
use backtrace::Backtrace;
use log4rs::append::Append;
use log4rs::append::console::ConsoleAppender;
use log4rs::append::rolling_file::policy::compound::CompoundPolicy;
use log4rs::append::rolling_file::policy::compound::roll::fixed_window::FixedWindowRoller;
use log4rs::append::rolling_file::policy::compound::trigger::size::SizeTrigger;
use log4rs::append::rolling_file::RollingFileAppender;
use log4rs::Config;
use log4rs::config::{Appender, Root};
use log4rs::encode::pattern::PatternEncoder;
use log4rs::filter::threshold::ThresholdFilter;
use log::{error, LevelFilter};
use crate::Settings;
const LOGGING_PATTERN: &str = "{d(%Y%m%d %H:%M:%S%.3f)} {h({l})} {M} - {m}{n}";
/// 32 log files to rotate over by default.
const ROTATE_LOG_FILES: u32 = 32;
/// Size of the log in bytes to rotate over (6 megabytes).
const MAX_FILE_SIZE: u64 = 1024 * 1024 * 6;
/// Include build information.
pub mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
/// Initialize the logger.
pub fn init_logger() {
let stdout = ConsoleAppender::builder()
.encoder(Box::new(PatternEncoder::new(&LOGGING_PATTERN)))
.build();
let mut root = Root::builder();
let mut app = vec![];
app.push(
Appender::builder()
.filter(Box::new(ThresholdFilter::new(LevelFilter::Info)))
.build("stdout", Box::new(stdout)),
);
root = root.appender("stdout");
// Setup file logging.
let filter = Box::new(ThresholdFilter::new(LevelFilter::Info));
let file: Box<dyn Append> = {
let path = Settings::log_path();
let roller = FixedWindowRoller::builder()
.build(&format!("{}.{{}}.gz", path), ROTATE_LOG_FILES)
.unwrap();
let trigger = SizeTrigger::new(MAX_FILE_SIZE);
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
Box::new(
RollingFileAppender::builder()
.append(true)
.encoder(Box::new(PatternEncoder::new(&LOGGING_PATTERN)))
.build(path, Box::new(policy))
.expect("Failed to create logfile"),
)
};
app.push(
Appender::builder()
.filter(filter)
.build("file", file),
);
root = root.appender("file");
let config = Config::builder()
.appenders(app)
.build(root.build(LevelFilter::Info))
.unwrap();
let _ = log4rs::init_config(config).unwrap();
log::info!("{}", build_info());
send_panic_to_log();
}
/// Get information about application build.
fn build_info() -> String {
format!(
"This is Grim version {}, built for {} by {}.",
built_info::PKG_VERSION,
built_info::TARGET,
built_info::RUSTC_VERSION,
)
}
/// Hook to send panics to logs as well as stderr.
fn send_panic_to_log() {
panic::set_hook(Box::new(|info| {
let backtrace = Backtrace::new();
let thread = thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
match info.location() {
Some(location) => {
error!(
"{}\nThread '{}' panicked at '{}': {}:{}{:?}\n\n",
build_info(),
thread,
msg,
location.file(),
location.line(),
backtrace
);
}
None => error!("Thread '{}' panicked at '{}'{:?}", thread, msg, backtrace),
}
// Also print to stderr.
eprintln!(
"Thread '{}' panicked with message:\n\"{}\"\nSee {} for further details.",
thread, msg, Settings::log_path()
);
// Create file to show report send on launch.
let log = Settings::crash_check_path();
let _ = File::create(log);
}));
}
+10 -48
View File
@@ -23,11 +23,8 @@ pub fn main() {
#[allow(dead_code)]
#[cfg(not(target_os = "android"))]
fn real_main() {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.parse_default_env()
.init();
// Initialize logger.
grim::logger::init_logger();
// Handle file path argument passing.
let args: Vec<_> = std::env::args().collect();
let mut data = None;
@@ -40,50 +37,15 @@ fn real_main() {
data = content
}
// Setup callback on panic crash.
std::panic::set_hook(Box::new(|info| {
// Format error.
let backtrace = backtrace::Backtrace::new();
let time = grim::gui::views::View::format_time(chrono::Utc::now().timestamp());
let os = egui::os::OperatingSystem::from_target_os();
let ver = grim::VERSION;
let msg = panic_info_message(info);
let loc = if let Some(location) = info.location() {
format!("{}:{}:{}", location.file(), location.line(), location.column())
} else {
"no location found.".parse().unwrap()
};
let err = format!("{} - {:?} - v{}\n{}\n{}\n\n{:?}", time, os, ver, msg, loc, backtrace);
// Save backtrace to file.
let log = grim::Settings::crash_report_path();
if log.exists() {
use std::io::{Seek, SeekFrom, Write};
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(log)
.unwrap();
if file.seek(SeekFrom::End(0)).is_ok() {
file.write(err.as_bytes()).unwrap_or_default();
}
} else {
std::fs::write(log, err.as_bytes()).unwrap_or_default();
}
// Print message error.
println!("{}\n{}", msg, loc);
}));
// Start GUI.
let _ = std::panic::catch_unwind(|| {
if is_app_running(&data) {
return;
} else if let Some(data) = data {
grim::on_data(data);
}
let platform = grim::gui::platform::Desktop::new();
start_app_socket(platform.clone());
start_desktop_gui(platform);
});
if is_app_running(&data) {
return;
} else if let Some(data) = data {
grim::on_data(data);
}
let platform = grim::gui::platform::Desktop::new();
start_app_socket(platform.clone());
start_desktop_gui(platform);
}
/// Get panic message from crash payload.
+15 -8
View File
@@ -48,10 +48,10 @@ pub struct Settings {
impl Settings {
/// Main application directory name.
pub const MAIN_DIR_NAME: &'static str = ".grim";
/// Crash report file name.
pub const CRASH_REPORT_FILE_NAME: &'static str = "crash.log";
/// Application socket name.
pub const SOCKET_NAME: &'static str = "grim.sock";
/// Log file name.
pub const LOG_FILE_NAME: &'static str = "grim.log";
/// Initialize settings with app and node configs.
fn init() -> Self {
@@ -148,6 +148,13 @@ impl Settings {
path
}
/// Get log file path.
pub fn log_path() -> String {
let mut log_path = Self::base_path(None);
log_path.push(Self::LOG_FILE_NAME);
log_path.to_str().unwrap().into()
}
/// Get desktop application socket path.
pub fn socket_path() -> PathBuf {
let mut socket_path = Self::base_path(None);
@@ -162,16 +169,16 @@ impl Settings {
path
}
/// Get configuration file path from provided name and subdirectory if needed.
pub fn crash_report_path() -> PathBuf {
/// Get path of file created when application crashed.
pub fn crash_check_path() -> PathBuf {
let mut path = Self::base_path(None);
path.push(Self::CRASH_REPORT_FILE_NAME);
path.push("crashed");
path
}
/// Delete crash report file.
pub fn delete_crash_report() {
let log = Self::crash_report_path();
/// Delete crash file.
pub fn delete_crash_check() {
let log = Self::crash_check_path();
if log.exists() {
let _ = fs::remove_file(log.clone());
}