1+ using System ;
2+ using System . Diagnostics ;
3+ using System . Linq ;
4+ using System . Reflection ;
5+ using System . Text ;
6+ using BepInEx . Logging ;
7+ using RuntimeUnityEditor . Core ;
8+ using RuntimeUnityEditor . Core . Utils ;
9+ using UnityEngine ;
10+
11+ namespace RuntimeUnityEditor . Bepin6 . LogViewer
12+ {
13+ internal readonly struct LogViewerEntry
14+ {
15+ private LogViewerEntry ( object sender , LogEventArgs logEventArgs , StackFrame [ ] filteredStackTrace , string filteredStackTraceString )
16+ {
17+ Sender = sender ;
18+ LogEventArgs = logEventArgs ;
19+ FilteredStackTrace = filteredStackTrace ;
20+ FilteredStackTraceString = filteredStackTraceString ;
21+
22+ Method = filteredStackTrace != null && filteredStackTrace . Length > 0 ? filteredStackTrace [ 0 ] . GetMethod ( ) : null ;
23+
24+ var tooltip = "Filtered stack trace of this log write:\n \n " + filteredStackTraceString ;
25+ _timeString = new GUIContent ( DateTime . UtcNow . ToShortTimeString ( ) , null , tooltip ) ;
26+ _logLevelString = new GUIContent ( logEventArgs . Level . ToString ( ) , null , tooltip ) ;
27+ _sourceNameString = new GUIContent ( logEventArgs . Source . SourceName , null , tooltip ) ;
28+ _dataString = new GUIContent ( logEventArgs . Data ? . ToString ( ) ?? "NULL" , null , tooltip ) ;
29+ //ContentLevel = new GUIContent($"{_TimeString} [{_logLevelString,-7}:{_sourceSourceNameString,10}]", tooltip);
30+ //ContentText = new GUIContent(_dataString, tooltip);
31+ }
32+
33+ private readonly GUIContent _timeString ;
34+ private readonly GUIContent _logLevelString ;
35+ private readonly GUIContent _sourceNameString ;
36+ private readonly GUIContent _dataString ;
37+
38+ public MethodBase Method { get ; }
39+ //public GUIContent ContentLevel { get; }
40+ //public GUIContent ContentText { get; }
41+ public LogEventArgs LogEventArgs { get ; }
42+ public StackFrame [ ] FilteredStackTrace { get ; }
43+ public string FilteredStackTraceString { get ; }
44+ public object Sender { get ; }
45+
46+ public string GetClipboardString ( )
47+ {
48+ return $ "{ _timeString } { _logLevelString } { _sourceNameString } { _dataString } \n { FilteredStackTraceString } \n Sender: { Sender } ({ Sender ? . GetType ( ) . GetSourceCodeRepresentation ( ) ?? "NULL" } )";
49+ }
50+
51+ public bool DrawEntry ( )
52+ {
53+ GUI . color = GetColor ( ) ;
54+ var clicked = GUILayout . Button ( _timeString , GUI . skin . label , GUILayout . MinWidth ( 35 ) ) ;
55+ GUILayout . Label ( "[" , IMGUIUtils . LayoutOptionsExpandWidthFalse ) ;
56+ clicked |= GUILayout . Button ( _logLevelString , GUI . skin . label , GUILayout . MinWidth ( 45 ) ) ;
57+ GUILayout . Label ( ":" , IMGUIUtils . LayoutOptionsExpandWidthFalse ) ;
58+ clicked |= GUILayout . Button ( _sourceNameString , GUI . skin . label , GUILayout . MinWidth ( 100 ) ) ;
59+ GUILayout . Label ( "]" , IMGUIUtils . LayoutOptionsExpandWidthFalse ) ;
60+ GUI . color = Color . white ;
61+ clicked |= GUILayout . Button ( _dataString , GUI . skin . label , IMGUIUtils . LayoutOptionsExpandWidthTrue ) ;
62+ return clicked ;
63+ }
64+
65+ public Color GetColor ( )
66+ {
67+ switch ( LogEventArgs . Level )
68+ {
69+ case LogLevel . Fatal :
70+ case LogLevel . Error :
71+ return Color . red ;
72+
73+ case LogLevel . Warning :
74+ return Color . yellow ;
75+
76+ default :
77+ case LogLevel . Message :
78+ case LogLevel . Info :
79+ return Color . white ;
80+
81+ case LogLevel . Debug :
82+ return Color . gray ;
83+ }
84+ }
85+
86+ public bool IsMatch ( string searchString , LogLevel logLevelFilter )
87+ {
88+ return ( logLevelFilter & LogEventArgs . Level ) != 0
89+ && ( string . IsNullOrEmpty ( searchString )
90+ || _dataString . text . Contains ( searchString , StringComparison . OrdinalIgnoreCase )
91+ || FilteredStackTraceString . Contains ( searchString , StringComparison . OrdinalIgnoreCase )
92+ || LogEventArgs . Source . SourceName . Contains ( searchString , StringComparison . OrdinalIgnoreCase ) ) ;
93+ }
94+
95+ #region Parsing
96+
97+ private const int SkippedStackFrames = 4 ;
98+ private static bool _stacktraceTostringFallback ;
99+
100+ public static LogViewerEntry CreateFromLogEventArgs ( object sender , LogEventArgs eventArgs )
101+ {
102+ string hoverText ;
103+ var st = new StackTrace ( SkippedStackFrames ) ;
104+ StackFrame [ ] frames = null ;
105+
106+ fallback :
107+ if ( ! _stacktraceTostringFallback )
108+ {
109+ try
110+ {
111+ // Try to trim the stack trace up until user code and reduce the amount of text shown so it fits in the tooltip
112+ hoverText = ParseStackTrace ( st , out frames ) ;
113+ }
114+ catch ( Exception e )
115+ {
116+ RuntimeUnityEditorCore . Logger . Log ( Core . Utils . Abstractions . LogLevel . Error , $ "[{ nameof ( LogViewerWindow ) } ] Crash when trying to parse stack trace, falling back to using ToString\n " + e ) ;
117+ _stacktraceTostringFallback = true ;
118+ goto fallback ;
119+ }
120+ }
121+ else
122+ {
123+ hoverText = ParseStackTraceString ( st ) ;
124+ try
125+ {
126+ frames = st . GetFrames ( ) ;
127+ }
128+ catch ( Exception e )
129+ {
130+ Console . WriteLine ( e ) ;
131+ }
132+ }
133+
134+ return new LogViewerEntry ( sender , eventArgs , frames , hoverText ) ;
135+ }
136+
137+ private static string ParseStackTrace ( StackTrace st , out StackFrame [ ] filteredFrames )
138+ {
139+ filteredFrames = null ;
140+
141+ var frames = st . GetFrames ( ) ;
142+ if ( frames == null || frames . Length == 0 )
143+ return ParseStackTraceString ( st ) ;
144+
145+ var sb = new StringBuilder ( ) ;
146+ var realEncountered = false ;
147+ var firstRealIdx = 0 ;
148+ //var skipped = 0;
149+ for ( var i = 0 ; i < frames . Length ; i ++ )
150+ {
151+ var frame = frames [ i ] ;
152+ var m = frame . GetMethod ( ) ;
153+ var mName = m . Name ;
154+ var typeName = ( m . DeclaringType ?? m . ReflectedType ) ? . GetSourceCodeRepresentation ( ) ?? "???" ;
155+
156+ var first = false ;
157+ if ( ! realEncountered )
158+ {
159+ if ( typeName . StartsWith ( "BepInEx." , StringComparison . Ordinal ) ||
160+ typeName . StartsWith ( "System." , StringComparison . Ordinal ) ||
161+ typeName . StartsWith ( "UnityEngine." , StringComparison . Ordinal ) && mName . Contains ( "Log" , StringComparison . Ordinal ) ||
162+ typeName . StartsWith ( nameof ( RuntimeUnityEditor ) , StringComparison . Ordinal ) && mName . Equals ( "Log" , StringComparison . Ordinal ) )
163+ {
164+ //skipped++;
165+ continue ;
166+ }
167+
168+ realEncountered = true ;
169+ first = true ;
170+ }
171+
172+ if ( first )
173+ firstRealIdx = i ;
174+
175+ sb . AppendFormat ( "[{0}] " , i + SkippedStackFrames ) ;
176+ sb . Append ( typeName ) ;
177+ sb . Append ( '.' ) ;
178+ sb . Append ( mName ) ;
179+ //todo params
180+ sb . AppendLine ( ) ;
181+ }
182+
183+ //if (skipped > 0)
184+ // sb.AppendLine($"(The stack trace starts at frame {4 + skipped})");
185+
186+ filteredFrames = frames . Skip ( firstRealIdx ) . ToArray ( ) ;
187+
188+ if ( sb . Length == 0 )
189+ sb . Append ( @"¯\_(ツ)_/¯" ) ;
190+
191+ return sb . ToString ( ) ;
192+ }
193+
194+ private static string ParseStackTraceString ( StackTrace st )
195+ {
196+ var parts = st . ToString ( ) . Split ( new [ ] { '\n ' , '\r ' } , StringSplitOptions . RemoveEmptyEntries ) ;
197+ var firstGood = Array . FindIndex ( parts , part => ! part . Contains ( "at BepInEx." ) && ! part . Contains ( "at System." ) ) ;
198+ if ( firstGood >= 0 )
199+ {
200+ var temp = new string [ parts . Length - firstGood ] ;
201+ Array . Copy ( parts , firstGood , temp , 0 , parts . Length - firstGood ) ;
202+ return "Origin: " + string . Join ( "\n " , temp ) ;
203+ }
204+
205+ return string . Empty ;
206+ }
207+
208+ #endregion
209+ }
210+ }
0 commit comments