From 3f9875cb1a460916c29c258839fb31430c044bca Mon Sep 17 00:00:00 2001 From: MasterDraco Date: Wed, 12 Mar 2025 22:01:02 +0000 Subject: [PATCH] Simplify Program.cs and LoggingService to remove database dependencies --- src/Api/Program.cs | 94 +++------------- src/Services/LoggingService.cs | 189 +++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 77 deletions(-) create mode 100644 src/Services/LoggingService.cs diff --git a/src/Api/Program.cs b/src/Api/Program.cs index cfe8023..31463ef 100644 --- a/src/Api/Program.cs +++ b/src/Api/Program.cs @@ -1,72 +1,38 @@ using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.IO; -using System.Linq; -using System.Threading.Tasks; using TransmissionRssManager.Core; -using TransmissionRssManager.Data; -using TransmissionRssManager.Data.Models; -using TransmissionRssManager.Data.Repositories; using TransmissionRssManager.Services; -using Serilog; -using Serilog.Events; - -// Configure Serilog -var logsDirectory = Path.Combine(AppContext.BaseDirectory, "logs"); -Directory.CreateDirectory(logsDirectory); - -Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) - .Enrich.FromLogContext() - .WriteTo.Console() - .WriteTo.File( - Path.Combine(logsDirectory, "log-.txt"), - rollingInterval: RollingInterval.Day, - retainedFileCountLimit: 31, - fileSizeLimitBytes: 10 * 1024 * 1024) - .CreateLogger(); var builder = WebApplication.CreateBuilder(args); -// Add Serilog to the application -builder.Host.UseSerilog(); +// Add logging +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); + +// Create logs directory for file logging +var logsDirectory = Path.Combine(AppContext.BaseDirectory, "logs"); +Directory.CreateDirectory(logsDirectory); // Add services to the container builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -// Configure database -var connectionString = builder.Configuration["ConnectionStrings:DefaultConnection"] - ?? "Host=localhost;Database=torrentmanager;Username=postgres;Password=postgres"; -builder.Services.AddDbContext(options => - options.UseNpgsql(connectionString)); +// Add custom services as singletons for simplicity +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); -// Register repositories -builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -// Add custom services (now scoped instead of singleton to work with DbContext) -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); - -// Add background services (these remain singleton) +// Add background services builder.Services.AddHostedService(); builder.Services.AddHostedService(); -builder.Services.AddHostedService(); -// MetricsBackgroundService is not needed with the simplified implementation var app = builder.Build(); @@ -82,38 +48,12 @@ app.UseRouting(); app.UseAuthorization(); app.MapControllers(); -// Initialize database and run migrations -using (var scope = app.Services.CreateScope()) -{ - var services = scope.ServiceProvider; - try - { - var context = services.GetRequiredService(); - context.Database.Migrate(); - - // Run data migration from file-based storage if needed - if (!context.UserPreferences.Any()) - { - var migrationService = services.GetRequiredService(); - await migrationService.MigrateDataAsync(); - } - } - catch (Exception ex) - { - var logger = services.GetRequiredService>(); - logger.LogError(ex, "An error occurred during database initialization or migration"); - } -} - try { await app.RunAsync(); } catch (Exception ex) { - Log.Fatal(ex, "Application terminated unexpectedly"); -} -finally -{ - Log.CloseAndFlush(); + var logger = app.Services.GetRequiredService>(); + logger.LogError(ex, "Application terminated unexpectedly"); } \ No newline at end of file diff --git a/src/Services/LoggingService.cs b/src/Services/LoggingService.cs new file mode 100644 index 0000000..abb606c --- /dev/null +++ b/src/Services/LoggingService.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using TransmissionRssManager.Core; + +namespace TransmissionRssManager.Services +{ + public interface ILoggingService + { + void Configure(UserPreferences preferences); + Task> GetLogsAsync(LogFilterOptions options); + Task ClearLogsAsync(DateTime? olderThan = null); + Task ExportLogsAsync(LogFilterOptions options); + void Log(LogLevel level, string message, string context = null, Dictionary properties = null); + } + + public class LogFilterOptions + { + public string Level { get; set; } = "All"; + public string Search { get; set; } = ""; + public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } + public string Context { get; set; } = ""; + public int Limit { get; set; } = 100; + public int Offset { get; set; } = 0; + } + + public class LoggingService : ILoggingService + { + private readonly ILogger _logger; + private readonly string _logFilePath; + private readonly object _logLock = new object(); + private List _inMemoryLogs = new List(); + private readonly int _maxLogEntries = 1000; + + public LoggingService(ILogger logger) + { + _logger = logger; + + // Prepare log directory and file + var logsDirectory = Path.Combine(AppContext.BaseDirectory, "logs"); + Directory.CreateDirectory(logsDirectory); + _logFilePath = Path.Combine(logsDirectory, "application_logs.json"); + + // Initialize log file if it doesn't exist + if (!File.Exists(_logFilePath)) + { + File.WriteAllText(_logFilePath, "[]"); + } + + // Load existing logs into memory + try + { + var json = File.ReadAllText(_logFilePath); + _inMemoryLogs = JsonSerializer.Deserialize>(json) ?? new List(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to load logs from file"); + _inMemoryLogs = new List(); + } + } + + public void Configure(UserPreferences preferences) + { + // No-op in simplified version + } + + public Task> GetLogsAsync(LogFilterOptions options) + { + var filteredLogs = _inMemoryLogs.AsEnumerable(); + + // Apply filters + if (!string.IsNullOrEmpty(options.Level) && options.Level != "All") + { + filteredLogs = filteredLogs.Where(l => l.Level == options.Level); + } + + if (!string.IsNullOrEmpty(options.Search)) + { + filteredLogs = filteredLogs.Where(l => + l.Message.Contains(options.Search, StringComparison.OrdinalIgnoreCase)); + } + + if (options.StartDate.HasValue) + { + filteredLogs = filteredLogs.Where(l => l.Timestamp >= options.StartDate.Value); + } + + if (options.EndDate.HasValue) + { + filteredLogs = filteredLogs.Where(l => l.Timestamp <= options.EndDate.Value); + } + + if (!string.IsNullOrEmpty(options.Context)) + { + filteredLogs = filteredLogs.Where(l => l.Context == options.Context); + } + + // Sort, paginate and return + return Task.FromResult( + filteredLogs + .OrderByDescending(l => l.Timestamp) + .Skip(options.Offset) + .Take(options.Limit) + .ToList() + ); + } + + public Task ClearLogsAsync(DateTime? olderThan = null) + { + lock (_logLock) + { + if (olderThan.HasValue) + { + _inMemoryLogs.RemoveAll(l => l.Timestamp < olderThan.Value); + } + else + { + _inMemoryLogs.Clear(); + } + + SaveLogs(); + } + + return Task.CompletedTask; + } + + public async Task ExportLogsAsync(LogFilterOptions options) + { + var logs = await GetLogsAsync(options); + var json = JsonSerializer.Serialize(logs, new JsonSerializerOptions { WriteIndented = true }); + return Encoding.UTF8.GetBytes(json); + } + + public void Log(LogLevel level, string message, string context = null, Dictionary properties = null) + { + var levelString = level.ToString(); + + // Log to standard logger + _logger.Log(level, message); + + // Store in our custom log system + var entry = new LogEntry + { + Id = _inMemoryLogs.Count > 0 ? _inMemoryLogs.Max(l => l.Id) + 1 : 1, + Timestamp = DateTime.UtcNow, + Level = levelString, + Message = message, + Context = context ?? "System", + Properties = properties != null ? JsonSerializer.Serialize(properties) : "{}" + }; + + lock (_logLock) + { + _inMemoryLogs.Add(entry); + + // Keep log size under control + if (_inMemoryLogs.Count > _maxLogEntries) + { + _inMemoryLogs = _inMemoryLogs + .OrderByDescending(l => l.Timestamp) + .Take(_maxLogEntries) + .ToList(); + } + + SaveLogs(); + } + } + + private void SaveLogs() + { + try + { + var json = JsonSerializer.Serialize(_inMemoryLogs); + File.WriteAllText(_logFilePath, json); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to save logs to file"); + } + } + } +} \ No newline at end of file