summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Duerr <chrisduerr@users.noreply.github.com>2018-11-17 14:39:13 +0000
committerGitHub <noreply@github.com>2018-11-17 14:39:13 +0000
commit6265ef91d5496461fcd1025b21323982f3e47085 (patch)
treed8ab17aac3c2833d5bb231a5346504c227a242f1 /src
parentea637cde1c82ad971078671aff087236f3fb0fbd (diff)
downloadalacritty-6265ef91d5496461fcd1025b21323982f3e47085.tar.gz
alacritty-6265ef91d5496461fcd1025b21323982f3e47085.zip
Display errors and warnings
To make sure that all error and information reporting to the user is unified, all instances of `print!`, `eprint!`, `println!` and `eprintln!` have been removed and replaced by logging. When `RUST_LOG` is not specified, the default Alacritty logger now also prints to both the stderr and a log file. The log file is only created when a message is written to it and its name is printed to stdout the first time it is used. Whenever a warning or an error has been written to the log file/stderr, a message is now displayed in Alacritty which points to the log file where the full error is documented. The message is cleared whenever the screen is cleared using either the `clear` command or the `Ctrl+L` key binding. To make sure that log files created by root don't prevent normal users from interacting with them, the Alacritty log file is `/tmp/Alacritty-$PID.log`. Since it's still possible that the log file can't be created, the UI error/warning message now informs the user if the message was only written to stderr. The reason why it couldn't be created is then printed to stderr. To make sure the deletion of the log file at runtime doesn't create any issues, the file is re-created if a write is attempted without the file being present. To help with debugging Alacritty issues, a timestamp and the error level are printed in all log messages. All log messages now follow this format: [YYYY-MM-DD HH:MM] [LEVEL] Message Since it's not unusual to spawn a lot of different terminal emulators without restarting, Alacritty can create a ton of different log files. To combat this problem, logfiles are removed by default after Alacritty has been closed. If the user wants to persist the log of a single session, the `--persistent_logging` option can be used. For persisting all log files, the `persistent_logging` option can be set in the configuration file
Diffstat (limited to 'src')
-rw-r--r--src/ansi.rs6
-rw-r--r--src/cli.rs13
-rw-r--r--src/config.rs87
-rw-r--r--src/display.rs45
-rw-r--r--src/event.rs2
-rw-r--r--src/grid/tests.rs12
-rw-r--r--src/input.rs4
-rw-r--r--src/lib.rs1
-rw-r--r--src/logging.rs203
-rw-r--r--src/macros.rs28
-rw-r--r--src/main.rs35
-rw-r--r--src/renderer/mod.rs14
-rw-r--r--src/term/mod.rs14
13 files changed, 338 insertions, 126 deletions
diff --git a/src/ansi.rs b/src/ansi.rs
index f1ca759a..2171f868 100644
--- a/src/ansi.rs
+++ b/src/ansi.rs
@@ -60,7 +60,7 @@ fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
let r = parse_hex!();
let val = next!();
- if val != Some('/') { println!("val={:?}", val); return None; }
+ if val != Some('/') { return None; }
let g = parse_hex!();
if next!() != Some('/') { return None; }
let b = parse_hex!();
@@ -901,7 +901,7 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
macro_rules! unhandled {
() => {{
- warn!("[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}",
+ debug!("[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}",
action, args, intermediates);
return;
}}
@@ -1137,7 +1137,7 @@ impl<'a, H, W> vte::Perform for Performer<'a, H, W>
) {
macro_rules! unhandled {
() => {{
- warn!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
+ debug!("[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})",
params, intermediates, byte as char, byte);
return;
}}
diff --git a/src/cli.rs b/src/cli.rs
index 0a36179a..99728780 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -31,6 +31,7 @@ pub struct Options {
pub command: Option<Shell<'static>>,
pub working_dir: Option<PathBuf>,
pub config: Option<PathBuf>,
+ pub persistent_logging: bool,
}
impl Default for Options {
@@ -46,6 +47,7 @@ impl Default for Options {
command: None,
working_dir: None,
config: None,
+ persistent_logging: false,
}
}
}
@@ -71,6 +73,9 @@ impl Options {
.conflicts_with("live-config-reload"))
.arg(Arg::with_name("print-events")
.long("print-events"))
+ .arg(Arg::with_name("persistent-logging")
+ .long("persistent-logging")
+ .help("Keep the log file after quitting Alacritty"))
.arg(Arg::with_name("dimensions")
.long("dimensions")
.short("d")
@@ -129,6 +134,10 @@ impl Options {
options.live_config_reload = Some(false);
}
+ if matches.is_present("persistent-logging") {
+ options.persistent_logging = true;
+ }
+
if let Some(mut dimensions) = matches.values_of("dimensions") {
let width = dimensions.next().map(|w| w.parse().map(Column));
let height = dimensions.next().map(|h| h.parse().map(Line));
@@ -147,8 +156,8 @@ impl Options {
}
match matches.occurrences_of("v") {
- 0 => {},
- 1 => options.log_level = log::LevelFilter::Info,
+ 0 if !options.print_events => {},
+ 0 | 1 => options.log_level = log::LevelFilter::Info,
2 => options.log_level = log::LevelFilter::Debug,
3 | _ => options.log_level = log::LevelFilter::Trace
}
diff --git a/src/config.rs b/src/config.rs
index ee30c085..ae1ec897 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -73,7 +73,7 @@ fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Dura
match u64::deserialize(deserializer) {
Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)),
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(default_threshold_ms())
},
}
@@ -172,7 +172,7 @@ fn deserialize_visual_bell_duration<'a, D>(deserializer: D) -> ::std::result::Re
match u16::deserialize(deserializer) {
Ok(duration) => Ok(duration),
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(default_visual_bell_duration())
},
}
@@ -310,19 +310,19 @@ impl<'de> Deserialize<'de> for Decorations {
"none" => Ok(Decorations::None),
"full" => Ok(Decorations::Full),
"true" => {
- eprintln!("deprecated decorations boolean value, \
+ error!("deprecated decorations boolean value, \
use one of transparent|buttonless|none|full instead; \
Falling back to \"full\"");
Ok(Decorations::Full)
},
"false" => {
- eprintln!("deprecated decorations boolean value, \
+ error!("deprecated decorations boolean value, \
use one of transparent|buttonless|none|full instead; \
Falling back to \"none\"");
Ok(Decorations::None)
},
_ => {
- eprintln!("invalid decorations value: {}; Using default value", value);
+ error!("invalid decorations value: {}; Using default value", value);
Ok(Decorations::Full)
}
}
@@ -336,23 +336,23 @@ impl<'de> Deserialize<'de> for Decorations {
"none" => Ok(Decorations::None),
"full" => Ok(Decorations::Full),
"true" => {
- eprintln!("deprecated decorations boolean value, \
+ error!("deprecated decorations boolean value, \
use one of none|full instead; \
Falling back to \"full\"");
Ok(Decorations::Full)
},
"false" => {
- eprintln!("deprecated decorations boolean value, \
+ error!("deprecated decorations boolean value, \
use one of none|full instead; \
Falling back to \"none\"");
Ok(Decorations::None)
},
"transparent" | "buttonless" => {
- eprintln!("macos-only decorations value: {}; Using default value", value);
+ error!("macos-only decorations value: {}; Using default value", value);
Ok(Decorations::Full)
},
_ => {
- eprintln!("invalid decorations value: {}; Using default value", value);
+ error!("invalid decorations value: {}; Using default value", value);
Ok(Decorations::Full)
}
}
@@ -392,7 +392,7 @@ fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8
match Delta::deserialize(deserializer) {
Ok(delta) => Ok(delta),
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(default_padding())
},
}
@@ -503,6 +503,10 @@ pub struct Config {
#[serde(default, deserialize_with="failure_default")]
cursor: Cursor,
+ /// Keep the log file after quitting
+ #[serde(default, deserialize_with="failure_default")]
+ persistent_logging: bool,
+
// TODO: DEPRECATED
#[serde(default, deserialize_with = "failure_default")]
custom_cursor_colors: Option<bool>,
@@ -528,7 +532,7 @@ fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T
let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) {
Ok(vec) => vec,
Err(err) => {
- eprintln!("problem with config: {}; Using empty vector", err);
+ error!("problem with config: {}; Using empty vector", err);
return Ok(Vec::new());
},
};
@@ -539,7 +543,7 @@ fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T
match T::deserialize(value) {
Ok(binding) => bindings.push(binding),
Err(err) => {
- eprintln!("problem with config: {}; Skipping value", err);
+ error!("problem with config: {}; Skipping value", err);
},
}
}
@@ -557,7 +561,7 @@ fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize,
match usize::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
- eprintln!("problem with config: {}; Using `8`", err);
+ error!("problem with config: {}; Using `8`", err);
Ok(default_tabspaces())
},
}
@@ -569,7 +573,7 @@ fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::E
match bool::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
- eprintln!("problem with config: {}; Using `true`", err);
+ error!("problem with config: {}; Using `true`", err);
Ok(true)
},
}
@@ -583,7 +587,7 @@ fn failure_default<'a, D, T>(deserializer: D)
match T::deserialize(deserializer) {
Ok(value) => Ok(value),
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(T::default())
},
}
@@ -645,7 +649,7 @@ fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Resul
match u32::deserialize(deserializer) {
Ok(lines) => {
if lines > MAX_SCROLLBACK_LINES {
- eprintln!(
+ error!(
"problem with config: scrollback size is {}, but expected a maximum of {}; \
Using {1} instead",
lines, MAX_SCROLLBACK_LINES,
@@ -656,7 +660,7 @@ fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Resul
}
},
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(default_scrolling_history())
},
}
@@ -668,7 +672,7 @@ fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Re
match u8::deserialize(deserializer) {
Ok(lines) => Ok(lines),
Err(err) => {
- eprintln!("problem with config: {}; Using default value", err);
+ error!("problem with config: {}; Using default value", err);
Ok(default_scrolling_multiplier())
},
}
@@ -710,7 +714,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper {
"Shift" => res.shift = true,
"Alt" | "Option" => res.alt = true,
"Control" => res.ctrl = true,
- _ => eprintln!("unknown modifier {:?}", modifier),
+ _ => error!("unknown modifier {:?}", modifier),
}
}
@@ -831,7 +835,7 @@ impl<'a> de::Deserialize<'a> for ModeWrapper {
"~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR,
"AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD,
"~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD,
- _ => eprintln!("unknown mode {:?}", modifier),
+ _ => error!("unknown mode {:?}", modifier),
}
}
@@ -1193,7 +1197,7 @@ fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8,
match u8::deserialize(deserializer) {
Ok(index) => {
if index < 16 {
- eprintln!(
+ error!(
"problem with config: indexed_color's index is '{}', \
but a value bigger than 15 was expected; \
Ignoring setting",
@@ -1207,7 +1211,7 @@ fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8,
}
},
Err(err) => {
- eprintln!("problem with config: {}; Ignoring setting", err);
+ error!("problem with config: {}; Ignoring setting", err);
// Return value out of range to ignore this color
Ok(0)
@@ -1262,7 +1266,7 @@ fn deserialize_optional_color<'a, D>(deserializer: D) -> ::std::result::Result<O
},
Ok(None) => Ok(None),
Err(err) => {
- eprintln!("problem with config: {}; Using standard foreground color", err);
+ error!("problem with config: {}; Using standard foreground color", err);
Ok(None)
},
}
@@ -1361,7 +1365,7 @@ fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error>
match rgb {
Ok(rgb) => Ok(rgb),
Err(err) => {
- eprintln!("problem with config: {}; Using color #ff00ff", err);
+ error!("problem with config: {}; Using color #ff00ff", err);
Ok(Rgb { r: 255, g: 0, b: 255 })
},
}
@@ -1671,6 +1675,12 @@ impl Config {
self.scrolling.history = history;
}
+ /// Keep the log file after quitting Alacritty
+ #[inline]
+ pub fn persistent_logging(&self) -> bool {
+ self.persistent_logging
+ }
+
pub fn load_from<P: Into<PathBuf>>(path: P) -> Result<Config> {
let path = path.into();
let raw = Config::read_file(path.as_path())?;
@@ -1701,24 +1711,22 @@ impl Config {
}
fn print_deprecation_warnings(&mut self) {
- use ::util::fmt;
if self.dimensions.is_some() {
- eprintln!("{}", fmt::Yellow("Config `dimensions` is deprecated. \
- Please use `window.dimensions` instead."));
+ warn!("{}", "Config `dimensions` is deprecated. \
+ Please use `window.dimensions` instead.");
}
if self.padding.is_some() {
- eprintln!("{}", fmt::Yellow("Config `padding` is deprecated. \
- Please use `window.padding` instead."));
+ warn!("{}", "Config `padding` is deprecated. Please use `window.padding` instead.");
}
if self.mouse.faux_scrollback_lines.is_some() {
- println!("{}", fmt::Yellow("Config `mouse.faux_scrollback_lines` is deprecated. \
- Please use `mouse.faux_scrolling_lines` instead."));
+ warn!("{}", "Config `mouse.faux_scrollback_lines` is deprecated. \
+ Please use `mouse.faux_scrolling_lines` instead.");
}
if let Some(custom_cursor_colors) = self.custom_cursor_colors {
- eprintln!("{}", fmt::Yellow("Config `custom_cursor_colors` is deprecated."));
+ warn!("{}", "Config `custom_cursor_colors` is deprecated.");
if !custom_cursor_colors {
self.colors.cursor.cursor = None;
@@ -1727,18 +1735,17 @@ impl Config {
}
if self.cursor_style.is_some() {
- eprintln!("{}", fmt::Yellow("Config `cursor_style` is deprecated. \
- Please use `cursor.style` instead."));
+ warn!("{}", "Config `cursor_style` is deprecated. Please use `cursor.style` instead.");
}
if self.hide_cursor_when_typing.is_some() {
- eprintln!("{}", fmt::Yellow("Config `hide_cursor_when_typing` is deprecated. \
- Please use `mouse.hide_when_typing` instead."));
+ warn!("{}", "Config `hide_cursor_when_typing` is deprecated. \
+ Please use `mouse.hide_when_typing` instead.");
}
if self.unfocused_hollow_cursor.is_some() {
- eprintln!("{}", fmt::Yellow("Config `unfocused_hollow_cursor` is deprecated. \
- Please use `cursor.unfocused_hollow` instead."));
+ warn!("{}", "Config `unfocused_hollow_cursor` is deprecated. \
+ Please use `cursor.unfocused_hollow` instead.");
}
}
}
@@ -1839,7 +1846,7 @@ impl DeserializeSize for Size {
match size {
Ok(size) => Ok(size),
Err(err) => {
- eprintln!("problem with config: {}; Using size 12", err);
+ error!("problem with config: {}; Using size 12", err);
Ok(Size::new(12.))
},
}
@@ -2062,7 +2069,7 @@ impl Monitor {
let _ = config_tx.send(config);
handler.on_config_reload();
},
- Err(err) => eprintln!("Ignoring invalid config: {}", err),
+ Err(err) => error!("Ignoring invalid config: {}", err),
}
}
}
diff --git a/src/display.rs b/src/display.rs
index 1a145185..12842649 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -28,6 +28,7 @@ use renderer::{self, GlyphCache, QuadRenderer};
use term::{Term, SizeInfo, RenderableCell};
use sync::FairMutex;
use window::{self, Window};
+use logging::LoggerProxy;
use Rgb;
#[derive(Debug)]
@@ -99,6 +100,7 @@ pub struct Display {
meter: Meter,
font_size: font::Size,
size_info: SizeInfo,
+ logger_proxy: LoggerProxy,
}
/// Can wakeup the render loop from other threads
@@ -129,7 +131,11 @@ impl Display {
&self.size_info
}
- pub fn new(config: &Config, options: &cli::Options) -> Result<Display, Error> {
+ pub fn new(
+ config: &Config,
+ options: &cli::Options,
+ logger_proxy: LoggerProxy
+ ) -> Result<Display, Error> {
// Extract some properties from config
let render_timer = config.render_timer();
@@ -218,6 +224,7 @@ impl Display {
meter: Meter::new(),
font_size: font::Size::new(0.),
size_info,
+ logger_proxy,
})
}
@@ -420,10 +427,38 @@ impl Display {
g: 0x4e,
b: 0x53,
};
- self.renderer
- .with_api(config, &size_info, visual_bell_intensity, |mut api| {
- api.render_string(&timing[..], glyph_cache, color);
- });
+ self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| {
+ api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, color);
+ });
+ }
+
+ // Display errors and warnings
+ if self.logger_proxy.errors() {
+ let msg = match self.logger_proxy.log_path() {
+ Some(path) => format!(" ERROR! See log at {} ", path),
+ None => " ERROR! See log in stderr ".into(),
+ };
+ let color = Rgb {
+ r: 0xff,
+ g: 0x00,
+ b: 0x00,
+ };
+ self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| {
+ api.render_string(&msg, size_info.lines() - 1, glyph_cache, color);
+ });
+ } else if self.logger_proxy.warnings() {
+ let msg = match self.logger_proxy.log_path() {
+ Some(path) => format!(" WARNING! See log at {} ", path),
+ None => " WARNING! See log in stderr ".into(),
+ };
+ let color = Rgb {
+ r: 0xff,
+ g: 0xff,
+ b: 0x00,
+ };
+ self.renderer.with_api(config, &size_info, visual_bell_intensity, |mut api| {
+ api.render_string(&msg, size_info.lines() - 1, glyph_cache, color);
+ });
}
}
diff --git a/src/event.rs b/src/event.rs
index 7a1faa70..ece8ec6f 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -488,7 +488,7 @@ impl<N: Notify> Processor<N> {
let hide_mouse = &mut self.hide_mouse;
let mut process = |event| {
if print_events {
- println!("glutin event: {:?}", event);
+ info!("glutin event: {:?}", event);
}
Processor::handle_event(
&mut processor,
diff --git a/src/grid/tests.rs b/src/grid/tests.rs
index e136e3b3..9cb30a7f 100644
--- a/src/grid/tests.rs
+++ b/src/grid/tests.rs
@@ -20,19 +20,13 @@ use index::{Point, Line, Column};
// Scroll up moves lines upwards
#[test]
fn scroll_up() {
- println!();
-
let mut grid = Grid::new(Line(10), Column(1), 0, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
}
- println!("grid: {:?}", grid);
-
grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0);
- println!("grid: {:?}", grid);
-
assert_eq!(grid[Line(0)][Column(0)], 2);
assert_eq!(grid[Line(0)].occ, 1);
assert_eq!(grid[Line(1)][Column(0)], 3);
@@ -58,19 +52,13 @@ fn scroll_up() {
// Scroll down moves lines downwards
#[test]
fn scroll_down() {
- println!();
-
let mut grid = Grid::new(Line(10), Column(1), 0, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
}
- println!("grid: {:?}", grid);
-
grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0);
- println!("grid: {:?}", grid);
-
assert_eq!(grid[Line(0)][Column(0)], 0); // was 8
assert_eq!(grid[Line(0)].occ, 0);
assert_eq!(grid[Line(1)][Column(0)], 0); // was 9
diff --git a/src/input.rs b/src/input.rs
index 600a1abe..d2f8f6b5 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -218,7 +218,7 @@ impl Action {
.and_then(|clipboard| clipboard.load_primary() )
.map(|contents| { self.paste(ctx, &contents) })
.unwrap_or_else(|err| {
- eprintln!("Error loading data from clipboard. {}", Red(err));
+ error!("Error loading data from clipboard. {}", Red(err));
});
},
Action::PasteSelection => {
@@ -228,7 +228,7 @@ impl Action {
.and_then(|clipboard| clipboard.load_selection() )
.map(|contents| { self.paste(ctx, &contents) })
.unwrap_or_else(|err| {
- warn!("Error loading data from clipboard. {}", Red(err));
+ error!("Error loading data from clipboard. {}", Red(err));
});
}
},
diff --git a/src/lib.rs b/src/lib.rs
index 1d393d1a..7ba3538b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -64,6 +64,7 @@ extern crate xdg;
extern crate base64;
extern crate terminfo;
extern crate url;
+extern crate time;
#[macro_use]
pub mod macros;
diff --git a/src/logging.rs b/src/logging.rs
index 10929980..5ad1dcd5 100644
--- a/src/logging.rs
+++ b/src/logging.rs
@@ -17,37 +17,134 @@
//! The main executable is supposed to call `initialize()` exactly once during
//! startup. All logging messages are written to stdout, given that their
//! log-level is sufficient for the level configured in `cli::Options`.
-use log;
-use std::sync;
-use std::io;
use cli;
+use log::{self, Level};
+use time;
-pub struct Logger<T> {
+use std::env;
+use std::fs::{self, File, OpenOptions};
+use std::io::{self, LineWriter, Stdout, Write};
+use std::path::PathBuf;
+use std::process;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+
+pub fn initialize(options: &cli::Options) -> Result<LoggerProxy, log::SetLoggerError> {
+ // Use env_logger if RUST_LOG environment variable is defined. Otherwise,
+ // use the alacritty-only logger.
+ if ::std::env::var("RUST_LOG").is_ok() {
+ ::env_logger::try_init()?;
+ Ok(LoggerProxy::default())
+ } else {
+ let logger = Logger::new(options.log_level);
+ let proxy = logger.proxy();
+
+ log::set_boxed_logger(Box::new(logger))?;
+
+ Ok(proxy)
+ }
+}
+
+/// Proxy object for bidirectional communicating with the global logger.
+#[derive(Clone, Default)]
+pub struct LoggerProxy {
+ errors: Arc<AtomicBool>,
+ warnings: Arc<AtomicBool>,
+ logfile_proxy: OnDemandLogFileProxy,
+}
+
+impl LoggerProxy {
+ /// Check for new logged errors.
+ pub fn errors(&self) -> bool {
+ self.errors.load(Ordering::Relaxed)
+ }
+
+ /// Check for new logged warnings.
+ pub fn warnings(&self) -> bool {
+ self.warnings.load(Ordering::Relaxed)
+ }
+
+ /// Get the path of the log file if it has been created.
+ pub fn log_path(&self) -> Option<&str> {
+ if self.logfile_proxy.created.load(Ordering::Relaxed) {
+ Some(&self.logfile_proxy.path)
+ } else {
+ None
+ }
+ }
+
+ /// Clear log warnings/errors from the Alacritty UI.
+ pub fn clear(&mut self) {
+ self.errors.store(false, Ordering::Relaxed);
+ self.warnings.store(false, Ordering::Relaxed);
+ }
+
+ pub fn delete_log(&mut self) {
+ self.logfile_proxy.delete_log();
+ }
+}
+
+struct Logger {
level: log::LevelFilter,
- output: sync::Mutex<T>
+ logfile: Mutex<OnDemandLogFile>,
+ stdout: Mutex<LineWriter<Stdout>>,
+ errors: Arc<AtomicBool>,
+ warnings: Arc<AtomicBool>,
}
-impl<T: Send + io::Write> Logger<T> {
+impl Logger {
// False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734
#[cfg_attr(feature = "cargo-clippy", allow(new_ret_no_self))]
- pub fn new(output: T, level: log::LevelFilter) -> Logger<io::LineWriter<T>> {
+ fn new(level: log::LevelFilter) -> Self {
log::set_max_level(level);
+
+ let logfile = Mutex::new(OnDemandLogFile::new());
+ let stdout = Mutex::new(LineWriter::new(io::stdout()));
+
Logger {
level,
- output: sync::Mutex::new(io::LineWriter::new(output))
+ logfile,
+ stdout,
+ errors: Arc::new(AtomicBool::new(false)),
+ warnings: Arc::new(AtomicBool::new(false)),
+ }
+ }
+
+ fn proxy(&self) -> LoggerProxy {
+ LoggerProxy {
+ errors: self.errors.clone(),
+ warnings: self.warnings.clone(),
+ logfile_proxy: self.logfile.lock().expect("").proxy(),
}
}
}
-impl<T: Send + io::Write> log::Log for Logger<T> {
+impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) && record.target().starts_with("alacritty") {
- if let Ok(ref mut writer) = self.output.lock() {
- let _ = writer.write_all(format!("{}\n", record.args()).as_ref());
+ let msg = format!(
+ "[{}] [{}] {}\n",
+ time::now().strftime("%F %R").unwrap(),
+ record.level(),
+ record.args()
+ );
+
+ if let Ok(ref mut logfile) = self.logfile.lock() {
+ let _ = logfile.write_all(msg.as_ref());
+ }
+
+ if let Ok(ref mut stdout) = self.stdout.lock() {
+ let _ = stdout.write_all(msg.as_ref());
+ }
+
+ match record.level() {
+ Level::Error => self.errors.store(true, Ordering::Relaxed),
+ Level::Warn => self.warnings.store(true, Ordering::Relaxed),
+ _ => (),
}
}
}
@@ -55,12 +152,82 @@ impl<T: Send + io::Write> log::Log for Logger<T> {
fn flush(&self) {}
}
-pub fn initialize(options: &cli::Options) -> Result<(), log::SetLoggerError> {
- // Use env_logger if RUST_LOG environment variable is defined. Otherwise,
- // use the alacritty-only logger.
- if ::std::env::var("RUST_LOG").is_ok() {
- ::env_logger::try_init()
- } else {
- log::set_boxed_logger(Box::new(Logger::new(io::stdout(), options.log_level)))
+#[derive(Clone, Default)]
+struct OnDemandLogFileProxy {
+ created: Arc<AtomicBool>,
+ path: String,
+}
+
+impl OnDemandLogFileProxy {
+ fn delete_log(&mut self) {
+ if self.created.load(Ordering::Relaxed) && fs::remove_file(&self.path).is_ok() {
+ let _ = writeln!(io::stdout(), "Deleted log file at {:?}", self.path);
+ self.created.store(false, Ordering::Relaxed);
+ }
+ }
+}
+
+struct OnDemandLogFile {
+ file: Option<LineWriter<File>>,
+ created: Arc<AtomicBool>,
+ path: PathBuf,
+}
+
+impl OnDemandLogFile {
+ fn new() -> Self {
+ let mut path = env::temp_dir();
+ path.push(format!("Alacritty-{}.log", process::id()));
+
+ OnDemandLogFile {
+ path,
+ file: None,
+ created: Arc::new(AtomicBool::new(false)),
+ }
+ }
+
+ fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> {
+ // Allow to recreate the file if it has been deleted at runtime
+ if self.file.is_some() && !self.path.as_path().exists() {
+ self.file = None;
+ }
+
+ // Create the file if it doesn't exist yet
+ if self.file.is_none() {
+ let file = OpenOptions::new()
+ .append(true)
+ .create(true)
+ .open(&self.path);
+
+ match file {
+ Ok(file) => {
+ self.file = Some(io::LineWriter::new(file));
+ self.created.store(true, Ordering::Relaxed);
+ let _ = writeln!(io::stdout(), "Created log file at {:?}", self.path);
+ }
+ Err(e) => {
+ let _ = writeln!(io::stdout(), "Unable to create log file: {}", e);
+ return Err(e);
+ }
+ }
+ }
+
+ Ok(self.file.as_mut().unwrap())
+ }
+
+ fn proxy(&self) -> OnDemandLogFileProxy {
+ OnDemandLogFileProxy {
+ created: self.created.clone(),
+ path: self.path.to_string_lossy().to_string(),
+ }
+ }
+}
+
+impl Write for OnDemandLogFile {
+ fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
+ self.file()?.write(buf)
+ }
+
+ fn flush(&mut self) -> Result<(), io::Error> {
+ self.file()?.flush()
}
}
diff --git a/src/macros.rs b/src/macros.rs
index 464110e6..519f8b6a 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -15,33 +15,7 @@
#[macro_export]
macro_rules! die {
($($arg:tt)*) => {{
- eprintln!($($arg)*);
+ error!($($arg)*);
::std::process::exit(1);
}}
}
-
-#[macro_export]
-macro_rules! maybe {
- ($option:expr) => {
- match $option {
- Some(value) => value,
- None => return None,
- }
- }
-}
-
-#[macro_export]
-macro_rules! print {
- ($($arg:tt)*) => {{
- use std::io::Write;
- let _ = write!(::std::io::stdout(), $($arg)*);
- }};
-}
-
-#[macro_export]
-macro_rules! eprint {
- ($($arg:tt)*) => {{
- use std::io::Write;
- let _ = write!(::std::io::stderr(), $($arg)*);
- }};
-}
diff --git a/src/main.rs b/src/main.rs
index 11adb396..a44c8eb3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -52,7 +52,7 @@ use alacritty::event;
use alacritty::event_loop::{self, EventLoop, Msg};
#[cfg(target_os = "macos")]
use alacritty::locale;
-use alacritty::logging;
+use alacritty::logging::{self, LoggerProxy};
use alacritty::sync::FairMutex;
use alacritty::term::Term;
use alacritty::tty::{self, process_should_exit};
@@ -65,8 +65,13 @@ fn main() {
#[cfg(windows)]
unsafe { AttachConsole(ATTACH_PARENT_PROCESS); }
- // Load command line options and config
+ // Load command line options
let options = cli::Options::load();
+
+ // Initialize the logger as soon as possible as to capture output from other subsystems
+ let logger_proxy = logging::initialize(&options).expect("Unable to initialize logger");
+
+ // Load configuration file
let config = load_config(&options).update_dynamic_title(&options);
// Switch to home directory
@@ -77,11 +82,9 @@ fn main() {
locale::set_locale_environment();
// Run alacritty
- if let Err(err) = run(config, &options) {
+ if let Err(err) = run(config, &options, logger_proxy) {
die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err));
}
-
- info!("Goodbye.");
}
/// Load configuration
@@ -98,7 +101,7 @@ fn load_config(options: &cli::Options) -> Config {
});
Config::load_from(&*config_path).unwrap_or_else(|err| {
- eprintln!("Error: {}; Loading default config", err);
+ error!("Error: {}; Loading default config", err);
Config::default()
})
}
@@ -107,10 +110,11 @@ fn load_config(options: &cli::Options) -> Config {
///
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
-fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
- // Initialize the logger first as to capture output from other subsystems
- logging::initialize(options)?;
-
+fn run(
+ mut config: Config,
+ options: &cli::Options,
+ mut logger_proxy: LoggerProxy,
+) -> Result<(), Box<Error>> {
info!("Welcome to Alacritty.");
if let Some(config_path) = config.path() {
info!("Configuration loaded from {}", config_path.display());
@@ -122,7 +126,7 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
// Create a display.
//
// The display manages a window and can draw the terminal
- let mut display = Display::new(&config, options)?;
+ let mut display = Display::new(&config, options, logger_proxy.clone())?;
info!(
"PTY Dimensions: {:?} x {:?}",
@@ -135,7 +139,8 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
// This object contains all of the state about what's being displayed. It's
// wrapped in a clonable mutex since both the I/O loop and display need to
// access it.
- let terminal = Term::new(&config, display.size().to_owned());
+ let mut terminal = Term::new(&config, display.size().to_owned());
+ terminal.set_logger_proxy(logger_proxy.clone());
let terminal = Arc::new(FairMutex::new(terminal));
// Find the window ID for setting $WINDOWID
@@ -258,5 +263,11 @@ fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
#[cfg(windows)]
unsafe { FreeConsole(); }
+ info!("Goodbye.");
+
+ if !options.persistent_logging && !config.persistent_logging() {
+ logger_proxy.delete_log();
+ }
+
Ok(())
}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index 1a193caf..ef5a1e76 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -817,10 +817,16 @@ impl<'a> RenderApi<'a> {
self.batch.clear();
}
- /// Render a string in a predefined location. Used for printing render time for profiling and
- /// optimization.
- pub fn render_string(&mut self, string: &str, glyph_cache: &mut GlyphCache, color: Rgb) {
- let line = Line(23);
+
+ /// Render a string in a variable location. Used for printing the render timer, warnings and
+ /// errors.
+ pub fn render_string(
+ &mut self,
+ string: &str,
+ line: Line,
+ glyph_cache: &mut GlyphCache,
+ color: Rgb
+ ) {
let col = Column(0);
let cells = string
diff --git a/src/term/mod.rs b/src/term/mod.rs
index 39213ee2..0f228f86 100644
--- a/src/term/mod.rs
+++ b/src/term/mod.rs
@@ -31,6 +31,7 @@ use config::{Config, VisualBellAnimation};
use {MouseCursor, Rgb};
use copypasta::{Clipboard, Load, Store};
use input::FONT_SIZE_STEP;
+use logging::LoggerProxy;
pub mod cell;
pub mod color;
@@ -810,6 +811,9 @@ pub struct Term {
/// Automatically scroll to bottom when new lines are added
auto_scroll: bool,
+
+ /// Proxy object for clearing displayed errors and warnings
+ logger_proxy: Option<LoggerProxy>,
}
/// Terminal size info
@@ -936,9 +940,14 @@ impl Term {
dynamic_title: config.dynamic_title(),
tabspaces,
auto_scroll: config.scrolling().auto_scroll,
+ logger_proxy: None,
}
}
+ pub fn set_logger_proxy(&mut self, logger_proxy: LoggerProxy) {
+ self.logger_proxy = Some(logger_proxy);
+ }
+
pub fn change_font_size(&mut self, delta: f32) {
// Saturating addition with minimum font size FONT_SIZE_STEP
let new_size = self.font_size + Size::new(delta);
@@ -1822,6 +1831,11 @@ impl ansi::Handler for Term {
}
},
ansi::ClearMode::All => {
+ // Clear displayed errors and warnings
+ if let Some(ref mut logger_proxy) = self.logger_proxy {
+ logger_proxy.clear();
+ }
+
self.grid.region_mut(..).each(|c| c.reset(&template));
},
ansi::ClearMode::Above => {