//using Meryel.UnityCodeAssist.Serilog; //using Meryel.UnityCodeAssist.Serilog.Core; using UnityEngine; using UnityEditor; using System.Linq; #if ELOGGER #pragma warning disable IDE0005 using Serilog = Meryel.Serilog; #pragma warning restore IDE0005 #nullable enable namespace Meryel.UnityCodeAssist.Editor.Logger { //[InitializeOnLoad] public static class ELogger { public static event System.Action? OnVsInternalLogChanged; // Change 'new LoggerConfiguration().MinimumLevel.Debug();' if you change these values const Serilog.Events.LogEventLevel fileMinLevel = Serilog.Events.LogEventLevel.Debug; const Serilog.Events.LogEventLevel outputWindowMinLevel = Serilog.Events.LogEventLevel.Information; static LoggingLevelSwitch? fileLevelSwitch, outputWindowLevelSwitch; //static bool IsInitialized { get; set; } static ILogEventSink? _outputWindowSink; static ILogEventSink? _memorySink; public static string GetInternalLogContent() => _memorySink == null ? string.Empty : ((MemorySink)_memorySink).Export(); public static int GetErrorCountInInternalLog() => _memorySink == null ? 0 : ((MemorySink)_memorySink).ErrorCount; public static int GetWarningCountInInternalLog() => _memorySink == null ? 0 : ((MemorySink)_memorySink).WarningCount; public static string? FilePath { get; private set; } public static string? VSFilePath { get; private set; } //**-- make it work with multiple clients static string? _vsInternalLog; public static string? VsInternalLog { get => _vsInternalLog; set { _vsInternalLog = value; OnVsInternalLogChanged?.Invoke(); } } static ELogger() { var isFirst = false; const string stateName = "isFirst"; if (!SessionState.GetBool(stateName, false)) { isFirst = true; SessionState.SetBool(stateName, true); } var projectPath = CommonTools.GetProjectPath(); var outputWindowSink = new System.Lazy(() => new UnityOutputWindowSink(null)); Init(isFirst, projectPath, outputWindowSink); if (isFirst) LogHeader(Application.unityVersion, projectPath); } /// /// Empty method for invoking static class ctor /// public static void Bump() { } static void LogHeader(string unityVersion, string solutionDir) { var os = System.Runtime.InteropServices.RuntimeInformation.OSDescription; var assisterVersion = Assister.Version; var syncModel = Synchronizer.Model.Utilities.Version; var hash = CommonTools.GetHashForLogFile(solutionDir); var port = Synchronizer.Model.Utilities.GetPortForMQTTnet(solutionDir); Serilog.Log.Debug( "Beginning logging {OS}, Unity {U}, Unity Code Assist {A}, Communication Protocol {SM}, Project: '{Dir}', Project Hash: {Hash}, Port: {Port}", os, unityVersion, assisterVersion, syncModel, solutionDir, hash, port); } static string GetFilePath(string solutionDir) { var solutionHash = CommonTools.GetHashForLogFile(solutionDir); var tempDir = System.IO.Path.GetTempPath(); var fileName = $"UnityCodeAssist_U_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_' var filePath = System.IO.Path.Combine(tempDir, fileName); return filePath; } static string GetVSFilePath(string solutionDir) { var solutionHash = CommonTools.GetHashForLogFile(solutionDir); var tempDir = System.IO.Path.GetTempPath(); #if MERYEL_UCA_LITE_VERSION var fileName = $"UnityCodeAssistLite_VS_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_' #else var fileName = $"UnityCodeAssist_VS_Log_{solutionHash}_.TXT"; // hour code will be appended to the end of file, so add a trailing '_' #endif var filePath = System.IO.Path.Combine(tempDir, fileName); return filePath; } public static void Init(bool isFirst, string solutionDir, System.Lazy outputWindowSink) { FilePath = GetFilePath(solutionDir); VSFilePath = GetVSFilePath(solutionDir); fileLevelSwitch = new LoggingLevelSwitch(fileMinLevel); outputWindowLevelSwitch = new LoggingLevelSwitch(outputWindowMinLevel); var config = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.With(new DomainHashEnricher()); const string outputTemplate = "{Timestamp:HH:mm:ss.fff} [U] [{Level:u3}] [{DomainHash}] {Message:lj}{NewLine}{Exception}"; config = config.WriteTo.PersistentFile(FilePath , outputTemplate: outputTemplate , shared: true , persistentFileRollingInterval: PersistentFileRollingInterval.Day , preserveLogFilename: true , levelSwitch: fileLevelSwitch , rollOnEachProcessRun: isFirst ); _outputWindowSink ??= outputWindowSink.Value; if (_outputWindowSink != null) config = config.WriteTo.Sink(_outputWindowSink, outputWindowMinLevel, outputWindowLevelSwitch); _memorySink ??= new MemorySink(outputTemplate); config = config.WriteTo.Sink(_memorySink, fileMinLevel, null); config = config.Destructure.With(new MyDestructuringPolicy()); Serilog.Log.Logger = config.CreateLogger(); //switchableLogger.Set(config.CreateLogger(), disposePrev: true); OnOptionsChanged(); //IsInitialized = true; } public static void OnOptionsChanged() { // Since we don't use LogEventLevel.Fatal, we can use it for disabling sinks var isLoggingToFile = OptionsIsLoggingToFile; var targetFileLevel = isLoggingToFile ? fileMinLevel : Serilog.Events.LogEventLevel.Fatal; if (fileLevelSwitch != null) fileLevelSwitch.MinimumLevel = targetFileLevel; var isLoggingToOutputWindow = OptionsIsLoggingToOutputWindow; var targetOutputWindowLevel = isLoggingToOutputWindow ? outputWindowMinLevel : Serilog.Events.LogEventLevel.Fatal; if (outputWindowLevelSwitch != null) outputWindowLevelSwitch.MinimumLevel = targetOutputWindowLevel; } //**-- UI for these two static bool OptionsIsLoggingToFile => true; static bool OptionsIsLoggingToOutputWindow => true; } public class MyDestructuringPolicy : IDestructuringPolicy { // serilog cannot destruct StringArrayContainer by default, so do it manually public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Serilog.Events.LogEventPropertyValue? result) { if (value is Synchronizer.Model.StringArrayContainer sac) { var items = sac.Container.Select(item => propertyValueFactory.CreatePropertyValue(item, true)); result = new Serilog.Events.SequenceValue(items); return true; } result = null; return false; } } } #endif