diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ade3484..8ada6ca4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Channels MUST use `MPEG-TS` or `HLS Segmenter` streaming modes - Since `MPEG-TS` uses `HLS Segmenter` under the hood, the preview player will use `HLS Segmenter`, so it's not 100% equivalent, but it should be representative - Add button to stop transcoding session for each channel that has an active session +- Add more log levels to `Settings` page, allowing more specific debug logging as needed + - Default Minimum Log Level (applies when no other categories/level overrides match) + - Scanning Minimum Log Level + - Scheduling Minimum Log Level + - Streaming Minimum Log Level ### Fixed - Fix error loading path replacements when using MySql diff --git a/ErsatzTV.Application/Configuration/Commands/UpdateGeneralSettingsHandler.cs b/ErsatzTV.Application/Configuration/Commands/UpdateGeneralSettingsHandler.cs index fa2887727..8229e50e9 100644 --- a/ErsatzTV.Application/Configuration/Commands/UpdateGeneralSettingsHandler.cs +++ b/ErsatzTV.Application/Configuration/Commands/UpdateGeneralSettingsHandler.cs @@ -1,20 +1,19 @@ using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; -using Serilog.Core; namespace ErsatzTV.Application.Configuration; public class UpdateGeneralSettingsHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; - private readonly LoggingLevelSwitch _loggingLevelSwitch; + private readonly LoggingLevelSwitches _loggingLevelSwitches; public UpdateGeneralSettingsHandler( - LoggingLevelSwitch loggingLevelSwitch, + LoggingLevelSwitches loggingLevelSwitches, IConfigElementRepository configElementRepository) { - _loggingLevelSwitch = loggingLevelSwitch; + _loggingLevelSwitches = loggingLevelSwitches; _configElementRepository = configElementRepository; } @@ -24,8 +23,17 @@ public class UpdateGeneralSettingsHandler : IRequestHandler ApplyUpdate(GeneralSettingsViewModel generalSettings) { - await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevel, generalSettings.MinimumLogLevel); - _loggingLevelSwitch.MinimumLevel = generalSettings.MinimumLogLevel; + await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevel, generalSettings.DefaultMinimumLogLevel); + _loggingLevelSwitches.DefaultLevelSwitch.MinimumLevel = generalSettings.DefaultMinimumLogLevel; + + await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevelScanning, generalSettings.ScanningMinimumLogLevel); + _loggingLevelSwitches.ScanningLevelSwitch.MinimumLevel = generalSettings.ScanningMinimumLogLevel; + + await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevelScheduling, generalSettings.SchedulingMinimumLogLevel); + _loggingLevelSwitches.SchedulingLevelSwitch.MinimumLevel = generalSettings.SchedulingMinimumLogLevel; + + await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevelStreaming, generalSettings.StreamingMinimumLogLevel); + _loggingLevelSwitches.StreamingLevelSwitch.MinimumLevel = generalSettings.StreamingMinimumLogLevel; return Unit.Default; } diff --git a/ErsatzTV.Application/Configuration/GeneralSettingsViewModel.cs b/ErsatzTV.Application/Configuration/GeneralSettingsViewModel.cs index 27b5e582e..c32896355 100644 --- a/ErsatzTV.Application/Configuration/GeneralSettingsViewModel.cs +++ b/ErsatzTV.Application/Configuration/GeneralSettingsViewModel.cs @@ -4,5 +4,8 @@ namespace ErsatzTV.Application.Configuration; public class GeneralSettingsViewModel { - public LogEventLevel MinimumLogLevel { get; set; } + public LogEventLevel DefaultMinimumLogLevel { get; set; } + public LogEventLevel ScanningMinimumLogLevel { get; set; } + public LogEventLevel SchedulingMinimumLogLevel { get; set; } + public LogEventLevel StreamingMinimumLogLevel { get; set; } } diff --git a/ErsatzTV.Application/Configuration/Queries/GetGeneralSettingsHandler.cs b/ErsatzTV.Application/Configuration/Queries/GetGeneralSettingsHandler.cs index 6de6d6a49..cb4d7e17f 100644 --- a/ErsatzTV.Application/Configuration/Queries/GetGeneralSettingsHandler.cs +++ b/ErsatzTV.Application/Configuration/Queries/GetGeneralSettingsHandler.cs @@ -13,12 +13,24 @@ public class GetGeneralSettingsHandler : IRequestHandler Handle(GetGeneralSettings request, CancellationToken cancellationToken) { - Option maybeLogLevel = + Option maybeDefaultLevel = await _configElementRepository.GetValue(ConfigElementKey.MinimumLogLevel); + + Option maybeScanningLevel = + await _configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelScanning); + Option maybeSchedulingLevel = + await _configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelScheduling); + + Option maybeStreamingLevel = + await _configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelStreaming); + return new GeneralSettingsViewModel { - MinimumLogLevel = await maybeLogLevel.IfNoneAsync(LogEventLevel.Information) + DefaultMinimumLogLevel = await maybeDefaultLevel.IfNoneAsync(LogEventLevel.Information), + ScanningMinimumLogLevel = await maybeScanningLevel.IfNoneAsync(LogEventLevel.Information), + SchedulingMinimumLogLevel = await maybeSchedulingLevel.IfNoneAsync(LogEventLevel.Information), + StreamingMinimumLogLevel = await maybeStreamingLevel.IfNoneAsync(LogEventLevel.Information), }; } } diff --git a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs index ef96e0048..e3b2696d3 100644 --- a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs @@ -84,7 +84,16 @@ public abstract class CallLibraryScannerHandler // because the compact json writer used by the scanner // writes in UTC LogEvent logEvent = LogEventReader.ReadFromString(s); - Log.Write( + + ILogger log = Log.Logger; + if (logEvent.Properties.TryGetValue("SourceContext", out LogEventPropertyValue property)) + { + log = log.ForContext( + Serilog.Core.Constants.SourceContextPropertyName, + property.ToString().Trim('"')); + } + + log.Write( new LogEvent( logEvent.Timestamp.ToLocalTime(), logEvent.Level, diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs index bca8d9b42..4f32f352e 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs @@ -125,7 +125,7 @@ public class HlsSessionWorker : IHlsSessionWorker catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) { // do nothing - _logger.LogInformation("HlsSessionWorker.TrimPlaylist was canceled"); + _logger.LogDebug("HlsSessionWorker.TrimPlaylist was canceled"); } catch (Exception ex) { @@ -302,7 +302,7 @@ public class HlsSessionWorker : IHlsSessionWorker if (!realtime) { Interlocked.Increment(ref _workAheadCount); - _logger.LogInformation("HLS segmenter will work ahead for channel {Channel}", _channelNumber); + _logger.LogDebug("HLS segmenter will work ahead for channel {Channel}", _channelNumber); HlsSessionState nextState = _state switch { @@ -319,7 +319,7 @@ public class HlsSessionWorker : IHlsSessionWorker } else { - _logger.LogInformation( + _logger.LogDebug( "HLS segmenter will NOT work ahead for channel {Channel}", _channelNumber); @@ -341,7 +341,7 @@ public class HlsSessionWorker : IHlsSessionWorker long ptsOffset = await GetPtsOffset(_channelNumber, cancellationToken); // _logger.LogInformation("PTS offset: {PtsOffset}", ptsOffset); - _logger.LogInformation("HLS session state: {State}", _state); + _logger.LogDebug("HLS session state: {State}", _state); DateTimeOffset now = _state is HlsSessionState.SeekAndWorkAhead ? DateTimeOffset.Now @@ -379,7 +379,7 @@ public class HlsSessionWorker : IHlsSessionWorker Command process = processModel.Process; - _logger.LogInformation("ffmpeg hls arguments {FFmpegArguments}", process.Arguments); + _logger.LogDebug("ffmpeg hls arguments {FFmpegArguments}", process.Arguments); try { @@ -389,7 +389,7 @@ public class HlsSessionWorker : IHlsSessionWorker if (commandResult.ExitCode == 0) { - _logger.LogInformation("HLS process has completed for channel {Channel}", _channelNumber); + _logger.LogDebug("HLS process has completed for channel {Channel}", _channelNumber); _logger.LogDebug("Transcoded until: {Until}", processModel.Until); _transcodedUntil = processModel.Until; _state = NextState(_state, processModel); @@ -427,7 +427,7 @@ public class HlsSessionWorker : IHlsSessionWorker { Command errorProcess = errorProcessModel.Process; - _logger.LogInformation( + _logger.LogDebug( "ffmpeg hls error arguments {FFmpegArguments}", errorProcess.Arguments); @@ -451,7 +451,7 @@ public class HlsSessionWorker : IHlsSessionWorker } catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException) { - _logger.LogInformation("Terminating HLS process for channel {Channel}", _channelNumber); + _logger.LogInformation("Terminating HLS session for channel {Channel}", _channelNumber); return false; } } @@ -600,7 +600,7 @@ public class HlsSessionWorker : IHlsSessionWorker return await File.ReadAllLinesAsync(fileName, cancellationToken); } - _logger.LogInformation("Playlist does not exist at expected location {File}", fileName); + _logger.LogDebug("Playlist does not exist at expected location {File}", fileName); return None; } diff --git a/ErsatzTV.Core/Domain/ConfigElementKey.cs b/ErsatzTV.Core/Domain/ConfigElementKey.cs index 38fec510c..4cb0bf829 100644 --- a/ErsatzTV.Core/Domain/ConfigElementKey.cs +++ b/ErsatzTV.Core/Domain/ConfigElementKey.cs @@ -7,6 +7,9 @@ public class ConfigElementKey public string Key { get; } public static ConfigElementKey MinimumLogLevel => new("log.minimum_level"); + public static ConfigElementKey MinimumLogLevelScanning => new("log.minimum_level.scanning"); + public static ConfigElementKey MinimumLogLevelScheduling => new("log.minimum_level.scheduling"); + public static ConfigElementKey MinimumLogLevelStreaming => new("log.minimum_level.streaming"); public static ConfigElementKey FFmpegPath => new("ffmpeg.ffmpeg_path"); public static ConfigElementKey FFprobePath => new("ffmpeg.ffprobe_path"); public static ConfigElementKey FFmpegDefaultProfileId => new("ffmpeg.default_profile_id"); diff --git a/ErsatzTV.Core/LoggingLevelSwitches.cs b/ErsatzTV.Core/LoggingLevelSwitches.cs new file mode 100644 index 000000000..670215b55 --- /dev/null +++ b/ErsatzTV.Core/LoggingLevelSwitches.cs @@ -0,0 +1,14 @@ +using Serilog.Core; + +namespace ErsatzTV.Core; + +public class LoggingLevelSwitches +{ + public LoggingLevelSwitch DefaultLevelSwitch { get; } = new(); + + public LoggingLevelSwitch ScanningLevelSwitch { get; } = new(); + + public LoggingLevelSwitch SchedulingLevelSwitch { get; } = new(); + + public LoggingLevelSwitch StreamingLevelSwitch { get; } = new(); +} diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 016a8fb77..f43c241a7 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -293,7 +293,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory if (profileEntrypoints is not null && profileEntrypoints.Count != 0) { - _logger.LogInformation( + _logger.LogDebug( "Detected {Count} VAAPI profile entrypoints for using {Driver} {Device}", profileEntrypoints.Count, driver, @@ -359,7 +359,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory if (profileEntrypoints is not null && profileEntrypoints.Count != 0) { - _logger.LogInformation( + _logger.LogDebug( "Detected {Count} VAAPI profile entrypoints for using QSV device {Device}", profileEntrypoints.Count, device); @@ -408,7 +408,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory const string MODEL_PATTERN = @"(GTX\s+[0-9a-zA-Z]+[\sTtIi]+)"; Match modelMatch = Regex.Match(line, MODEL_PATTERN); string model = modelMatch.Success ? modelMatch.Groups[1].Value.Trim() : "unknown"; - _logger.LogInformation( + _logger.LogDebug( "Detected NVIDIA GPU model {Model} architecture SM {Architecture}", model, architecture); diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index f678fc160..7863ada68 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -659,7 +659,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder if (ffmpegState.DecoderHardwareAccelerationMode != HardwareAccelerationMode.None || ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None) { - _logger.LogInformation( + _logger.LogDebug( "Forcing {Threads} ffmpeg thread when hardware acceleration is used", 1); @@ -667,7 +667,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder } else if (ffmpegState.Start.Exists(s => s > TimeSpan.Zero) && desiredState.Realtime) { - _logger.LogInformation( + _logger.LogDebug( "Forcing {Threads} ffmpeg thread due to buggy combination of stream seek and realtime output", 1); diff --git a/ErsatzTV/Controllers/InternalController.cs b/ErsatzTV/Controllers/InternalController.cs index 095f9d6ba..806d9debc 100644 --- a/ErsatzTV/Controllers/InternalController.cs +++ b/ErsatzTV/Controllers/InternalController.cs @@ -53,7 +53,7 @@ public class InternalController : ControllerBase { Command command = processModel.Process; - _logger.LogInformation("ffmpeg arguments {FFmpegArguments}", command.Arguments); + _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); var process = new FFmpegProcess { StartInfo = new ProcessStartInfo diff --git a/ErsatzTV/Controllers/IptvController.cs b/ErsatzTV/Controllers/IptvController.cs index 4c45b761a..de4e95582 100644 --- a/ErsatzTV/Controllers/IptvController.cs +++ b/ErsatzTV/Controllers/IptvController.cs @@ -111,7 +111,7 @@ public class IptvController : ControllerBase Command command = processModel.Process; _logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber); - _logger.LogInformation("ffmpeg arguments {FFmpegArguments}", command.Arguments); + _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); var process = new FFmpegProcess { StartInfo = new ProcessStartInfo diff --git a/ErsatzTV/Pages/Channels.razor b/ErsatzTV/Pages/Channels.razor index d9b985724..1d4dab39f 100644 --- a/ErsatzTV/Pages/Channels.razor +++ b/ErsatzTV/Pages/Channels.razor @@ -183,8 +183,6 @@ string currentUri = NavigationManager.Uri; string streamUri = currentUri.Replace("/channels", $"/iptv/channel/{channel.Number}.m3u8?mode=segmenter"); - Serilog.Log.Logger.Information("Stream uri: {StreamUri}", streamUri); - var parameters = new DialogParameters { { "StreamUri", streamUri } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraLarge }; await Dialog.ShowAsync("Channel Preview", parameters, options); diff --git a/ErsatzTV/Pages/Playouts.razor b/ErsatzTV/Pages/Playouts.razor index dc199b523..654b1206e 100644 --- a/ErsatzTV/Pages/Playouts.razor +++ b/ErsatzTV/Pages/Playouts.razor @@ -14,12 +14,16 @@ Add Playout - - Add Block Playout - - - Add External Json Playout - + + + Add Block Playout + + + + + Add External Json Playout + + + Label="Default Minimum Log Level" + @bind-Value="_generalSettings.DefaultMinimumLogLevel" + For="@(() => _generalSettings.DefaultMinimumLogLevel)"> + Debug + Information + Warning + Error + + + Debug + Information + Warning + Error + + + Debug + Information + Warning + Error + + Debug Information Warning @@ -132,7 +159,9 @@ - Save Settings + + Save Settings + diff --git a/ErsatzTV/Program.cs b/ErsatzTV/Program.cs index 44841106f..d1c2a7afb 100644 --- a/ErsatzTV/Program.cs +++ b/ErsatzTV/Program.cs @@ -35,12 +35,12 @@ public class Program .AddEnvironmentVariables() .Build(); - LoggingLevelSwitch = new LoggingLevelSwitch(); + LoggingLevelSwitches = new LoggingLevelSwitches(); } private static IConfiguration Configuration { get; } - private static LoggingLevelSwitch LoggingLevelSwitch { get; } + private static LoggingLevelSwitches LoggingLevelSwitches { get; } public static async Task Main(string[] args) { @@ -54,11 +54,29 @@ public class Program return 1; } - LoggingLevelSwitch.MinimumLevel = LogEventLevel.Information; + LoggingLevelSwitches.DefaultLevelSwitch.MinimumLevel = LogEventLevel.Information; + LoggingLevelSwitches.ScanningLevelSwitch.MinimumLevel = LogEventLevel.Information; + LoggingLevelSwitches.SchedulingLevelSwitch.MinimumLevel = LogEventLevel.Information; + LoggingLevelSwitches.StreamingLevelSwitch.MinimumLevel = LogEventLevel.Information; LoggerConfiguration loggerConfiguration = new LoggerConfiguration() .ReadFrom.Configuration(Configuration) - .MinimumLevel.ControlledBy(LoggingLevelSwitch) + + .MinimumLevel.ControlledBy(LoggingLevelSwitches.DefaultLevelSwitch) + + // scanning + .MinimumLevel.Override("ErsatzTV.Services.ScannerService", LoggingLevelSwitches.ScanningLevelSwitch) + .MinimumLevel.Override("ErsatzTV.Scanner", LoggingLevelSwitches.ScanningLevelSwitch) + + // scheduling + .MinimumLevel.Override("ErsatzTV.Core.Scheduling", LoggingLevelSwitches.SchedulingLevelSwitch) + .MinimumLevel.Override("ErsatzTV.Application.Subtitles.ExtractEmbeddedSubtitlesHandler", LoggingLevelSwitches.SchedulingLevelSwitch) + + // streaming + .MinimumLevel.Override("ErsatzTV.Application.Streaming", LoggingLevelSwitches.StreamingLevelSwitch) + .MinimumLevel.Override("ErsatzTV.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch) + .MinimumLevel.Override("ErsatzTV.Controllers.IptvController", LoggingLevelSwitches.StreamingLevelSwitch) + .Destructure.UsingAttributes() .Enrich.FromLogContext() .WriteTo.File( @@ -78,7 +96,8 @@ public class Program { loggerConfiguration = loggerConfiguration.WriteTo.Console( theme: AnsiConsoleTheme.Code, - formatProvider: CultureInfo.InvariantCulture); + formatProvider: CultureInfo.InvariantCulture, + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <{SourceContext:l}> {NewLine}{Exception}"); // for troubleshooting log category // outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <{SourceContext:l}> {NewLine}{Exception}" @@ -104,7 +123,7 @@ public class Program private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureServices(services => services.AddSingleton(LoggingLevelSwitch)) + .ConfigureServices(services => services.AddSingleton(LoggingLevelSwitches)) .ConfigureWebHostDefaults( webBuilder => webBuilder.UseStartup() .UseConfiguration(Configuration) diff --git a/ErsatzTV/Services/RunOnce/LoadLoggingLevelService.cs b/ErsatzTV/Services/RunOnce/LoadLoggingLevelService.cs index c4a9de4d4..674bc28bb 100644 --- a/ErsatzTV/Services/RunOnce/LoadLoggingLevelService.cs +++ b/ErsatzTV/Services/RunOnce/LoadLoggingLevelService.cs @@ -31,12 +31,28 @@ public class LoadLoggingLevelService : BackgroundService IConfigElementRepository configElementRepository = scope.ServiceProvider.GetRequiredService(); - Option maybeLogLevel = - await configElementRepository.GetValue(ConfigElementKey.MinimumLogLevel); - foreach (LogEventLevel logLevel in maybeLogLevel) + foreach (LogEventLevel logLevel in await configElementRepository.GetValue(ConfigElementKey.MinimumLogLevel)) { - LoggingLevelSwitch loggingLevelSwitch = scope.ServiceProvider.GetRequiredService(); - loggingLevelSwitch.MinimumLevel = logLevel; + LoggingLevelSwitches loggingLevelSwitches = scope.ServiceProvider.GetRequiredService(); + loggingLevelSwitches.DefaultLevelSwitch.MinimumLevel = logLevel; + } + + foreach (LogEventLevel logLevel in await configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelScanning)) + { + LoggingLevelSwitches loggingLevelSwitches = scope.ServiceProvider.GetRequiredService(); + loggingLevelSwitches.ScanningLevelSwitch.MinimumLevel = logLevel; + } + + foreach (LogEventLevel logLevel in await configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelScheduling)) + { + LoggingLevelSwitches loggingLevelSwitches = scope.ServiceProvider.GetRequiredService(); + loggingLevelSwitches.SchedulingLevelSwitch.MinimumLevel = logLevel; + } + + foreach (LogEventLevel logLevel in await configElementRepository.GetValue(ConfigElementKey.MinimumLogLevelStreaming)) + { + LoggingLevelSwitches loggingLevelSwitches = scope.ServiceProvider.GetRequiredService(); + loggingLevelSwitches.StreamingLevelSwitch.MinimumLevel = logLevel; } } }