Skip to content

Commit cc73b74

Browse files
stho32claude
andcommitted
Implement R008: Add comprehensive server logging with Serilog
- Added Serilog.AspNetCore and Serilog.Sinks.File packages - Configured daily rotating log files with 14-day retention - Added structured logging to all server endpoints - Log file path: logs/lnac-server-YYYYMMDD.log - Added request logging middleware - Logs include: client connections, messages sent/received, file operations - Added proper exception handling with fatal error logging - Updated .gitignore to exclude data directory 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent a477bf0 commit cc73b74

File tree

4 files changed

+102
-20
lines changed

4 files changed

+102
-20
lines changed

Source/LocalNetAppChat/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ bld/
3131
[Oo]bj/
3232
[Ll]og/
3333
[Ll]ogs/
34+
data/
3435

3536
# Visual Studio 2015/2017 cache/options directory
3637
.vs/

Source/LocalNetAppChat/LocalNetAppChat.Server.Domain/Messaging/MessagingServiceProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public Result<string> SendMessage(string key, LnacMessage message)
4040
var receivedMessage = _messageProcessors.Process(message.ToReceivedMessage());
4141
_messageList.Add(receivedMessage);
4242

43-
Console.WriteLine($"- [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] queue status {_messageList.GetStatus()}");
43+
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INF] Queue status: {_messageList.GetStatus()}");
4444

4545
return Result<string>.Success("Ok");
4646
}

Source/LocalNetAppChat/LocalNetAppChat.Server/LocalNetAppChat.Server.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
<ProjectReference Include="..\LocalNetAppChat.Server.Domain\LocalNetAppChat.Server.Domain.csproj" />
1313
</ItemGroup>
1414

15+
<ItemGroup>
16+
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
17+
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
18+
</ItemGroup>
19+
1520
<ItemGroup>
1621
<Reference Include="System.ServiceModel.Primitives">
1722
<HintPath>..\..\..\..\..\Users\Entwickler\.nuget\packages\system.servicemodel.primitives\4.10.0\ref\net6.0\System.ServiceModel.Primitives.dll</HintPath>

Source/LocalNetAppChat/LocalNetAppChat.Server/Program.cs

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using LocalNetAppChat.Server.Domain.Messaging.MessageProcessing;
77
using LocalNetAppChat.Server.Domain.Security;
88
using LocalNetAppChat.Server.Domain.StoringFiles;
9+
using Serilog;
10+
using Serilog.Events;
911

1012
var parser = new Parser(
1113
new ICommandLineOption[] {
@@ -39,112 +41,186 @@
3941
$"\n\n [Usage]\n\n" +
4042
$"\n{string.Join("\n", commandsWithDescription)}");
4143

42-
Console.WriteLine("\n\nExamples:\r\n\r\n Start the server with the default settings\r\n $ LocalNetAppChat.Server\r\n - Start the server in HTTPS mode\r\n $ LocalNetAppChat.Server --https\r\n - Start the server with different ip and port and custom key\r\n $ LocalNetAppChat.Server --listenOn \"54.15.12.1\" --port \"54822\" --https --key \"HeythereGithubExample\"");
44+
Console.WriteLine("\n\nExamples:\r\n\r\n Start the server with the default settings\r\n $ LocalNetAppChat.Server\r\n - Start the server in HTTPS mode\r\n $ LocalNetAppChat.Server --https\r\n - Start the server with different ip and port and custom key\r\n $ LocalNetAppChat.Server --listenOn \"54.15.12.1\" --port \"54822\" --https --key \"HeythereGithubExample\"");
4345

4446
return;
4547
}
4648

47-
var serverKey = parser.TryGetOptionWithValue<string>("--key");
48-
var accessControl = new KeyBasedAccessControl(serverKey??string.Empty);
49+
// Configure Serilog
50+
var logsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "logs");
51+
Directory.CreateDirectory(logsDirectory);
52+
53+
Log.Logger = new LoggerConfiguration()
54+
.MinimumLevel.Information()
55+
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
56+
.Enrich.FromLogContext()
57+
.WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}")
58+
.WriteTo.File(
59+
Path.Combine(logsDirectory, "lnac-server-.log"),
60+
rollingInterval: RollingInterval.Day,
61+
retainedFileCountLimit: 14,
62+
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
63+
.CreateLogger();
64+
65+
try
66+
{
67+
Log.Information("Starting LocalNetAppChat Server");
68+
69+
var serverKey = parser.TryGetOptionWithValue<string>("--key");
70+
var accessControl = new KeyBasedAccessControl(serverKey??string.Empty);
71+
72+
var messagingServiceProvider = new MessagingServiceProvider(
73+
accessControl,
74+
MessageProcessorFactory.Get(
75+
new ThreadSafeCounter(),
76+
new DateTimeProvider())
77+
);
4978

50-
var messagingServiceProvider = new MessagingServiceProvider(
51-
accessControl,
52-
MessageProcessorFactory.Get(
53-
new ThreadSafeCounter(),
54-
new DateTimeProvider())
55-
);
79+
var storageServiceProvider = new StorageServiceProvider(
80+
accessControl,
81+
Path.Combine(Directory.GetCurrentDirectory(), "data"));
5682

57-
var storageServiceProvider = new StorageServiceProvider(
58-
accessControl,
59-
Path.Combine(Directory.GetCurrentDirectory(), "data"));
83+
var hostingUrl = HostingUrlGenerator.GenerateUrl(
84+
parser.GetOptionWithValue<string>("--listenOn") ?? "",
85+
parser.GetOptionWithValue<int>("--port"),
86+
parser.GetBoolOption("--https"));
6087

61-
var hostingUrl = HostingUrlGenerator.GenerateUrl(
62-
parser.GetOptionWithValue<string>("--listenOn") ?? "",
63-
parser.GetOptionWithValue<int>("--port"),
64-
parser.GetBoolOption("--https"));
88+
Log.Information("Server configured to listen on {HostingUrl} with key authentication", hostingUrl);
6589

66-
var builder = WebApplication.CreateBuilder(new[] { "--urls", hostingUrl });
90+
var builder = WebApplication.CreateBuilder(new[] { "--urls", hostingUrl });
91+
builder.Host.UseSerilog();
6792
var app = builder.Build();
6893

69-
app.MapGet("/", () => "LocalNetAppChat Server!");
94+
// Add Serilog request logging
95+
app.UseSerilogRequestLogging(options =>
96+
{
97+
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
98+
});
99+
100+
app.MapGet("/", () => "LocalNetAppChat Server!");
70101

71102
app.MapGet("/receive", (string key, string clientName) =>
72103
{
104+
Log.Debug("Client {ClientName} requesting messages", clientName);
73105
var result = messagingServiceProvider.GetMessages(key, clientName);
74106

75107
if (!result.IsSuccess)
76108
{
109+
Log.Warning("Failed to get messages for client {ClientName}: {Error}", clientName, result.Error);
77110
return result.Error;
78111
}
79112

113+
Log.Information("Delivered {MessageCount} messages to client {ClientName}", result.Value.Length, clientName);
80114
return JsonSerializer.Serialize(result.Value);
81115
});
82116

83117
app.MapPost("/send", (string key, LnacMessage message) =>
84118
{
119+
Log.Information("Received message from {ClientName}: {MessageText}", message.Name, message.Text);
85120
var result = messagingServiceProvider.SendMessage(key, message);
86121

87122
if (!result.IsSuccess)
88123
{
124+
Log.Warning("Failed to send message from {ClientName}: {Error}", message.Name, result.Error);
89125
return result.Error;
90126
}
91127

128+
Log.Debug("Message from {ClientName} successfully queued", message.Name);
92129
return result.Value;
93130
});
94131

95132
app.MapPost("/upload", async (HttpRequest request, string key) =>
96133
{
97134
if (!request.HasFormContentType)
135+
{
136+
Log.Warning("Upload request received without form content type");
98137
return Results.BadRequest();
138+
}
99139

100140
var form = await request.ReadFormAsync();
101141

102142
if (form.Files.Any() == false)
143+
{
144+
Log.Warning("Upload request received without files");
103145
return Results.BadRequest("There are no files");
146+
}
104147

105148
var file = form.Files.FirstOrDefault();
106149

107150
if (file is null || file.Length == 0)
151+
{
152+
Log.Warning("Upload request received with empty file");
108153
return Results.BadRequest("File cannot be empty");
154+
}
109155

156+
Log.Information("Uploading file {FileName} ({FileSize} bytes)", file.FileName, file.Length);
110157
var result = await storageServiceProvider.Upload(key,
111158
file.FileName, file.OpenReadStream());
112159

113160
if (!result.IsSuccess)
161+
{
162+
Log.Error("Failed to upload file {FileName}: {Error}", file.FileName, result.Error);
114163
return Results.BadRequest(result.Error);
164+
}
115165

166+
Log.Information("Successfully uploaded file {FileName}", file.FileName);
116167
return Results.Content(result.Value);
117168
});
118169

119170

120171
app.MapGet("/listallfiles", (string key) =>
121172
{
173+
Log.Debug("Listing all files request received");
122174
var result = storageServiceProvider.GetFiles(key);
123175

124176
if (!result.IsSuccess)
177+
{
178+
Log.Warning("Failed to list files: {Error}", result.Error);
125179
return Results.BadRequest(result.Error);
180+
}
126181

182+
Log.Information("Listed {FileCount} files", result.Value.Length);
127183
return Results.Json(result.Value);
128184
});
129185

130186
app.MapGet("/download", async (HttpRequest _, string key, string filename) =>
131187
{
188+
Log.Information("Download request for file {FileName}", filename);
132189
var result = await storageServiceProvider.Download(key, filename);
133190

134191
if (!result.IsSuccess)
192+
{
193+
Log.Warning("Failed to download file {FileName}: {Error}", filename, result.Error);
135194
return Results.BadRequest(result.Error);
195+
}
136196

197+
Log.Information("Successfully downloaded file {FileName}", filename);
137198
return Results.File(result.Value, fileDownloadName: filename);
138199
});
139200

140201
app.MapPost("/deletefile", (HttpRequest _, string filename, string key) =>
141202
{
203+
Log.Information("Delete request for file {FileName}", filename);
142204
var result = storageServiceProvider.Delete(key, filename);
143205

144206
if (!result.IsSuccess)
207+
{
208+
Log.Warning("Failed to delete file {FileName}: {Error}", filename, result.Error);
145209
return Results.BadRequest(result.Error);
210+
}
146211

212+
Log.Information("Successfully deleted file {FileName}", filename);
147213
return Results.Content(result.Value);
148214
});
149215

150-
app.Run();
216+
Log.Information("Server started successfully");
217+
app.Run();
218+
}
219+
catch (Exception ex)
220+
{
221+
Log.Fatal(ex, "Server terminated unexpectedly");
222+
}
223+
finally
224+
{
225+
Log.CloseAndFlush();
226+
}

0 commit comments

Comments
 (0)