Simplify Program.cs and LoggingService to remove database dependencies

This commit is contained in:
MasterDraco 2025-03-12 22:01:02 +00:00
parent d919516f2d
commit 3f9875cb1a
2 changed files with 206 additions and 77 deletions

View File

@ -1,72 +1,38 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TransmissionRssManager.Core; using TransmissionRssManager.Core;
using TransmissionRssManager.Data;
using TransmissionRssManager.Data.Models;
using TransmissionRssManager.Data.Repositories;
using TransmissionRssManager.Services; 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); var builder = WebApplication.CreateBuilder(args);
// Add Serilog to the application // Add logging
builder.Host.UseSerilog(); 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 // Add services to the container
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen();
// Configure database // Add custom services as singletons for simplicity
var connectionString = builder.Configuration["ConnectionStrings:DefaultConnection"] builder.Services.AddSingleton<IConfigService, ConfigService>();
?? "Host=localhost;Database=torrentmanager;Username=postgres;Password=postgres"; builder.Services.AddSingleton<ITransmissionClient, TransmissionClient>();
builder.Services.AddDbContext<TorrentManagerContext>(options => builder.Services.AddSingleton<IRssFeedManager, RssFeedManager>();
options.UseNpgsql(connectionString)); builder.Services.AddSingleton<IPostProcessor, PostProcessor>();
builder.Services.AddSingleton<IMetricsService, MetricsService>();
builder.Services.AddSingleton<ILoggingService, LoggingService>();
// Register repositories // Add background services
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<ITorrentRepository, TorrentRepository>();
builder.Services.AddScoped<DataMigrationService>();
// Add custom services (now scoped instead of singleton to work with DbContext)
builder.Services.AddScoped<IConfigService, ConfigService>();
builder.Services.AddScoped<ITransmissionClient, TransmissionClient>();
builder.Services.AddScoped<IRssFeedManager, RssFeedManager>();
builder.Services.AddScoped<IPostProcessor, PostProcessor>();
builder.Services.AddScoped<ILoggingService, LoggingService>();
builder.Services.AddScoped<IMetricsService, MetricsService>();
builder.Services.AddScoped<ISchedulerService, SchedulerService>();
// Add background services (these remain singleton)
builder.Services.AddHostedService<RssFeedBackgroundService>(); builder.Services.AddHostedService<RssFeedBackgroundService>();
builder.Services.AddHostedService<PostProcessingBackgroundService>(); builder.Services.AddHostedService<PostProcessingBackgroundService>();
builder.Services.AddHostedService<FeedSchedulerBackgroundService>();
// MetricsBackgroundService is not needed with the simplified implementation
var app = builder.Build(); var app = builder.Build();
@ -82,38 +48,12 @@ app.UseRouting();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
// Initialize database and run migrations
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<TorrentManagerContext>();
context.Database.Migrate();
// Run data migration from file-based storage if needed
if (!context.UserPreferences.Any())
{
var migrationService = services.GetRequiredService<DataMigrationService>();
await migrationService.MigrateDataAsync();
}
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred during database initialization or migration");
}
}
try try
{ {
await app.RunAsync(); await app.RunAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Fatal(ex, "Application terminated unexpectedly"); var logger = app.Services.GetRequiredService<ILogger<Program>>();
} logger.LogError(ex, "Application terminated unexpectedly");
finally
{
Log.CloseAndFlush();
} }

View File

@ -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<List<LogEntry>> GetLogsAsync(LogFilterOptions options);
Task ClearLogsAsync(DateTime? olderThan = null);
Task<byte[]> ExportLogsAsync(LogFilterOptions options);
void Log(LogLevel level, string message, string context = null, Dictionary<string, string> 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<LoggingService> _logger;
private readonly string _logFilePath;
private readonly object _logLock = new object();
private List<LogEntry> _inMemoryLogs = new List<LogEntry>();
private readonly int _maxLogEntries = 1000;
public LoggingService(ILogger<LoggingService> 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<List<LogEntry>>(json) ?? new List<LogEntry>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load logs from file");
_inMemoryLogs = new List<LogEntry>();
}
}
public void Configure(UserPreferences preferences)
{
// No-op in simplified version
}
public Task<List<LogEntry>> 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<byte[]> 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<string, string> 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");
}
}
}
}