From 5afc91ef2f857f392631e79c4e3bef29b3256acc Mon Sep 17 00:00:00 2001
From: Aleksander <170264518+t-aleksander@users.noreply.github.com>
Date: Wed, 16 Oct 2024 12:52:33 +0200
Subject: [PATCH] Log improvements (#340)
* some log improvements, also updates the documentation url
* cleanup
* cleanup 2
* sqlx prepare
---
.github/pull_request_template.md | 6 +-
README.md | 2 +-
resources-linux/defguard-service.service | 2 +-
...bb5148296157f00ef39610c18fc290da384bf.json | 20 ++
...e2cc7673709453d011e627930d6c184966d36.json | 20 ++
src-tauri/Cargo.lock | 5 +-
src-tauri/Cargo.toml | 3 +-
src-tauri/src/appstate.rs | 21 +-
src-tauri/src/bin/defguard-client.rs | 94 ++++--
src-tauri/src/commands.rs | 299 ++++++++++--------
src-tauri/src/database/mod.rs | 56 ++--
src-tauri/src/database/models/instance.rs | 16 +-
src-tauri/src/database/models/location.rs | 5 +-
.../src/database/models/location_stats.rs | 15 +-
src-tauri/src/database/models/settings.rs | 5 +-
src-tauri/src/database/models/tunnel.rs | 13 +-
src-tauri/src/enterprise/periodic/config.rs | 36 +--
src-tauri/src/error.rs | 6 +-
.../src/log_watcher/global_log_watcher.rs | 42 +--
src-tauri/src/log_watcher/mod.rs | 7 +
.../src/log_watcher/service_log_watcher.rs | 63 ++--
src-tauri/src/service/config.rs | 4 +-
src-tauri/src/service/mod.rs | 54 ++--
src-tauri/src/service/utils.rs | 2 +-
src-tauri/src/tray.rs | 25 +-
src-tauri/src/utils.rs | 170 ++++++----
src/i18n/en/index.ts | 13 +-
src/i18n/i18n-types.ts | 48 ++-
src/pages/client/clientAPI/clientApi.ts | 4 +-
src/pages/client/clientAPI/types.ts | 3 +
.../components/GlobalLogs/GlobalLogs.tsx | 60 +++-
.../GlobalLogs/GlobalLogsSelect.tsx | 10 +-
.../GlobalLogs/GlobalLogsSourceSelect.tsx | 71 +++++
.../components/GlobalLogs/style.scss | 13 +
34 files changed, 829 insertions(+), 384 deletions(-)
create mode 100644 src-tauri/.sqlx/query-2e752bd9e60b5ddd0c9eeeabb6cbb5148296157f00ef39610c18fc290da384bf.json
create mode 100644 src-tauri/.sqlx/query-979d26af5b82c1a991402203707e2cc7673709453d011e627930d6c184966d36.json
create mode 100644 src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSourceSelect.tsx
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 63767beb..e7b25f34 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -7,17 +7,17 @@
#### Documentation ###
-- [ ] If testing requires changes in the environment or deployment, please **update the documentation** (https://defguard.gitbook.io) first and **attach the link to the documentation** section in this pull request
+- [ ] If testing requires changes in the environment or deployment, please **update the documentation** (https://docs.defguard.net) first and **attach the link to the documentation** section in this pull request
- [ ] I have commented on my code, particularly in hard-to-understand areas
-#### Testing ###
+#### Testing ###
- [ ] I have performed manual tests manually and all changes work
- [ ] New and existing unit tests pass locally with my changes
### 🏚️ Main Branch Merge Checklist:
-#### Testing ###
+#### Testing ###
- [ ] I have merged my changes before to dev and the dev checklist is done
- [ ] I have tested all functionalities on the dev instance and they work
diff --git a/README.md b/README.md
index 733ab85f..55c77ec4 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Desktop client for managing WireGuard VPN connections (any WireGuard server and
- Multiple instances & locations - When combining with [defguard](https://github.com/DefGuard/defguard) VPN & SSO you can have multiple defguard instances (sites/installations) and multiple Locations (VPN tunnels in that location/site) in one client! If you are an admin/devops - all your customers (instances) and all their tunnels (locations) can be in one place!
- Fast! - Built with Rust, [tauri](https://tauri.app/) and [React.js](https://react.dev/).
-To learn more about the system see our [documentation](https://defguard.gitbook.io).
+To learn more about the system see our [documentation](https://docs.defguard.net).
## Development
diff --git a/resources-linux/defguard-service.service b/resources-linux/defguard-service.service
index 6cc2cd3d..bce9f07b 100644
--- a/resources-linux/defguard-service.service
+++ b/resources-linux/defguard-service.service
@@ -1,6 +1,6 @@
[Unit]
Description=Defguard interface daemon service
-Documentation=https://defguard.gitbook.io/defguard/
+Documentation=https://docs.defguard.net
Wants=network-online.target
After=network-online.target
diff --git a/src-tauri/.sqlx/query-2e752bd9e60b5ddd0c9eeeabb6cbb5148296157f00ef39610c18fc290da384bf.json b/src-tauri/.sqlx/query-2e752bd9e60b5ddd0c9eeeabb6cbb5148296157f00ef39610c18fc290da384bf.json
new file mode 100644
index 00000000..acfde086
--- /dev/null
+++ b/src-tauri/.sqlx/query-2e752bd9e60b5ddd0c9eeeabb6cbb5148296157f00ef39610c18fc290da384bf.json
@@ -0,0 +1,20 @@
+{
+ "db_name": "SQLite",
+ "query": "SELECT name FROM location WHERE id = $1",
+ "describe": {
+ "columns": [
+ {
+ "name": "name",
+ "ordinal": 0,
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Right": 1
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "2e752bd9e60b5ddd0c9eeeabb6cbb5148296157f00ef39610c18fc290da384bf"
+}
diff --git a/src-tauri/.sqlx/query-979d26af5b82c1a991402203707e2cc7673709453d011e627930d6c184966d36.json b/src-tauri/.sqlx/query-979d26af5b82c1a991402203707e2cc7673709453d011e627930d6c184966d36.json
new file mode 100644
index 00000000..cd8dbd20
--- /dev/null
+++ b/src-tauri/.sqlx/query-979d26af5b82c1a991402203707e2cc7673709453d011e627930d6c184966d36.json
@@ -0,0 +1,20 @@
+{
+ "db_name": "SQLite",
+ "query": "SELECT name FROM tunnel WHERE id = $1;",
+ "describe": {
+ "columns": [
+ {
+ "name": "name",
+ "ordinal": 0,
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Right": 1
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "979d26af5b82c1a991402203707e2cc7673709453d011e627930d6c184966d36"
+}
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index a9847410..7c8424a3 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -1287,6 +1287,7 @@ dependencies = [
"tauri-plugin-single-instance",
"tauri-plugin-window-state",
"thiserror",
+ "time",
"tokio",
"tokio-util",
"tonic",
@@ -1302,8 +1303,8 @@ dependencies = [
[[package]]
name = "defguard_wireguard_rs"
-version = "0.5.2"
-source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.5.2#a7840aa584b4af134e8327fdb03f0793b696554a"
+version = "0.5.3"
+source = "git+https://github.com/DefGuard/wireguard-rs.git?rev=v0.5.3#e3b1026ad561d151178e6cc39b5cd2d94acc97ca"
dependencies = [
"base64 0.22.1",
"libc",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 57c191d0..ebc7c647 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -22,7 +22,7 @@ base64 = "0.22"
clap = { version = "4.5", features = ["derive", "env"] }
chrono = { version = "0.4", features = ["serde"] }
dark-light = "1.1"
-defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.5.2" }
+defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.5.3" }
dirs = "5.0"
lazy_static = "1.5"
local-ip-address = "0.6"
@@ -58,6 +58,7 @@ tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-wo
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
thiserror = "1.0"
+time = { version = "0.3", features = ["formatting"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
tokio-util = "0.7"
tonic = "0.12"
diff --git a/src-tauri/src/appstate.rs b/src-tauri/src/appstate.rs
index b2c91ebf..ba164631 100644
--- a/src-tauri/src/appstate.rs
+++ b/src-tauri/src/appstate.rs
@@ -57,7 +57,7 @@ impl AppState {
location_id: Id,
connection_type: &ConnectionType,
) -> Option {
- debug!("Removing active connection for location with id: {location_id}");
+ trace!("Removing active connection for location with id: {location_id}");
let mut connections = self.active_connections.lock().await;
if let Some(index) = connections.iter().position(|conn| {
@@ -65,7 +65,7 @@ impl AppState {
}) {
// Found a connection with the specified location_id
let removed_connection = connections.remove(index);
- info!("Active connection has been removed from the active connections list.");
+ trace!("Active connection has been removed from the active connections list.");
Some(removed_connection)
} else {
debug!("No active connection found with location_id: {location_id}");
@@ -91,9 +91,10 @@ impl AppState {
}
pub async fn close_all_connections(&self) -> Result<(), crate::error::Error> {
- info!("Closing all active connections...");
+ debug!("Closing all active connections...");
let active_connections = self.active_connections.lock().await;
- info!("Found {} active connections", active_connections.len());
+ let active_connections_count = active_connections.len();
+ debug!("Found {} active connections", active_connections_count);
for connection in active_connections.iter() {
debug!(
"Found active connection with location {}",
@@ -103,7 +104,11 @@ impl AppState {
debug!("Removing interface {}", connection.interface_name);
disconnect_interface(connection, self).await?;
}
- info!("All active connections closed");
+ if active_connections_count > 0 {
+ info!("All active connections ({active_connections_count}) have been closed.");
+ } else {
+ debug!("There were no active connections to close, nothing to do.");
+ }
Ok(())
}
@@ -113,8 +118,8 @@ impl AppState {
connection_type: ConnectionType,
) -> Option {
let connections = self.active_connections.lock().await;
- debug!(
- "Checking for active connection with id: {id}, connection_type: {connection_type:?} in active connections: {connections:?}"
+ trace!(
+ "Checking for active connection with id: {id}, connection_type: {connection_type:?} in active connections."
);
if let Some(connection) = connections
@@ -122,7 +127,7 @@ impl AppState {
.find(|conn| conn.location_id == id && conn.connection_type == connection_type)
{
// 'connection' now contains the first element with the specified id and connection_type
- debug!("Found connection: {connection:?}");
+ trace!("Found connection: {connection:?}");
Some(connection.to_owned())
} else {
debug!("Couldn't find connection with id: {id}, connection_type: {connection_type:?} in active connections.");
diff --git a/src-tauri/src/bin/defguard-client.rs b/src-tauri/src/bin/defguard-client.rs
index 0b615ef8..6e3bd012 100644
--- a/src-tauri/src/bin/defguard-client.rs
+++ b/src-tauri/src/bin/defguard-client.rs
@@ -27,6 +27,7 @@ use defguard_client::{
enterprise::periodic::config::poll_config,
events::SINGLE_INSTANCE,
periodic::version::poll_version,
+ service,
tray::{configure_tray_icon, handle_tray_event, reload_tray_menu},
utils::load_log_targets,
VERSION,
@@ -37,6 +38,7 @@ use log::{Level, LevelFilter};
use tauri::{api::process, Env};
use tauri::{Builder, Manager, RunEvent, State, SystemTray, WindowEvent};
use tauri_plugin_log::LogTarget;
+use time;
#[derive(Clone, serde::Serialize)]
struct Payload {
@@ -49,6 +51,7 @@ extern crate log;
// for tauri log plugin
const LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir];
+const LOG_FILTER: [&str; 5] = ["tauri", "sqlx", "hyper", "h2", "tower"];
lazy_static! {
static ref LOG_INCLUDES: Vec = load_log_targets();
@@ -76,9 +79,16 @@ async fn main() {
}
let log_level =
- LevelFilter::from_str(&env::var("DEFGUARD_CLIENT_LOG_LEVEL").unwrap_or("info".into()))
+ LevelFilter::from_str(&env::var("DEFGUARD_CLIENT_LOG_LEVEL").unwrap_or("debug".into()))
.unwrap_or(LevelFilter::Info);
+ // Sets the time format. Service's logs have a subsecond part, so we also need to include it here,
+ // otherwise the logs couldn't be sorted correctly when displayed together in the UI.
+ let format = time::format_description::parse(
+ "[[[year]-[month]-[day]][[[hour]:[minute]:[second].[subsecond]]",
+ )
+ .unwrap();
+
let app = Builder::default()
.invoke_handler(tauri::generate_handler![
all_locations,
@@ -126,6 +136,18 @@ async fn main() {
}))
.plugin(
tauri_plugin_log::Builder::default()
+ .format(move |out, message, record| {
+ out.finish(format_args!(
+ "{}[{}][{}] {}",
+ tauri_plugin_log::TimezoneStrategy::UseUtc
+ .get_now()
+ .format(&format)
+ .unwrap(),
+ record.level(),
+ record.target(),
+ message
+ ))
+ })
.targets(LOG_TARGETS)
.level(log_level)
.filter(|metadata| {
@@ -142,6 +164,22 @@ async fn main() {
}
true
})
+ .filter(|metadata| {
+ // Log all errors, warnings and infos
+ if metadata.level() == LevelFilter::Error
+ || metadata.level() == LevelFilter::Warn
+ || metadata.level() == LevelFilter::Info
+ {
+ return true;
+ }
+ // Otherwise do not log the following targets
+ for target in LOG_FILTER.iter() {
+ if metadata.target().contains(target) {
+ return false;
+ }
+ }
+ true
+ })
.build(),
)
.plugin(tauri_plugin_window_state::Builder::default().build())
@@ -149,28 +187,38 @@ async fn main() {
.build(tauri::generate_context!())
.expect("error while building tauri application");
- info!("Starting version v{}...", VERSION);
+ info!("Starting Defguard client version {}", VERSION);
// initialize database
let app_handle = app.handle();
- debug!("Initializing database connection...");
- let app_state: State = app_handle.state();
- let db = database::init_db(&app_handle)
- .await
- .expect("Database initialization failed");
- *app_state.db.lock().unwrap() = Some(db);
- info!("Database initialization completed");
- debug!("Getting database info to check if the connection is working...");
- let result = database::info(&app_state.get_pool()).await;
- if let Err(e) = result {
- error!(
- "There was an error while getting the database info: {:?}. The database connection might not be working.",
- e
- );
- }
+
info!(
- "Database info has been fetched successfully. The connection with the database is working."
+ "The application data (database file) will be stored in: {:?} \
+ and the application logs in: {:?}. Logs of the background defguard service responsible for \
+ managing the VPN connections at the network level will be stored in: {:?}.",
+ // display the path to the app data direcory, convert option to option<&str>
+ app_handle
+ .path_resolver()
+ .app_data_dir()
+ .unwrap_or("UNDEFINED DATA DIRECTORY".into()),
+ app_handle
+ .path_resolver()
+ .app_log_dir()
+ .unwrap_or("UNDEFINED LOG DIRECTORY".into()),
+ service::config::DEFAULT_LOG_DIR
);
+ debug!("Performing database setup...");
+ let app_state: State = app_handle.state();
+ let db = match database::init_db(&app_handle).await {
+ Ok(db) => db,
+ Err(e) => {
+ error!("Failed to initialize database: {}", e);
+ return;
+ }
+ };
+ *app_state.db.lock().unwrap() = Some(db);
+ debug!("Database setup has been completed successfully.");
+
// configure tray
debug!("Configuring tray icon...");
if let Ok(settings) = Settings::get(&app_state.get_pool()).await {
@@ -179,15 +227,15 @@ async fn main() {
debug!("Tray icon has been configured successfully");
// run periodic tasks
- debug!("Starting periodic tasks...");
+ debug!("Starting periodic tasks (config and version polling)...");
tauri::async_runtime::spawn(poll_version(app_handle.clone()));
tauri::async_runtime::spawn(poll_config(app_handle.clone()));
debug!("Periodic tasks have been started");
// load tray menu after database initialization to show all instance and locations
- debug!("Reloading tray menu to show all instances and locations...");
+ debug!("Re-generating tray menu to show all available instances and locations as we have connected to the database...");
reload_tray_menu(&app_handle).await;
- debug!("Tray menu has been reloaded successfully");
+ debug!("Tray menu has been re-generated successfully");
// Handle Ctrl-C
debug!("Setting up Ctrl-C handler...");
@@ -202,7 +250,7 @@ async fn main() {
debug!("Ctrl-C handler has been set up successfully");
// run app
- info!("Running the application...");
+ debug!("Starting the main application event loop...");
app.run(|app_handle, event| match event {
// prevent shutdown on window close
RunEvent::ExitRequested { api, .. } => {
@@ -211,7 +259,7 @@ async fn main() {
}
// handle shutdown
RunEvent::Exit => {
- info!("Exiting event loop...");
+ debug!("Exiting the application's main event loop...");
let app_state: State = app_handle.state();
app_state.quit(app_handle);
}
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 7e535f4b..e3a484b5 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -1,3 +1,4 @@
+use core::fmt;
use std::{
collections::{HashMap, HashSet},
env,
@@ -62,7 +63,7 @@ pub async fn connect(
);
handle_connection_for_location(&location, preshared_key, handle.clone()).await?;
reload_tray_menu(&handle).await;
- info!("Connected to location {} successfully", location.name);
+ info!("Connected to location {location}");
} else {
error!("Location with ID {location_id} not found in the database, aborting connection attempt");
return Err(Error::NotFound);
@@ -73,7 +74,7 @@ pub async fn connect(
tunnel.name
);
handle_connection_for_tunnel(&tunnel, handle).await?;
- info!("Connected to tunnel {} successfully", tunnel.name);
+ info!("Successfully connected to tunnel {tunnel}");
} else {
error!("Tunnel {location_id} not found");
return Err(Error::NotFound);
@@ -101,22 +102,13 @@ pub async fn disconnect(
connection_type: ConnectionType,
handle: AppHandle,
) -> Result<(), Error> {
- debug!("Received a command to disconnect from a {connection_type} with ID {location_id}");
let state = handle.state::();
+ let name = get_tunnel_or_location_name(location_id, connection_type, &state).await;
+ debug!("Received a command to disconnect from the {connection_type} {name}({location_id})");
- let name = match get_tunnel_or_location_name(location_id, connection_type, &state).await {
- Some(name) => {
- debug!("Identified {connection_type} with ID {location_id} as \"{name}\", handling disconnection...");
- name
- }
- None => {
- debug!("Could not identify {connection_type} with ID {location_id}, this {connection_type} will be referred to as UNKNOWN, trying to disconnect anyway...");
- "UNKNOWN".to_string()
- }
- };
-
+ debug!("Removing active connection for {connection_type} {name}({location_id}) from the application's state, if it exists...");
if let Some(connection) = state.remove_connection(location_id, &connection_type).await {
- debug!("Found active connection for {connection_type} {name}({location_id})");
+ debug!("Found and removed active connection from the application's state for {connection_type} {name}({location_id})");
trace!("Connection: {connection:?}");
disconnect_interface(&connection, &state).await?;
debug!("Emitting the event informing the frontend about the disconnection from {connection_type} {name}({location_id})");
@@ -130,15 +122,30 @@ pub async fn disconnect(
stop_log_watcher_task(&handle, &connection.interface_name)?;
reload_tray_menu(&handle).await;
if connection_type == ConnectionType::Location {
+ let name = get_tunnel_or_location_name(location_id, connection_type, &state).await;
if let Err(err) = maybe_update_instance_config(location_id, &handle).await {
- warn!("Failed to update instance for location {location_id}: {err}");
+ match err {
+ Error::CoreNotEnterprise => {
+ debug!(
+ "Tried to fetch instance config from core after disconnecting from {name}(ID: {location_id}), but the core is not enterprise, so we can't fetch the config."
+ );
+ }
+ Error::NoToken => {
+ debug!(
+ "Tried to fetch instance config from core after disconnecting from {name}(ID: {location_id}), but this location's instance has no polling token, so we can't fetch the config."
+ );
+ }
+ _ => {
+ warn!("Error while trying to fetch instance config after disconnecting from {name}(ID: {location_id}): {err}");
+ }
+ }
};
}
- info!("Finished disconnecting from {connection_type} {name}({location_id})");
+ info!("Disconnected from {connection_type} {name}(ID: {location_id})");
Ok(())
} else {
- error!("Error while disconnecting from {connection_type} {name}({location_id}): connection not found");
+ warn!("Couldn't disconnect from {connection_type} {name}(ID: {location_id}), as no active connection was found.");
Err(Error::NotFound)
}
}
@@ -222,7 +229,7 @@ pub async fn save_device_config(
.expect("Missing instance info in device config response");
let mut instance: Instance = instance_info.into();
if response.token.is_some() {
- info!("Received polling token for instance {}", instance.name);
+ debug!("The newly saved device config has a polling token, automatic configuration polling will be possible if the core has an enterprise license.")
} else {
warn!(
"Missing polling token for instance {}, core and/or proxy services may need an update, configuration polling won't work",
@@ -233,7 +240,7 @@ pub async fn save_device_config(
debug!("Saving instance {}", instance.name);
let instance = instance.save(&mut *transaction).await?;
- info!("Saved instance {}", instance.name);
+ debug!("Saved instance {}", instance.name);
let device = response
.device
@@ -244,7 +251,7 @@ pub async fn save_device_config(
keys.pubkey, instance.name, instance.id
);
let keys = keys.save(&mut *transaction).await?;
- info!(
+ debug!(
"Saved wireguard key {} for instance {}({})",
keys.pubkey, instance.name, instance.id
);
@@ -255,21 +262,17 @@ pub async fn save_device_config(
new_location.name, instance.name, instance.id
);
let new_location = new_location.save(&mut *transaction).await?;
- info!(
+ debug!(
"Saved location {} for instance {}({})",
new_location.name, instance.name, instance.id
);
}
transaction.commit().await?;
- info!("Instance {}({:?}) created.", instance.name, instance.id);
+ info!("New instance {} created.", instance);
trace!("Created following instance: {instance:#?}");
let locations = Location::find_by_instance_id(&app_state.get_pool(), instance.id).await?;
trace!("Created following locations: {locations:#?}");
handle.emit_all(INSTANCE_UPDATE, ())?;
- info!(
- "Device configuration saved for instance {}({})",
- instance.name, instance.id,
- );
let res: SaveDeviceConfigResponse = SaveDeviceConfigResponse {
locations,
instance,
@@ -280,10 +283,12 @@ pub async fn save_device_config(
#[tauri::command(async)]
pub async fn all_instances(app_state: State<'_, AppState>) -> Result>, Error> {
- debug!("Retrieving all instances.");
-
+ debug!("Getting information about all instances.");
let instances = Instance::all(&app_state.get_pool()).await?;
- debug!("Found ({}) instances", instances.len());
+ trace!(
+ "Found {} instances to return information about.",
+ instances.len()
+ );
trace!("Instances found: {instances:#?}");
let mut instance_info = Vec::new();
let connection_ids = app_state
@@ -310,8 +315,11 @@ pub async fn all_instances(app_state: State<'_, AppState>) -> Result) -> fmt::Result {
+ write!(f, "{}", self.name)
+ }
+}
+
#[tauri::command(async)]
pub async fn all_locations(
instance_id: Id,
app_state: State<'_, AppState>,
) -> Result, Error> {
- debug!("Retrieving all locations.");
+ let instance = match Instance::find_by_id(&app_state.get_pool(), instance_id).await? {
+ Some(instance) => instance,
+ None => {
+ error!("Tried to get all locations for the instance with ID {instance_id}, but the instance was not found.");
+ return Err(Error::NotFound);
+ }
+ };
+ trace!(
+ "Getting information about all locations for instance {}.",
+ instance.name
+ );
let locations = Location::find_by_instance_id(&app_state.get_pool(), instance_id).await?;
+ trace!(
+ "Found {} locations for instance {instance} to return information about.",
+ locations.len()
+ );
let active_locations_ids: Vec = app_state
.get_connection_id_by_type(&ConnectionType::Location)
.await;
@@ -357,10 +385,9 @@ pub async fn all_locations(
};
location_info.push(info);
}
- debug!("Locations retrieved({})", location_info.len());
- debug!(
- "Returning {} locations for instance {instance_id}",
- location_info.len(),
+ trace!(
+ "Returning information about {} locations for instance {instance}",
+ location_info.len()
);
trace!("Locations returned:\n{location_info:#?}");
@@ -404,11 +431,12 @@ pub async fn update_instance(
app_state: State<'_, AppState>,
app_handle: AppHandle,
) -> Result<(), Error> {
- debug!("Received update_instance command");
+ debug!("Received command to update instance with id {instance_id}");
trace!("Processing following response:\n {response:#?}");
let pool = app_state.get_pool();
if let Some(mut instance) = Instance::find_by_id(&pool, instance_id).await? {
+ debug!("The instance with id {instance_id} to update was found: {instance}");
let mut transaction = pool.begin().await?;
do_update_instance(&mut transaction, &mut instance, response).await?;
transaction.commit().await?;
@@ -417,7 +445,7 @@ pub async fn update_instance(
reload_tray_menu(&app_handle).await;
Ok(())
} else {
- error!("Instance with id {instance_id} not found");
+ error!("Instance to update with id {instance_id} was not found, aborting update");
Err(Error::NotFound)
}
}
@@ -462,7 +490,7 @@ pub async fn do_update_instance(
response: DeviceConfigResponse,
) -> Result<(), Error> {
// update instance
- debug!("Updating instance {}({}).", instance.name, instance.id);
+ debug!("Updating instance {}", instance);
let locations_changed = locations_changed(transaction, instance, &response).await?;
let instance_info = response
.instance
@@ -476,13 +504,13 @@ pub async fn do_update_instance(
&& instance_info.disable_all_traffic
{
debug!(
- "Disabling all traffic for all locations of instance {}({}).",
- instance.name, instance.id
+ "Disabling all traffic for all locations of instance {}.",
+ instance
);
Location::disable_all_traffic_for_all(transaction.as_mut(), instance.id).await?;
debug!(
- "Disabled all traffic for all locations of instance {}({}).",
- instance.name, instance.id
+ "Disabled all traffic for all locations of instance {}.",
+ instance
);
}
instance.disable_all_traffic = instance_info.disable_all_traffic;
@@ -491,7 +519,7 @@ pub async fn do_update_instance(
// This happens during polling, as core doesn't issue a new token for polling request
if response.token.is_some() {
instance.token = response.token;
- info!("Set polling token for instance {}", instance.name);
+ debug!("Set polling token for instance {}", instance.name);
} else {
debug!(
"No polling token received for instance {}, not updating",
@@ -500,8 +528,8 @@ pub async fn do_update_instance(
}
instance.save(transaction.as_mut()).await?;
debug!(
- "Instance {}({}) main config applied from core's response.",
- instance.name, instance.id
+ "A new base configuration has been applied to instance {}, even if nothing changed",
+ instance
);
// check if locations have changed
@@ -540,50 +568,40 @@ pub async fn do_update_instance(
current_location.dns = new_location.dns;
current_location.save(transaction.as_mut()).await?;
info!(
- "Location {}({}) updated for instance {}({}).",
- current_location.name, current_location.id, instance.name, instance.id,
+ "Location {} configuration updated for instance {}",
+ current_location, instance,
);
} else {
// create new location
debug!(
- "Creating new location for instance {}({}).",
- instance.name, instance.id
+ "Creating new location {new_location} for instance {}",
+ instance
);
let new_location = new_location.save(transaction.as_mut()).await?;
info!(
- "New location {}({}) created for instance {}({})",
- new_location.name, new_location.id, instance.name, instance.id
+ "New location {} created for instance {}",
+ new_location, instance
);
}
}
- info!(
- "Locations updated for instance {}({}).",
- instance.name, instance.id
- );
// remove locations which were present in current locations
// but no longer found in core response
- debug!(
- "Removing locations for instance {}({}).",
- instance.name, instance.id
- );
+ debug!("Removing locations for instance {}.", instance);
for removed_location in current_locations {
removed_location.delete(transaction.as_mut()).await?;
+ info!(
+ "Removed location {} for instance {} during instance update",
+ removed_location, instance
+ );
}
- info!(
- "Locations removed for instance {}({}).",
- instance.name, instance.id
- );
+ debug!("Finished updating locations for instance {}", instance);
} else {
info!(
- "Locations for instance {}({}) didn't change. Not updating them.",
- instance.name, instance.id
+ "Locations for instance {} didn't change. Not updating them.",
+ instance
);
}
- info!(
- "Instance {}({}) update is done.",
- instance.name, instance.id
- );
Ok(())
}
@@ -705,14 +723,23 @@ pub async fn active_connection(
handle: AppHandle,
) -> Result
- For more help, please visit defguard help (https://defguard.gitbook.io/)
+ For more help, please visit defguard help (https://docs.defguard.net)
`,
},
diff --git a/src/i18n/i18n-types.ts b/src/i18n/i18n-types.ts
index 67248acd..365262c7 100644
--- a/src/i18n/i18n-types.ts
+++ b/src/i18n/i18n-types.ts
@@ -324,6 +324,26 @@ type RootTranslation = {
trace: string
}
}
+ globalLogs: {
+ logSources: {
+ /**
+ * Client
+ */
+ cliet: string
+ /**
+ * Service
+ */
+ service: string
+ /**
+ * All
+ */
+ all: string
+ }
+ /**
+ * The source of the logs. Logs can come from the Defguard client or the background Defguard service that manages VPN conncetions at the network level.
+ */
+ logSourceHelper: string
+ }
theme: {
/**
* Theme
@@ -831,7 +851,7 @@ type RootTranslation = {
</ul>
</div>
<p>
- For more help, please visit defguard help (https://defguard.gitbook.io/)
+ For more help, please visit defguard help (https://docs.defguard.net)
</p>
*/
@@ -917,7 +937,7 @@ type RootTranslation = {
</p>
</div>
<p>
- For more help, please visit defguard help (https://defguard.gitbook.io/)
+ For more help, please visit defguard help (https://docs.defguard.net)
</p>
*/
@@ -1830,6 +1850,26 @@ export type TranslationFunctions = {
trace: () => LocalizedString
}
}
+ globalLogs: {
+ logSources: {
+ /**
+ * Client
+ */
+ cliet: () => LocalizedString
+ /**
+ * Service
+ */
+ service: () => LocalizedString
+ /**
+ * All
+ */
+ all: () => LocalizedString
+ }
+ /**
+ * The source of the logs. Logs can come from the Defguard client or the background Defguard service that manages VPN conncetions at the network level.
+ */
+ logSourceHelper: () => LocalizedString
+ }
theme: {
/**
* Theme
@@ -2336,7 +2376,7 @@ export type TranslationFunctions = {
- For more help, please visit defguard help (https://defguard.gitbook.io/)
+ For more help, please visit defguard help (https://docs.defguard.net)
*/
@@ -2422,7 +2462,7 @@ export type TranslationFunctions = {
- For more help, please visit defguard help (https://defguard.gitbook.io/)
+ For more help, please visit defguard help (https://docs.defguard.net)
*/
diff --git a/src/pages/client/clientAPI/clientApi.ts b/src/pages/client/clientAPI/clientApi.ts
index 4944ed4c..bc39f5de 100644
--- a/src/pages/client/clientAPI/clientApi.ts
+++ b/src/pages/client/clientAPI/clientApi.ts
@@ -32,12 +32,12 @@ async function invokeWrapper(
args?: InvokeArgs,
timeout: number = 5000,
): Promise {
- debug(`Invoking command '${command}'`);
+ debug(`Invoking command '${command}' on the frontend`);
try {
const res = await pTimeout(invoke(command, args), {
milliseconds: timeout,
});
- debug(`Invoke ${command} completed`);
+ debug(`Invoke ${command} completed on the frontend`);
trace(`${command} completed with data: ${JSON.stringify(res)}`);
return res;
} catch (e) {
diff --git a/src/pages/client/clientAPI/types.ts b/src/pages/client/clientAPI/types.ts
index 489e5736..85fd600a 100644
--- a/src/pages/client/clientAPI/types.ts
+++ b/src/pages/client/clientAPI/types.ts
@@ -46,6 +46,8 @@ export type SaveTunnelRequest = {
export type TrayIconTheme = 'color' | 'white' | 'black' | 'gray';
export type LogLevel = 'error' | 'info' | 'debug' | 'trace';
+export type GlobalLogLevel = 'error' | 'info' | 'debug';
+export type LogSource = 'Client' | 'Service' | 'All';
export type ClientView = 'grid' | 'detail' | null;
@@ -60,6 +62,7 @@ export type LogItem = {
level: LogLevel;
target: string;
fields: LogItemField;
+ source: LogSource;
};
export type InterfaceLogsRequest = {
diff --git a/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogs.tsx b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogs.tsx
index 846e33ec..82d76d96 100644
--- a/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogs.tsx
+++ b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogs.tsx
@@ -10,15 +10,21 @@ import { useI18nContext } from '../../../../../../i18n/i18n-react';
import { ActionButton } from '../../../../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton';
import { ActionButtonVariant } from '../../../../../../shared/defguard-ui/components/Layout/ActionButton/types';
import { Card } from '../../../../../../shared/defguard-ui/components/Layout/Card/Card';
+import { Helper } from '../../../../../../shared/defguard-ui/components/Layout/Helper/Helper';
import { clientApi } from '../../../../clientAPI/clientApi';
-import { LogItem, LogLevel } from '../../../../clientAPI/types';
-import { useClientStore } from '../../../../hooks/useClientStore';
+import {
+ GlobalLogLevel,
+ LogItem,
+ LogLevel,
+ LogSource,
+} from '../../../../clientAPI/types';
import { GlobalLogsSelect } from './GlobalLogsSelect';
+import { GlobalLogsSourceSelect } from './GlobalLogsSourceSelect';
export const GlobalLogs = () => {
const logsContainerElement = useRef(null);
- const appLogLevel = useClientStore((state) => state.settings.log_level);
- const locationLogLevelRef = useRef(appLogLevel);
+ const globalLogLevelRef = useRef('info');
+ const logSourceRef = useRef('All');
const { LL } = useI18nContext();
const localLL = LL.pages.client.pages.instancePage.detailView.details.logs;
const { startGlobalLogWatcher, stopGlobalLogWatcher } = clientApi;
@@ -53,9 +59,11 @@ export const GlobalLogs = () => {
logItems.forEach((item) => {
if (
logsContainerElement.current &&
- filterLogByLevel(locationLogLevelRef.current, item.level)
+ filterLogByLevel(globalLogLevelRef.current, item.level) &&
+ filterLogBySource(logSourceRef.current, item.source)
) {
- const messageString = `${item.timestamp} ${item.level} ${item.fields.message}`;
+ const dateTime = new Date(item.timestamp).toLocaleString();
+ const messageString = `[${dateTime}][${item.level}][${item.source}] ${item.fields.message}`;
const element = createLogLineElement(messageString);
const scrollAfterAppend =
logsContainerElement.current.scrollHeight -
@@ -101,15 +109,33 @@ export const GlobalLogs = () => {
{localLL.title()}
-
{
- locationLogLevelRef.current = level;
- clearLogs();
- stopGlobalLogWatcher();
- startGlobalLogWatcher();
- }}
- />
+
+
{
+ globalLogLevelRef.current = level;
+ clearLogs();
+ stopGlobalLogWatcher();
+ startGlobalLogWatcher();
+ }}
+ />
+
+
{
+ logSourceRef.current = source;
+ clearLogs();
+ stopGlobalLogWatcher();
+ startGlobalLogWatcher();
+ }}
+ />
+
+
+ {LL.pages.client.pages.settingsPage.tabs.global.globalLogs.logSourceHelper()}
+
+
+
+
{
@@ -150,3 +176,7 @@ const filterLogByLevel = (target: LogLevel, log: LogLevel): boolean => {
return true;
}
};
+
+const filterLogBySource = (target: LogSource, log: LogSource): boolean => {
+ return target === 'All' || target === log;
+};
diff --git a/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSelect.tsx b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSelect.tsx
index 18050438..88c7028f 100644
--- a/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSelect.tsx
+++ b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSelect.tsx
@@ -7,11 +7,11 @@ import {
SelectSelectedValue,
SelectSizeVariant,
} from '../../../../../../shared/defguard-ui/components/Layout/Select/types';
-import { LogLevel } from '../../../../clientAPI/types';
+import { GlobalLogLevel } from '../../../../clientAPI/types';
type Props = {
- initSelected: LogLevel;
- onChange: (selected: LogLevel) => void;
+ initSelected: GlobalLogLevel;
+ onChange: (selected: GlobalLogLevel) => void;
};
export const GlobalLogsSelect = ({ initSelected, onChange }: Props) => {
@@ -19,7 +19,7 @@ export const GlobalLogsSelect = ({ initSelected, onChange }: Props) => {
const localLL = LL.pages.client.pages.settingsPage.tabs.global.logging.options;
const [selected, setSelected] = useState(initSelected);
- const options = useMemo((): SelectOption[] => {
+ const options = useMemo((): SelectOption[] => {
return [
{
key: 0,
@@ -40,7 +40,7 @@ export const GlobalLogsSelect = ({ initSelected, onChange }: Props) => {
}, [localLL]);
const renderSelected = useCallback(
- (value: LogLevel): SelectSelectedValue => {
+ (value: GlobalLogLevel): SelectSelectedValue => {
const option = options.find((o) => o.value === value);
if (option) {
return {
diff --git a/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSourceSelect.tsx b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSourceSelect.tsx
new file mode 100644
index 00000000..8bbe204c
--- /dev/null
+++ b/src/pages/client/pages/ClientSettingsPage/components/GlobalLogs/GlobalLogsSourceSelect.tsx
@@ -0,0 +1,71 @@
+import { useCallback, useMemo, useState } from 'react';
+
+import { useI18nContext } from '../../../../../../i18n/i18n-react';
+import { Select } from '../../../../../../shared/defguard-ui/components/Layout/Select/Select';
+import {
+ SelectOption,
+ SelectSelectedValue,
+ SelectSizeVariant,
+} from '../../../../../../shared/defguard-ui/components/Layout/Select/types';
+import { LogSource } from '../../../../clientAPI/types';
+
+type Props = {
+ initSelected: LogSource;
+ onChange: (selected: LogSource) => void;
+};
+
+export const GlobalLogsSourceSelect = ({ initSelected, onChange }: Props) => {
+ const { LL } = useI18nContext();
+ const localLL = LL.pages.client.pages.settingsPage.tabs.global.globalLogs.logSources;
+ const [selected, setSelected] = useState(initSelected);
+
+ const options = useMemo((): SelectOption[] => {
+ return [
+ {
+ key: 0,
+ label: localLL.all(),
+ value: 'All',
+ },
+ {
+ key: 1,
+ label: localLL.cliet(),
+ value: 'Client',
+ },
+ {
+ key: 2,
+ label: localLL.service(),
+ value: 'Service',
+ },
+ ];
+ }, [localLL]);
+
+ const renderSelected = useCallback(
+ (value: LogSource): SelectSelectedValue => {
+ const option = options.find((o) => o.value === value);
+ if (option) {
+ return {
+ key: option.key,
+ displayValue: option.label,
+ };
+ }
+ return {
+ key: 0,
+ displayValue: '',
+ };
+ },
+ [options],
+ );
+
+ return (
+