diff --git a/Cargo.lock b/Cargo.lock index ea032189..3bddb59c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,8 @@ dependencies = [ "ironrdp", "sspi", "thiserror", + "tracing", + "tracing-subscriber", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 26d5ae8a..5277dd46 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -22,6 +22,8 @@ diplomat-runtime = "0.7.0" ironrdp = { workspace = true, features = ["connector", "dvc", "svc","rdpdr","rdpsnd","graphics","input"] } sspi = { workspace = true, features = ["network_client"] } thiserror.workspace = true +tracing.workspace = true +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } [target.'cfg(windows)'.build-dependencies] embed-resource = "2.2.0" diff --git a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs index 75720d1e..cc0f207a 100644 --- a/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs +++ b/ffi/dotnet/Devolutions.IronRdp.AvaloniaExample/MainWindow.axaml.cs @@ -30,6 +30,8 @@ public partial class MainWindow : Window private void OnOpened(object? sender, EventArgs e) { + Log.InitWithEnv(); + WindowState = WindowState.Maximized; var username = Environment.GetEnvironmentVariable("IRONRDP_USERNAME"); diff --git a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs index 2d4d8c81..1a778437 100644 --- a/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs +++ b/ffi/dotnet/Devolutions.IronRdp.ConnectExample/Program.cs @@ -9,6 +9,8 @@ namespace Devolutions.IronRdp.ConnectExample { var arguments = ParseArguments(args); + Log.InitWithEnv(); + if (arguments == null) { return; diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/Log.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/Log.cs new file mode 100644 index 00000000..ec2769f3 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/Log.cs @@ -0,0 +1,71 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp; + +#nullable enable + +public partial class Log: IDisposable +{ + private unsafe Raw.Log* _inner; + + /// + /// Creates a managed Log from a raw handle. + /// + /// + /// Safety: you should not build two managed objects using the same raw handle (may causes use-after-free and double-free). + ///
+ /// This constructor assumes the raw struct is allocated on Rust side. + /// If implemented, the custom Drop implementation on Rust side WILL run on destruction. + ///
+ public unsafe Log(Raw.Log* handle) + { + _inner = handle; + } + + public static void InitWithEnv() + { + unsafe + { + Raw.Log.InitWithEnv(); + } + } + + /// + /// Returns the underlying raw handle. + /// + public unsafe Raw.Log* AsFFI() + { + return _inner; + } + + /// + /// Destroys the underlying object immediately. + /// + public void Dispose() + { + unsafe + { + if (_inner == null) + { + return; + } + + Raw.Log.Destroy(_inner); + _inner = null; + + GC.SuppressFinalize(this); + } + } + + ~Log() + { + Dispose(); + } +} diff --git a/ffi/dotnet/Devolutions.IronRdp/Generated/RawLog.cs b/ffi/dotnet/Devolutions.IronRdp/Generated/RawLog.cs new file mode 100644 index 00000000..e1f649b9 --- /dev/null +++ b/ffi/dotnet/Devolutions.IronRdp/Generated/RawLog.cs @@ -0,0 +1,24 @@ +// by Diplomat + +#pragma warning disable 0105 +using System; +using System.Runtime.InteropServices; + +using Devolutions.IronRdp.Diplomat; +#pragma warning restore 0105 + +namespace Devolutions.IronRdp.Raw; + +#nullable enable + +[StructLayout(LayoutKind.Sequential)] +public partial struct Log +{ + private const string NativeLib = "DevolutionsIronRdp"; + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Log_init_with_env", ExactSpelling = true)] + public static unsafe extern void InitWithEnv(); + + [DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Log_destroy", ExactSpelling = true)] + public static unsafe extern void Destroy(Log* self); +} diff --git a/ffi/src/connector/config.rs b/ffi/src/connector/config.rs index f2a657ed..4bb54ad2 100644 --- a/ffi/src/connector/config.rs +++ b/ffi/src/connector/config.rs @@ -195,7 +195,7 @@ pub mod ffi { performance_flags: self.performance_flags.ok_or("performance flag is missing")?, desktop_scale_factor: 0, }; - + tracing::debug!(config=?inner_config, "Built config"); Ok(Box::new(Config(inner_config))) } } diff --git a/ffi/src/connector/mod.rs b/ffi/src/connector/mod.rs index 6b764133..8134847b 100644 --- a/ffi/src/connector/mod.rs +++ b/ffi/src/connector/mod.rs @@ -151,6 +151,7 @@ pub mod ffi { let Some(connector) = self.0.as_ref() else { return Err(ValueConsumedError::for_item("connector").into()); }; + tracing::trace!(pduhint=?connector.next_pdu_hint(), "Reading next PDU hint"); Ok(connector.next_pdu_hint().map(PduHint).map(Box::new)) } diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index d16ead91..e5eaaf56 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -7,6 +7,7 @@ pub mod dvc; pub mod error; pub mod graphics; pub mod input; +pub mod log; pub mod pdu; pub mod session; pub mod svc; diff --git a/ffi/src/log.rs b/ffi/src/log.rs new file mode 100644 index 00000000..85c4bb37 --- /dev/null +++ b/ffi/src/log.rs @@ -0,0 +1,66 @@ +use std::{error::Error, sync::Once}; + +static INIT_LOG: Once = Once::new(); + +const IRONRDP_LOG_PATH: &str = "IRONRDP_LOG_PATH"; +const IRONRDP_LOG: &str = "IRONRDP_LOG"; + +#[diplomat::bridge] +pub mod ffi { + use super::{setup_logging, INIT_LOG, IRONRDP_LOG_PATH}; + + #[diplomat::opaque] + pub struct Log; + + impl Log { + pub fn init_with_env() { + INIT_LOG.call_once(|| { + let log_file = std::env::var(IRONRDP_LOG_PATH).ok(); + let log_file = log_file.as_deref(); + setup_logging(log_file).expect("Failed to setup logging"); + }); + } + } +} + +fn setup_logging(log_file_path: Option<&str>) -> Result<(), Box> { + use std::{fs::create_dir_all, fs::OpenOptions, path::PathBuf}; + use tracing::metadata::LevelFilter; + use tracing_subscriber::prelude::*; + use tracing_subscriber::EnvFilter; + + let env_filter = EnvFilter::builder() + .with_default_directive(LevelFilter::WARN.into()) + .with_env_var(IRONRDP_LOG) + .from_env_lossy(); + + if let Some(log_file_path) = log_file_path { + let path = PathBuf::from(log_file_path); + if let Some(parent) = path.parent() { + create_dir_all(parent)?; + } + let file = OpenOptions::new().create(true).append(true).open(log_file_path)?; + + let fmt_layer = tracing_subscriber::fmt::layer() + .with_ansi(false) + .with_writer(file) + .compact(); + tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .try_init()?; + } else { + let fmt_layer = tracing_subscriber::fmt::layer() + .compact() + .with_file(true) + .with_line_number(true) + .with_thread_ids(true) + .with_target(false); + tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .try_init()?; + }; + + Ok(()) +}