mirror of
https://github.com/Stone-Red-Code/RemoteExec.git
synced 2026-09-04 00:56:17 +02:00
Improve logging
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace RemoteExec.Client;
|
||||||
|
|
||||||
|
internal class RemoteExecLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private bool disposedValue;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
public RemoteExecLoggerProvider(ILogger logger)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
// No managed or unmanaged resources to dispose
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
|
||||||
using RemoteExec.Shared;
|
using RemoteExec.Shared;
|
||||||
|
|
||||||
@@ -19,18 +20,35 @@ public class RemoteExecutor : IDisposable
|
|||||||
private CancellationTokenSource distributorCts = new();
|
private CancellationTokenSource distributorCts = new();
|
||||||
private Task? distributorTask;
|
private Task? distributorTask;
|
||||||
private readonly LoadBalancingStrategy loadBalancingStrategy;
|
private readonly LoadBalancingStrategy loadBalancingStrategy;
|
||||||
|
private readonly ILogger logger;
|
||||||
private bool disposedValue;
|
private bool disposedValue;
|
||||||
|
|
||||||
public event EventHandler<ServerMetricsUpdatedEventArgs>? MetricsUpdated;
|
public event EventHandler<ServerMetricsUpdatedEventArgs>? MetricsUpdated;
|
||||||
|
|
||||||
public RemoteExecutor(string url) : this([url], LoadBalancingStrategy.ResourceAware)
|
public RemoteExecutor(string url) : this([url], LoadBalancingStrategy.ResourceAware, NullLogger.Instance)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteExecutor(string[] urls, LoadBalancingStrategy loadBalancingStrategy = LoadBalancingStrategy.ResourceAware)
|
public RemoteExecutor(string url, ILogger logger) : this([url], LoadBalancingStrategy.ResourceAware, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteExecutor(string url, LoadBalancingStrategy loadBalancingStrategy) : this([url], loadBalancingStrategy, NullLogger.Instance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteExecutor(string[] urls) : this(urls, LoadBalancingStrategy.ResourceAware, NullLogger.Instance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteExecutor(string[] urls, LoadBalancingStrategy loadBalancingStrategy) : this(urls, loadBalancingStrategy, NullLogger.Instance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteExecutor(string[] urls, LoadBalancingStrategy loadBalancingStrategy, ILogger logger)
|
||||||
{
|
{
|
||||||
this.loadBalancingStrategy = loadBalancingStrategy;
|
this.loadBalancingStrategy = loadBalancingStrategy;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
foreach (string url in urls)
|
foreach (string url in urls)
|
||||||
{
|
{
|
||||||
@@ -42,7 +60,7 @@ public class RemoteExecutor : IDisposable
|
|||||||
.WithAutomaticReconnect()
|
.WithAutomaticReconnect()
|
||||||
.ConfigureLogging(logging =>
|
.ConfigureLogging(logging =>
|
||||||
{
|
{
|
||||||
_ = logging.AddProvider(new RemoteExecLoggerProvider());
|
_ = logging.AddProvider(new RemoteExecLoggerProvider(logger));
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -106,6 +124,8 @@ public class RemoteExecutor : IDisposable
|
|||||||
|
|
||||||
public async Task StopAsync(CancellationToken cancellationToken = default)
|
public async Task StopAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
logger.LogInformation("Stopping RemoteExecutor...");
|
||||||
|
|
||||||
await distributorCts.CancelAsync();
|
await distributorCts.CancelAsync();
|
||||||
|
|
||||||
if (distributorTask != null)
|
if (distributorTask != null)
|
||||||
@@ -113,13 +133,15 @@ public class RemoteExecutor : IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await distributorTask;
|
await distributorTask;
|
||||||
|
logger.LogDebug("Distributor task completed successfully");
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
// Expected
|
logger.LogError(ex, "Distributor task was canceled");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.LogDebug("Completing task channels for {ServerCount} servers", servers.Count);
|
||||||
foreach (ServerConnection server in servers)
|
foreach (ServerConnection server in servers)
|
||||||
{
|
{
|
||||||
server.TaskChannel.Writer.Complete();
|
server.TaskChannel.Writer.Complete();
|
||||||
@@ -133,6 +155,7 @@ public class RemoteExecutor : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(stopTasks);
|
await Task.WhenAll(stopTasks);
|
||||||
|
logger.LogInformation("RemoteExecutor stopped successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, ServerMetrics> GetCurrentServerMetrics()
|
public Dictionary<string, ServerMetrics> GetCurrentServerMetrics()
|
||||||
@@ -315,34 +338,3 @@ public class RemoteExecutor : IDisposable
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class RemoteExecLoggerProvider : ILoggerProvider
|
|
||||||
{
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
|
||||||
{
|
|
||||||
return new RemoteExecLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class RemoteExecLogger : ILogger
|
|
||||||
{
|
|
||||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"[{logLevel}] {formatter(state, exception)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,9 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
|
|
||||||
private static readonly ConcurrentDictionary<Guid, TaskCompletionSource<byte[]>> pendingAssemblyRequests = new();
|
private static readonly ConcurrentDictionary<Guid, TaskCompletionSource<byte[]>> pendingAssemblyRequests = new();
|
||||||
|
|
||||||
|
// Track pending assembly requests per connection to avoid duplicate requests
|
||||||
|
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, Task<byte[]>>> pendingAssemblyRequestsByConnection = new();
|
||||||
|
|
||||||
private static ServerMetrics? lastMetrics;
|
private static ServerMetrics? lastMetrics;
|
||||||
private static DateTime lastMetricsTimestamp;
|
private static DateTime lastMetricsTimestamp;
|
||||||
private static TimeSpan lastTotalProcessorTime;
|
private static TimeSpan lastTotalProcessorTime;
|
||||||
@@ -28,6 +31,7 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
RemoteJobAssemblyLoadContext assemblyLoadContext = new RemoteJobAssemblyLoadContext($"RemoteJob_{Guid.NewGuid()}");
|
RemoteJobAssemblyLoadContext assemblyLoadContext = new RemoteJobAssemblyLoadContext($"RemoteJob_{Guid.NewGuid()}");
|
||||||
|
|
||||||
_ = connections.TryAdd(Context.ConnectionId, assemblyLoadContext);
|
_ = connections.TryAdd(Context.ConnectionId, assemblyLoadContext);
|
||||||
|
_ = pendingAssemblyRequestsByConnection.TryAdd(Context.ConnectionId, new ConcurrentDictionary<string, Task<byte[]>>());
|
||||||
|
|
||||||
logger.LogInformation("Connection {ConnectionId} established", Context.ConnectionId);
|
logger.LogInformation("Connection {ConnectionId} established", Context.ConnectionId);
|
||||||
|
|
||||||
@@ -42,6 +46,8 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
logger.LogInformation("Connection {ConnectionId} disconnected", Context.ConnectionId);
|
logger.LogInformation("Connection {ConnectionId} disconnected", Context.ConnectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = pendingAssemblyRequestsByConnection.TryRemove(Context.ConnectionId, out _);
|
||||||
|
|
||||||
return base.OnDisconnectedAsync(exception);
|
return base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +105,6 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
{
|
{
|
||||||
_ = Interlocked.Increment(ref activeTasks);
|
_ = Interlocked.Increment(ref activeTasks);
|
||||||
|
|
||||||
logger.LogInformation(req.MethodName);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!connections.TryGetValue(Context.ConnectionId, out RemoteJobAssemblyLoadContext? assemblyLoadContext))
|
if (!connections.TryGetValue(Context.ConnectionId, out RemoteJobAssemblyLoadContext? assemblyLoadContext))
|
||||||
@@ -282,17 +286,36 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Guid guid = Guid.NewGuid();
|
if (!pendingAssemblyRequestsByConnection.TryGetValue(Context.ConnectionId, out ConcurrentDictionary<string, Task<byte[]>>? connectionPendingRequests))
|
||||||
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
|
{
|
||||||
|
throw new InvalidOperationException("Connection not found");
|
||||||
|
}
|
||||||
|
|
||||||
_ = pendingAssemblyRequests.TryAdd(guid, tcs);
|
Task<byte[]> assemblyBytesTask = connectionPendingRequests.GetOrAdd(assemblyName, key =>
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Guid guid = Guid.NewGuid();
|
||||||
|
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
|
||||||
|
|
||||||
await Clients.Caller.SendAsync("RequestAssembly", assemblyName, guid);
|
_ = pendingAssemblyRequests.TryAdd(guid, tcs);
|
||||||
|
|
||||||
// Wait for the assembly with a timeout
|
await Clients.Caller.SendAsync("RequestAssembly", key, guid);
|
||||||
byte[] assemblyBytes = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(30));
|
|
||||||
|
// Wait for the assembly with a timeout
|
||||||
|
return await tcs.Task.WaitAsync(TimeSpan.FromSeconds(30));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = connectionPendingRequests.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
byte[] assemblyBytes = await assemblyBytesTask;
|
||||||
|
|
||||||
// Return a temporary assembly just for metadata inspection
|
|
||||||
using MemoryStream ms = new MemoryStream(assemblyBytes);
|
using MemoryStream ms = new MemoryStream(assemblyBytes);
|
||||||
return Assembly.Load(assemblyBytes);
|
return Assembly.Load(assemblyBytes);
|
||||||
}
|
}
|
||||||
@@ -307,16 +330,34 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
|
|||||||
{
|
{
|
||||||
string assemblyName = assembly.GetName().FullName!;
|
string assemblyName = assembly.GetName().FullName!;
|
||||||
|
|
||||||
Guid guid = Guid.NewGuid();
|
if (!pendingAssemblyRequestsByConnection.TryGetValue(Context.ConnectionId, out ConcurrentDictionary<string, Task<byte[]>>? connectionPendingRequests))
|
||||||
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
|
{
|
||||||
|
throw new InvalidOperationException("Connection not found");
|
||||||
|
}
|
||||||
|
|
||||||
_ = pendingAssemblyRequests.TryAdd(guid, tcs);
|
Task<byte[]> assemblyBytesTask = connectionPendingRequests.GetOrAdd(assemblyName, key =>
|
||||||
|
{
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Guid guid = Guid.NewGuid();
|
||||||
|
TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>();
|
||||||
|
|
||||||
await Clients.Caller.SendAsync("RequestAssembly", assemblyName, guid);
|
_ = pendingAssemblyRequests.TryAdd(guid, tcs);
|
||||||
|
|
||||||
byte[] assemblyBytes = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(30));
|
await Clients.Caller.SendAsync("RequestAssembly", key, guid);
|
||||||
|
|
||||||
return assemblyBytes;
|
return await tcs.Task.WaitAsync(TimeSpan.FromSeconds(30));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_ = connectionPendingRequests.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return await assemblyBytesTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PreLoadReferencedAssembliesAsync(RemoteJobAssemblyLoadContext assemblyLoadContext, Assembly assembly)
|
private async Task PreLoadReferencedAssembliesAsync(RemoteJobAssemblyLoadContext assemblyLoadContext, Assembly assembly)
|
||||||
|
|||||||
Reference in New Issue
Block a user