From 32ece293edcbecaf6d22b5257929bb71cb1b2ebf Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Fri, 20 Jun 2025 19:43:47 +0200 Subject: [PATCH] Added Elmah, Serilog to Server, improved logging --- .gitignore | 3 +- docs/Indexer.md | 5 +-- src/Server/Controllers/EntityController.cs | 13 ++++--- .../Controllers/SearchdomainController.cs | 22 ++++++++--- src/Server/Program.cs | 38 ++++++++++++++++++- src/Server/SearchdomainManager.cs | 11 +++++- src/Server/Server.csproj | 3 ++ src/Server/appsettings.Development.json | 7 ++++ src/Server/appsettings.json | 20 ++++++++++ 9 files changed, 105 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index a7bd320..fef9f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ src/Models/obj src/Indexer/bin src/Indexer/obj src/Indexer/Scripts/__pycache__ -src/Indexer/logs \ No newline at end of file +src/Indexer/logs +src/Server/logs \ No newline at end of file diff --git a/docs/Indexer.md b/docs/Indexer.md index 2f69d42..0418338 100644 --- a/docs/Indexer.md +++ b/docs/Indexer.md @@ -9,9 +9,8 @@ The indexer by default ## Docker installation (On Linux you might need root privileges, thus use `sudo` where necessary) 1. Navigate to the `src` directory -2. Build the docker container: `docker build -t indexer -f Indexer/Dockerfile .` -3. Run the docker container: `docker run --net=host -t indexer` (the `-t` is optional, but you get more meaningful output) - +2. Build the docker container: `docker build -t embeddingsearch-indexer -f Indexer/Dockerfile .` +3. Run the docker container: `docker run --net=host -t embeddingsearch-indexer` (the `-t` is optional, but you get more meaningful output. Or use `-d` to run it in the background) ## Installing the dependencies ## Ubuntu 24.04 1. Install the .NET SDK: `sudo apt update && sudo apt install dotnet-sdk-8.0 -y` diff --git a/src/Server/Controllers/EntityController.cs b/src/Server/Controllers/EntityController.cs index b3bfe6f..50939f2 100644 --- a/src/Server/Controllers/EntityController.cs +++ b/src/Server/Controllers/EntityController.cs @@ -28,6 +28,7 @@ public class EntityController : ControllerBase searchdomain_ = _domainManager.GetSearchdomain(searchdomain); } catch (Exception) { + _logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]); // TODO DRY violation; perhaps move this logging to the SearchdomainManager? return Ok(new EntityQueryResults() {Results = []}); } var results = searchdomain_.Search(query); @@ -47,9 +48,9 @@ public class EntityController : ControllerBase { searchdomain_ = _domainManager.GetSearchdomain(searchdomain); } - catch (Exception ex) + catch (Exception) { - _logger.LogError("Unable to retrieve the searchdomain", [ex]); + _logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]); return Ok(new EntityIndexResult() { Success = false }); } List? entities = searchdomain_.EntitiesFromJSON(JsonSerializer.Serialize(jsonEntity)); @@ -73,9 +74,9 @@ public class EntityController : ControllerBase try { searchdomain_ = _domainManager.GetSearchdomain(searchdomain); - } catch (Exception ex) + } catch (Exception) { - _logger.LogError("Unable to retrieve the searchdomain", [ex]); + _logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]); return Ok(new EntityListResults() { Results = [], Success = false }); } EntityListResults entityListResults = new() {Results = [], Success = true}; @@ -115,7 +116,7 @@ public class EntityController : ControllerBase } [HttpGet("Delete")] - public ActionResult Delete(string searchdomain, string entityName) // TODO test this + public ActionResult Delete(string searchdomain, string entityName) { Searchdomain searchdomain_; try @@ -123,11 +124,13 @@ public class EntityController : ControllerBase searchdomain_ = _domainManager.GetSearchdomain(searchdomain); } catch (Exception) { + _logger.LogError("Unable to delete the entity {entityName} in {searchdomain} - the searchdomain likely does not exist", [entityName, searchdomain]); return Ok(new EntityDeleteResults() {Success = false}); } Entity? entity_ = searchdomain_.GetEntity(entityName); if (entity_ is null) { + _logger.LogError("Unable to delete the entity {entityName} in {searchdomain} - it was not found under the specified name", [entityName, searchdomain]); return Ok(new EntityDeleteResults() {Success = false}); } searchdomain_.RemoveEntity(entityName); diff --git a/src/Server/Controllers/SearchdomainController.cs b/src/Server/Controllers/SearchdomainController.cs index 819586b..e682b1f 100644 --- a/src/Server/Controllers/SearchdomainController.cs +++ b/src/Server/Controllers/SearchdomainController.cs @@ -21,8 +21,16 @@ public class SearchdomainController : ControllerBase [HttpGet("List")] public ActionResult List() { - var results = _domainManager.ListSearchdomains() - ?? throw new Exception("Unable to list searchdomains"); + List results; + try + { + results = _domainManager.ListSearchdomains(); + } + catch (Exception) + { + _logger.LogError("Unable to list searchdomains"); + throw; + } SearchdomainListResults searchdomainListResults = new() {Searchdomains = results}; return Ok(searchdomainListResults); } @@ -36,7 +44,8 @@ public class SearchdomainController : ControllerBase return Ok(new SearchdomainCreateResults(){Id = id, Success = true}); } catch (Exception) { - return Ok(new SearchdomainCreateResults(){Id = null, Success = false}); + _logger.LogError("Unable to create Searchdomain {searchdomain}", [searchdomain]); + return Ok(new SearchdomainCreateResults() { Id = null, Success = false }); } } @@ -49,9 +58,9 @@ public class SearchdomainController : ControllerBase { success = true; deletedEntries = _domainManager.DeleteSearchdomain(searchdomain); - } catch (Exception ex) + } catch (Exception) { - Console.WriteLine(ex); + _logger.LogError("Unable to delete searchdomain {searchdomain}", [searchdomain]); success = false; deletedEntries = 0; } @@ -73,7 +82,8 @@ public class SearchdomainController : ControllerBase searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name, settings = @settings WHERE id = @id", parameters); } catch (Exception) { - return Ok(new SearchdomainUpdateResults(){Success = false}); + _logger.LogError("Unable to update searchdomain {searchdomain}", [searchdomain]); + return Ok(new SearchdomainUpdateResults() { Success = false }); } return Ok(new SearchdomainUpdateResults(){Success = true}); } diff --git a/src/Server/Program.cs b/src/Server/Program.cs index fd5c79e..4d8a90b 100644 --- a/src/Server/Program.cs +++ b/src/Server/Program.cs @@ -1,3 +1,6 @@ +using ElmahCore; +using ElmahCore.Mvc; +using Serilog; using Server; var builder = WebApplication.CreateBuilder(args); @@ -8,16 +11,49 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); - +Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(builder.Configuration) + .CreateLogger(); +builder.Logging.AddSerilog(); builder.Services.AddSingleton(); +builder.Services.AddElmah(Options => +{ + Options.LogPath = builder.Configuration.GetValue("Embeddingsearch:Elmah:LogFolder") ?? "~/logs"; +}); + var app = builder.Build(); +List? allowedIps = builder.Configuration.GetSection("Embeddingsearch:Elmah:AllowedHosts") + .Get>(); + +app.Use(async (context, next) => +{ + if (context.Request.Path.StartsWithSegments("/elmah")) + { + var remoteIp = context.Connection.RemoteIpAddress?.ToString(); + bool blockRequest = allowedIps is null + || remoteIp is null + || !allowedIps.Contains(remoteIp); + if (blockRequest) + { + context.Response.StatusCode = 403; + await context.Response.WriteAsync("Forbidden"); + return; + } + } + + await next(); +}); + +app.UseElmah(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); + //app.UseElmahExceptionPage(); // Messes with JSON response for API calls. Leaving this here so I don't accidentally put this in again later on. } else { diff --git a/src/Server/SearchdomainManager.cs b/src/Server/SearchdomainManager.cs index 372145c..a8c8050 100644 --- a/src/Server/SearchdomainManager.cs +++ b/src/Server/SearchdomainManager.cs @@ -32,7 +32,15 @@ public class SearchdomainManager connection = new MySqlConnection(connectionString); connection.Open(); helper = new SQLHelper(connection); - DatabaseMigrations.Migrate(helper); + try + { + DatabaseMigrations.Migrate(helper); + } + catch (Exception ex) + { + _logger.LogCritical("Unable to migrate the database due to the exception: {ex}", [ex.Message]); + throw; + } } public Searchdomain GetSearchdomain(string searchdomain) @@ -73,6 +81,7 @@ public class SearchdomainManager { if (searchdomains.TryGetValue(searchdomain, out Searchdomain? value)) { + _logger.LogError("Searchdomain {searchdomain} could not be created, as it already exists", [searchdomain]); throw new Exception("Searchdomain already exists"); // TODO create proper SearchdomainAlreadyExists exception } Dictionary parameters = new() diff --git a/src/Server/Server.csproj b/src/Server/Server.csproj index 2d00256..ffb8eb7 100644 --- a/src/Server/Server.csproj +++ b/src/Server/Server.csproj @@ -7,6 +7,9 @@ + + + diff --git a/src/Server/appsettings.Development.json b/src/Server/appsettings.Development.json index 4ca93cb..6d2a5ea 100644 --- a/src/Server/appsettings.Development.json +++ b/src/Server/appsettings.Development.json @@ -17,6 +17,13 @@ "ConnectionStrings": { "SQL": "server=localhost;database=embeddingsearch;uid=embeddingsearch;pwd=somepassword!;" }, + "Elmah": { + "AllowedHosts": [ + "127.0.0.1", + "::1", + "172.17.0.1" + ] + }, "OllamaURL": "http://localhost:11434", "ApiKeys": ["Some UUID here", "Another UUID here"], "UseHttpsRedirection": true diff --git a/src/Server/appsettings.json b/src/Server/appsettings.json index 4d56694..54557c5 100644 --- a/src/Server/appsettings.json +++ b/src/Server/appsettings.json @@ -5,5 +5,25 @@ "Microsoft.AspNetCore": "Warning" } }, + "Serilog": { + "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], + "MinimumLevel": "Information", + "WriteTo": [ + { "Name": "Console" }, + { "Name": "File", "Args": { "path": "logs/log.txt", "rollingInterval": "Day", "retainedFileCountLimit": 7 } } + ], + "Properties": { + "Application": "Indexer" + } + }, + "EmbeddingsearchIndexer": { + "Elmah": { + "AllowedHosts": [ + "127.0.0.1", + "::1" + ], + "LogFolder": "./logs" + } + }, "AllowedHosts": "*" }