From 0ec5be5dc439acfd0ecee0719e55ce4049c1d713 Mon Sep 17 00:00:00 2001
From: "irvingouj @ Devolutions"
<139169536+irvingoujAtDevolution@users.noreply.github.com>
Date: Fri, 3 May 2024 02:44:00 -0400
Subject: [PATCH] feat(ffi): API for enabling tracing logs (#452)
---
Cargo.lock | 2 +
ffi/Cargo.toml | 2 +
.../MainWindow.axaml.cs | 2 +
.../Program.cs | 2 +
.../Devolutions.IronRdp/Generated/Log.cs | 71 +++++++++++++++++++
.../Devolutions.IronRdp/Generated/RawLog.cs | 24 +++++++
ffi/src/connector/config.rs | 2 +-
ffi/src/connector/mod.rs | 1 +
ffi/src/lib.rs | 1 +
ffi/src/log.rs | 66 +++++++++++++++++
10 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/Log.cs
create mode 100644 ffi/dotnet/Devolutions.IronRdp/Generated/RawLog.cs
create mode 100644 ffi/src/log.rs
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(())
+}