Skip to content

Add bug-report / log-upload endpoint (anonymous POST + admin retrieval) - #251

Open
itpick wants to merge 1 commit into
timiimit:masterfrom
ut4-hub:feature/bug-report-logs
Open

Add bug-report / log-upload endpoint (anonymous POST + admin retrieval)#251
itpick wants to merge 1 commit into
timiimit:masterfrom
ut4-hub:feature/bug-report-logs

Conversation

@itpick

@itpick itpick commented Aug 1, 2026

Copy link
Copy Markdown

What

Adds an in-game bug-report / log-upload ingest so players can send a log (plus an optional name + note) and get back a short Report ID, without leaving the game. Admins can list and download the reports.

Endpoints

  • POST ut/api/logsanonymous, multipart/form-data:
    • fields player (optional), note (optional); file part log
    • 25 MB cap, per-IP rate limit (10/hour, in-memory sliding window)
    • client IP stored only as a SHA-256 hash; body stored in GridFS (bucket bugreports) with a LogReports metadata doc + an 8-char Crockford Report ID
    • returns {"reportId":"XXXXXXXX"}
  • GET ut/api/logs — admin only, paged metadata list (no bodies)
  • GET ut/api/logs/{reportId} — admin only, streams the stored log

Notes

  • New LogsController + LogReportService (GridFS + indexes) + LogReportRateLimitService + LogReport model + DTOs + LogReportSettings (bucket/size/limit tunables, sensible defaults).
  • Wired into DI and startup index creation; adds MongoDB.Driver.GridFS at the same version as the existing MongoDB.Driver.
  • Admin endpoints reuse the existing bearer auth + ACL_Maintenance/Admin check.
  • Anonymous POST is size-capped, rate-limited, and never echoes stored content; the two GETs require admin auth.

Built + running in production on a downstream deployment.

Lets the game client send a log (plus optional player name + note) and get back a
short Report ID, so players can report bugs and crashes without leaving the game.

- POST ut/api/logs — anonymous, multipart/form-data (fields "player", "note";
  file part "log"). 25 MB cap, per-IP rate limit (10/hour, in-memory sliding
  window), client IP stored only as a SHA-256 hash. Body stored in GridFS (bucket
  "bugreports") with a LogReports metadata collection and an 8-char Report ID.
  Returns {"reportId":"XXXXXXXX"}.
- GET ut/api/logs — admin only, paged metadata list (no bodies).
- GET ut/api/logs/{reportId} — admin only, streams the stored log.

New LogsController + LogReportService (GridFS + indexes) + LogReportRateLimitService
+ LogReport model + DTOs + LogReportSettings (bucket/size/limit tunables with
defaults). Wired into DI and startup index creation. Adds MongoDB.Driver.GridFS
(same version as the existing MongoDB.Driver). Admin endpoints reuse the existing
bearer auth + ACL_Maintenance/Admin check.

@Saibamen Saibamen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not latest version

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an anonymous in-game log upload (“bug report”) flow backed by MongoDB GridFS, plus admin-only endpoints to list and download uploaded logs. This fits into the existing UT API surface by introducing a new controller + services and wiring them into DI/startup index creation.

Changes:

  • Adds POST ut/api/logs (anonymous multipart upload) returning an 8-char report ID, plus admin-only GET ut/api/logs and GET ut/api/logs/{reportId} retrieval endpoints.
  • Implements LogReportService (GridFS + metadata collection + indexes) and an in-memory per-IP (hashed) sliding-window rate limiter.
  • Wires new settings (LogReportSettings), DI registrations, startup index creation, and adds the GridFS driver package.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
UT4MasterServer/Program.cs Registers LogReportSettings, LogReportService, and LogReportRateLimitService in DI.
UT4MasterServer/Controllers/UT/LogsController.cs Implements upload + admin listing/download endpoints and request validation.
UT4MasterServer.Services/UT4MasterServer.Services.csproj Adds MongoDB.Driver.GridFS package reference.
UT4MasterServer.Services/Singleton/LogReportRateLimitService.cs Adds in-memory sliding-window rate limiting for anonymous uploads.
UT4MasterServer.Services/Scoped/LogReportService.cs Adds GridFS-backed persistence and metadata/index management for reports.
UT4MasterServer.Services/Hosted/ApplicationStartupService.cs Ensures new MongoDB indexes are created on startup.
UT4MasterServer.Models/Settings/LogReportSettings.cs Adds configurable bucket name, max size, and rate-limit parameters.
UT4MasterServer.Models/DTO/Response/LogReportResponse.cs Adds admin-facing metadata response DTO for listing reports.
UT4MasterServer.Models/DTO/Response/LogReportCreatedResponse.cs Adds upload response DTO containing reportId.
UT4MasterServer.Models/Database/LogReport.cs Adds MongoDB model for log report metadata (GridFS file reference + fields).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +132 to +133
if (skip < 0) skip = 0;
if (limit < 1 || limit > 100) limit = 100;
Comment on lines +190 to +193
private static bool IsValidReportID(string reportID)
{
return reportID.Length == 8 && reportID.All(x => x is (>= '0' and <= '9') or (>= 'A' and <= 'Z'));
}
Comment on lines +11 to +28
private const int CleanupThreshold = 1024;

private readonly Dictionary<string, List<DateTime>> uploadsPerClient = new();

/// <summary>
/// Records an upload attempt for <paramref name="clientKey"/> and returns
/// whether the attempt is allowed to proceed.
/// </summary>
public bool TryRecordUpload(string clientKey, int maxUploads, TimeSpan window)
{
var now = DateTime.UtcNow;

lock (uploadsPerClient) // Make sure counters are thread-safe
{
if (uploadsPerClient.Count >= CleanupThreshold)
{
RemoveStaleClients(now, window);
}
Comment on lines +195 to +199
private static string HashIP(IPAddress ip)
{
var hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(ip.ToString()));
return Convert.ToHexString(hashedBytes).ToLower();
}
Comment on lines +61 to +85
var reportID = await GenerateUniqueReportIDAsync();

ObjectId fileID = await bucket.UploadFromStreamAsync(reportID, logStream, new GridFSUploadOptions()
{
Metadata = new BsonDocument()
{
{ "ReportID", reportID },
{ "ContentType", contentType }
}
});

var report = new LogReport()
{
ReportID = reportID,
FileID = fileID,
Player = player,
Note = note,
ClientIPHash = clientIPHash,
UserAgent = userAgent,
CreatedAt = DateTime.UtcNow,
Length = length,
ContentType = contentType
};

await logReportCollection.InsertOneAsync(report);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants