diff --git a/src/Artemis.Core/Ninject/LoggerProvider.cs b/src/Artemis.Core/Ninject/LoggerProvider.cs index 42cd82430..fe2aaf5b6 100644 --- a/src/Artemis.Core/Ninject/LoggerProvider.cs +++ b/src/Artemis.Core/Ninject/LoggerProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Ninject.Activation; using Serilog; using Serilog.Core; @@ -17,6 +18,7 @@ namespace Artemis.Core.Ninject rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") .WriteTo.Debug() + .WriteTo.Sink() .MinimumLevel.ControlledBy(LoggingLevelSwitch) .CreateLogger(); @@ -28,4 +30,13 @@ namespace Artemis.Core.Ninject return Logger; } } + + internal class ArtemisSink : ILogEventSink + { + /// + public void Emit(LogEvent logEvent) + { + LogStore.Emit(logEvent); + } + } } \ No newline at end of file diff --git a/src/Artemis.Core/Stores/LogStore.cs b/src/Artemis.Core/Stores/LogStore.cs new file mode 100644 index 000000000..856ecd6fe --- /dev/null +++ b/src/Artemis.Core/Stores/LogStore.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Events; + +namespace Artemis.Core +{ + /// + /// A static store containing the last 500 logging events + /// + public static class LogStore + { + private static readonly LinkedList LinkedList = new(); + + /// + /// Gets a list containing the last 500 log events. + /// + public static List Events => LinkedList.ToList(); + + /// + /// Occurs when a new was received. + /// + public static event EventHandler? EventAdded; + + internal static void Emit(LogEvent logEvent) + { + LinkedList.AddLast(logEvent); + if (LinkedList.Count > 500) + LinkedList.RemoveFirst(); + + OnEventAdded(new LogEventEventArgs(logEvent)); + } + + private static void OnEventAdded(LogEventEventArgs e) + { + EventAdded?.Invoke(null, e); + } + } + + /// + /// Contains log event related data + /// + public class LogEventEventArgs : EventArgs + { + internal LogEventEventArgs(LogEvent logEvent) + { + LogEvent = logEvent; + } + + /// + /// Gets the log event + /// + public LogEvent LogEvent { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Properties/launchSettings.json b/src/Artemis.UI/Properties/launchSettings.json index e8d4f8dfb..be8d62f45 100644 --- a/src/Artemis.UI/Properties/launchSettings.json +++ b/src/Artemis.UI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Artemis.UI": { "commandName": "Project", - "commandLineArgs": "--force-elevation --logging=debug" + "commandLineArgs": "--force-elevation" } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml index 068b39899..32f2e6256 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml @@ -15,70 +15,74 @@ FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto" UseLayoutRounding="True" FadeContentIfInactive="False" - Width="800" + Width="1200" Height="800" d:DesignHeight="800" d:DesignWidth="800" d:DataContext="{d:DesignInstance debug:DebugViewModel}" Icon="/Resources/Images/Logo/logo-512.png" Topmost="{Binding StayOnTopSetting.Value}"> - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 0e0e97656..ae0364bb4 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -24,11 +24,11 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) { + DisplayName = "DATA MODEL"; _dataModelUIService = dataModelUIService; _pluginManagementService = pluginManagementService; _updateTimer = new Timer(25); - DisplayName = "Data model"; Modules = new BindableCollection(); } diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml index 9982531f3..160fa3d2f 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugView.xaml @@ -4,7 +4,44 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Debug.Tabs" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:s="https://github.com/canton7/Stylet" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:LogsDebugViewModel}"> - + + + + + + + + + + + + + + + + + + + + When reporting errors please don't take a screenshot of the logs, instead upload the full log or select & copy a part of it, thanks! + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugViewModel.cs index 5605473be..61e8a1cf1 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/LogsDebugViewModel.cs @@ -1,12 +1,127 @@ -using Stylet; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using Artemis.Core; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; +using Serilog.Events; +using Serilog.Formatting.Display; +using Stylet; namespace Artemis.UI.Screens.Settings.Debug.Tabs { public class LogsDebugViewModel : Screen { - public LogsDebugViewModel() + private readonly IDialogService _dialogService; + private readonly MessageTemplateTextFormatter _formatter; + private ScrollViewer _scrollViewer; + + public LogsDebugViewModel(IDialogService dialogService) { - DisplayName = "Logs"; + DisplayName = "LOGS"; + _dialogService = dialogService; + _formatter = new MessageTemplateTextFormatter( + "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" + ); } + + public FlowDocument LogsDocument { get; } = new(); + + public void ShowLogsFolder() + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs")); + } + + public async Task UploadLogs() + { + bool confirmed = await _dialogService.ShowConfirmDialogAt( + "DebuggerDialog", + "Upload logs", + "Automatically uploading logs is not yet implemented.\r\n\r\n" + + "To manually upload a log simply drag the log file from the logs folder\r\n" + + "into Discord or the GitHub issue textbox, depending on what you're using.", + "OPEN LOGS FOLDER", + "CANCEL"); + + if (confirmed) + ShowLogsFolder(); + } + + private Paragraph CreateLogEventParagraph(LogEvent logEvent) + { + Paragraph paragraph = new(new Run(RenderLogEvent(logEvent))) + { + // But mah MVVM + Foreground = logEvent.Level switch + { + LogEventLevel.Verbose => new SolidColorBrush(Colors.White), + LogEventLevel.Debug => new SolidColorBrush(Color.FromRgb(216, 216, 216)), + LogEventLevel.Information => new SolidColorBrush(Color.FromRgb(93, 201, 255)), + LogEventLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 177, 53)), + LogEventLevel.Error => new SolidColorBrush(Color.FromRgb(255, 63, 63)), + LogEventLevel.Fatal => new SolidColorBrush(Colors.Red), + _ => throw new ArgumentOutOfRangeException() + } + }; + + return paragraph; + } + + private string RenderLogEvent(LogEvent logEvent) + { + using StringWriter writer = new(); + _formatter.Format(logEvent, writer); + return writer.ToString().Trim(); + } + + #region Overrides of Screen + + /// + protected override void OnActivate() + { + LogsDocument.Blocks.AddRange(LogStore.Events.Select(e => CreateLogEventParagraph(e))); + LogStore.EventAdded += LogStoreOnEventAdded; + + base.OnActivate(); + } + + private void LogStoreOnEventAdded(object sender, LogEventEventArgs e) + { + Execute.PostToUIThread(() => + { + LogsDocument.Blocks.Add(CreateLogEventParagraph(e.LogEvent)); + while (LogsDocument.Blocks.Count > 500) + LogsDocument.Blocks.Remove(LogsDocument.Blocks.FirstBlock); + + if (_scrollViewer != null && Math.Abs(_scrollViewer.VerticalOffset - _scrollViewer.ScrollableHeight) < 10) + _scrollViewer.ScrollToBottom(); + }); + } + + /// + protected override void OnDeactivate() + { + LogStore.EventAdded -= LogStoreOnEventAdded; + LogsDocument.Blocks.Clear(); + + base.OnDeactivate(); + } + + /// + protected override void OnViewLoaded() + { + ScrollViewer scrollViewer = VisualTreeUtilities.FindChild(View, null); + _scrollViewer = scrollViewer; + _scrollViewer?.ScrollToBottom(); + + base.OnViewLoaded(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml index 05f2c83da..629db8ada 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugView.xaml @@ -10,10 +10,10 @@ d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:RenderDebugViewModel}"> - - - - + + + + In this window you can view the inner workings of Artemis. @@ -30,19 +30,20 @@ This image shows what is being rendered and dispatched to RGB.NET - - - - + + + + + - - + + - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index 934ba89b9..a879a5a78 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -23,8 +23,8 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs public RenderDebugViewModel(ICoreService coreService) { + DisplayName = "RENDERING"; _coreService = coreService; - DisplayName = "Rendering"; } public ImageSource CurrentFrame