Functional server start/stop with log output

This commit is contained in:
Pat Hartl 2023-04-13 17:43:26 -05:00
parent c618e05cf0
commit 8f1b013c0a
10 changed files with 231 additions and 9 deletions

View file

@ -2,13 +2,13 @@
{
public class Server : BaseModel
{
public string Name { get; set; }
public string Path { get; set; }
public string Arguments { get; set; }
public string WorkingDirectory { get; set; }
public string OnStartScriptPath { get; set; }
public string OnStopScriptPath { get; set; }
public string Name { get; set; } = "";
public string Path { get; set; } = "";
public string Arguments { get; set; } = "";
public string WorkingDirectory { get; set; } = "";
public string OnStartScriptPath { get; set; } = "";
public string OnStopScriptPath { get; set; } = "";
public bool Autostart { get; set; }
}

View file

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.SignalR;
namespace LANCommander.Hubs
{
public class LoggingHub : Hub
{
public void Log(string logMessage)
{
Clients.All.SendAsync("Log", logMessage);
}
}
}

View file

@ -21,6 +21,7 @@
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.4" />
@ -34,6 +35,8 @@
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.5" />
<PackageReference Include="NLog" Version="5.1.3" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.2.3" />
<PackageReference Include="rix0rrr.BeaconLib" Version="1.0.2" />
<PackageReference Include="swashbuckle" Version="5.6.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

View file

@ -0,0 +1,59 @@
using Microsoft.AspNetCore.SignalR.Client;
namespace LANCommander.Logging
{
public class LoggingHubConnection : IAsyncDisposable
{
private HubConnection? HubConnection;
private string HubUrl;
public LoggingHubConnection(string hubUrl)
{
HubUrl = hubUrl;
}
public async Task Log(string logMessage)
{
await EnsureConnection();
if (HubConnection != null)
await HubConnection.SendAsync("Log", logMessage);
}
public async Task EnsureConnection()
{
if (HubConnection == null)
{
HubConnection = new HubConnectionBuilder()
.WithUrl(HubUrl)
.Build();
await HubConnection.StartAsync();
}
else if (HubConnection.State == HubConnectionState.Disconnected)
{
await HubConnection.StartAsync();
}
}
public async ValueTask DisposeAsync()
{
if (HubConnection != null)
{
try
{
await HubConnection.StopAsync();
await HubConnection.DisposeAsync();
}
catch (Exception ex)
{
NLog.Common.InternalLogger.Error(ex, "Exception in LoggingHubConnection.DisposeAsync");
}
finally
{
HubConnection = null;
}
}
}
}
}

View file

@ -0,0 +1,37 @@
using NLog;
using NLog.Config;
using NLog.Targets;
namespace LANCommander.Logging
{
[Target("LoggingHub")]
public class LoggingHubTarget : AsyncTaskTarget
{
private LoggingHubConnection? Connection;
[RequiredParameter]
public string HubUrl { get; set; }
protected override void InitializeTarget()
{
Connection = new LoggingHubConnection(HubUrl);
}
protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
{
string message = Layout.Render(logEvent);
if (Connection != null)
await Connection.Log(message);
}
protected override async void CloseTarget()
{
if (Connection != null)
{
await Connection.DisposeAsync();
Connection = null;
}
}
}
}

View file

@ -24,6 +24,7 @@
<Space Direction="DirectionVHType.Horizontal">
<SpaceItem>
<Button OnClick="() => Edit(context)">Edit</Button>
<Button OnClick="() => Logs(context)">Logs</Button>
<Button OnClick="() => Start(context)">Start</Button>
<Button OnClick="() => Stop(context)">Stop</Button>
</SpaceItem>
@ -63,6 +64,11 @@
NavigationManager.NavigateTo($"/Servers/{server.Id}/Edit");
}
private void Logs(Server server)
{
NavigationManager.NavigateTo($"/Servers/{server.Id}/Logs");
}
private void Start(Server server)
{
ServerProcessService.StartServer(server);

View file

@ -0,0 +1,42 @@
@page "/Servers/{id:guid}/Logs"
@using Microsoft.AspNetCore.SignalR.Client
@attribute [Authorize]
@inject ServerService ServerService
@inject ServerProcessService ServerProcessService
@inject NavigationManager NavigationManager
@implements IAsyncDisposable
<pre>
@foreach (var message in Messages)
{
@message <br />
}
</pre>
@code {
[Parameter] public Guid Id { get; set; }
HubConnection? HubConnection;
List<string> Messages = new List<string>();
protected override async Task OnInitializedAsync()
{
HubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/hubs/logging"))
.Build();
HubConnection.On<string>("Log", (message) =>
{
Messages.Add(message);
InvokeAsync(StateHasChanged);
});
await HubConnection.StartAsync();
}
public async ValueTask DisposeAsync()
{
if (HubConnection is not null)
await HubConnection.DisposeAsync();
}
}

View file

@ -1,12 +1,14 @@
using BeaconLib;
using LANCommander.Data;
using LANCommander.Data.Models;
using LANCommander.Hubs;
using LANCommander.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using NLog.Web;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
@ -114,6 +116,8 @@ builder.WebHost.UseKestrel(options =>
options.Limits.MaxRequestBodySize = 1024 * 1024 * 150;
});
builder.Host.UseNLog();
var app = builder.Build();
// Configure the HTTP request pipeline.
@ -140,6 +144,8 @@ app.UseAuthorization();
app.UseMvcWithDefaultRoute();
app.MapHub<LoggingHub>("/hubs/logging");
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();

View file

@ -1,10 +1,13 @@
using LANCommander.Data.Models;
using NLog;
using System.Diagnostics;
namespace LANCommander.Services
{
public class ServerProcessService
{
private readonly Logger Logger = LogManager.GetCurrentClassLogger();
public Dictionary<Guid, Process> Processes = new Dictionary<Guid, Process>();
public Dictionary<Guid, int> Threads { get; set; } = new Dictionary<Guid, int>();
@ -15,19 +18,40 @@ namespace LANCommander.Services
process.StartInfo.FileName = server.Path;
process.StartInfo.WorkingDirectory = server.WorkingDirectory;
process.StartInfo.Arguments = server.Arguments;
process.StartInfo.UseShellExecute = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
Logger.Info("Game Server {ServerName} ({ServerId}) Info: {Message}", server.Name, server.Id, e.Data);
});
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{
Logger.Error("Game Server {ServerName} ({ServerId}) Error: {Message}", server.Name, server.Id, e.Data);
});
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
Processes[server.Id] = process;
await process.WaitForExitAsync();
}
public void StopServer(Server server)
{
if (Processes.ContainsKey(server.Id))
Processes[server.Id].Kill();
{
var process = Processes[server.Id];
process.Kill();
}
}
}
}

33
LANCommander/nlog.config Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogFile="internal.log"
internalLogLevel="Info">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<add assembly="LANCommander"/>
</extensions>
<!-- the targets to write to -->
<targets>
<!-- File Target for all log messages with basic details -->
<target xsi:type="File" name="MainLogFile" fileName="${basedir}/Logs/log.txt" archiveEvery="Day" />
<target xsi:type="File" name="GameServerLogFile" fileName="${basedir}/Logs/GameServers/${event-properties:ServerName}.log" archiveEvery="Day" />
<target xsi:type="LoggingHub" name="GameServerHub" hubUrl="http://localhost:1337/hubs/logging" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="MainLogFile,LoggingHub" />
<logger name="LANCommander.Services.ServerProcessService" minlevel="Info" writeTo="GameServerLogFile,GameServerHub" />
<!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
<!--<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
<logger name="*" minlevel="Trace" writeTo="ownFile-web" />-->
</rules>
</nlog>