Skip to content

Commit 1c57e58

Browse files
committed
Multilanguage option added
1 parent 7493b35 commit 1c57e58

File tree

5 files changed

+118
-30
lines changed

5 files changed

+118
-30
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ const stack = new netStack('.stacktrace', {
4848
prettyprint: true
4949
});
5050
```
51+
##### multilanguage: boolean
52+
Default: false.
53+
Find multiple languages and render them accordingly.
54+
```javascript
55+
const stack = new netStack('.stacktrace', {
56+
multilanguage: true
57+
});
58+
```
5159

5260
#### Ready to go css
5361
```css

netstack.js

+94-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*!
2-
* netStack v2.1.0
2+
* netStack v2.1.1
33
* A simple and easy JavaScript library for highlighting .NET stack traces
44
* License: Apache 2
55
* Author: https://elmah.io
@@ -34,6 +34,7 @@
3434
// Default values for classes
3535
this.settings = extend({
3636
prettyprint: false,
37+
multilanguage: false,
3738
frame: 'st-frame',
3839
type: 'st-type',
3940
method: 'st-method',
@@ -76,6 +77,8 @@
7677
return null;
7778
};
7879

80+
netStack.prototype.allEqual = arr => arr.every(val => val === arr[0]);
81+
7982
netStack.prototype.replacer = function(args, at_language) {
8083
if (args[0].substring(0).match(/(-{3}>)/)) {
8184
return '\r\n ' + args[0];
@@ -86,7 +89,7 @@
8689
}
8790
};
8891

89-
netStack.prototype.formatException = function(exceptionMessage, at_language) {
92+
netStack.prototype.formatException = function(exceptionMessage, at_language, loop, position) {
9093
var result = exceptionMessage || '';
9194
var searchReplaces = [
9295
{
@@ -104,7 +107,12 @@
104107
];
105108

106109
var self = this;
107-
searchReplaces.forEach(function(item) {
110+
searchReplaces.forEach(function(item, index) {
111+
// multilanguage, skip --- lines
112+
if (loop === true && position > 0 && index === 1) {
113+
return;
114+
}
115+
108116
if (item.repl == null) {
109117
result = result.replace(item.find, function() {
110118
return self.replacer(arguments, at_language);
@@ -116,6 +124,22 @@
116124
return result;
117125
};
118126

127+
netStack.prototype.detectLanguagesInOrder = function(input, regexes) {
128+
let matches = [];
129+
130+
for (let [language, regex] of Object.entries(regexes)) {
131+
let match;
132+
while ((match = regex.exec(input)) !== null) {
133+
matches.push({ language, index: match.index });
134+
}
135+
regex.lastIndex = 0;
136+
}
137+
138+
matches.sort((a, b) => a.index - b.index);
139+
140+
return matches.map((match) => match.language);
141+
};
142+
119143
netStack.prototype.init = function() {
120144
// Get the stacktrace, sanitize it, and split it into lines
121145
var stacktrace = this.element.textContent,
@@ -124,53 +148,94 @@
124148
lang = '',
125149
clone = '';
126150

127-
// look for the language
128-
for (var i = 0; i < lines.length; ++i) {
129-
if (lang === '') {
130-
var regexes = {
131-
english: /\s+at .*\)/,
132-
danish: /\s+ved .*\)/,
133-
german: /\s+bei .*\)/,
134-
spanish: /\s+en .*\)/,
135-
russian: /\s+в .*\)/,
136-
chinese: /\s+ .*\)/
137-
};
138-
139-
for (var key in regexes) {
140-
if (regexes[key].test(lines[i])) {
141-
lang = key;
142-
break;
151+
var languagesRegex = {
152+
english: /\s+at .*?\)/g,
153+
danish: /\s+ved .*?\)/g,
154+
german: /\s+bei .*?\)/g,
155+
spanish: /\s+en .*?\)/g,
156+
russian: /\s+в .*?\)/g,
157+
chinese: /\s+ .*?\)/g
158+
};
159+
160+
// look for the language(s) in the stack trace
161+
if (this.settings.multilanguage) {
162+
lang = this.detectLanguagesInOrder(lines, languagesRegex);
163+
} else {
164+
for (var i = 0; i < lines.length; ++i) {
165+
if (lang === '') {
166+
for (var key in languagesRegex) {
167+
if (languagesRegex[key].test(lines[i])) {
168+
lang = key;
169+
break;
170+
}
143171
}
144172
}
145173
}
146174
}
147175

148176
if (lang === '') return;
149177

150-
var selectedLanguage = this.search(lang, this.languages);
151-
this.language = selectedLanguage.name;
178+
// if multiline option is true, check if the language is the same for all lines
179+
if (typeof lang === 'object') {
180+
if (this.allEqual(lang)) {
181+
lang = lang[0];
182+
}
183+
}
184+
185+
// if lang is an array, we have multiple languages
186+
if (Array.isArray(lang)) {
187+
var selectedLanguage = [];
188+
for (var i = 0; i < lang.length; ++i) {
189+
selectedLanguage.push(this.search(lang[i], this.languages));
190+
}
191+
this.language = 'multilanguage';
192+
} else if (typeof lang === 'string') {
193+
var selectedLanguage = this.search(lang, this.languages);
194+
this.language = selectedLanguage.name;
195+
}
152196

153197
// Pritty print result if is set to true
154198
if (this.settings.prettyprint) {
155-
sanitizedStack = this.formatException(sanitizedStack, selectedLanguage.at);
199+
if (Array.isArray(selectedLanguage)) {
200+
var sanitizedStacks = sanitizedStack;
201+
const selectedLanguages = [...new Set(selectedLanguage)];
202+
selectedLanguages.forEach((language, index) => {
203+
sanitizedStacks = this.formatException(sanitizedStacks, language.at, true, index);
204+
});
205+
sanitizedStack = sanitizedStacks;
206+
} else {
207+
sanitizedStack = this.formatException(sanitizedStack, selectedLanguage.at);
208+
}
209+
156210
lines = sanitizedStack.split('\n');
157211
}
158212

213+
if (Array.isArray(selectedLanguage)) {
214+
var langContor = 0;
215+
}
216+
159217
for (var i = 0; i < lines.length; ++i) {
160218
var li = lines[i],
161-
hli = new RegExp('(\\S*)' + selectedLanguage.at + ' .*\\)');
219+
hli = new RegExp('(\\S*)' + selectedLanguage.at + ' .*\\)'),
220+
languageSet = selectedLanguage;
221+
222+
if (Array.isArray(selectedLanguage)) {
223+
hli = new RegExp('(\\S*)' + selectedLanguage[langContor].at + ' .*\\)');
224+
languageSet = selectedLanguage[langContor];
225+
hli.test(lines[i]) ? langContor++ : langContor;
226+
}
162227

163228
if (hli.test(lines[i])) {
164229

165230
// Frame
166-
var regFrame = new RegExp('(\\S*)' + selectedLanguage.at + ' .*?\\)'),
231+
var regFrame = new RegExp('(\\S*)' + languageSet.at + ' .*?\\)'),
167232
partsFrame = String(regFrame.exec(lines[i]));
168233

169234
if (partsFrame.substring(partsFrame.length - 1) == ',') {
170235
partsFrame = partsFrame.slice(0, -1);
171236
}
172237

173-
partsFrame = partsFrame.replace(selectedLanguage.at + ' ', '');
238+
partsFrame = partsFrame.replace(languageSet.at + ' ', '');
174239

175240
// Frame -> ParameterList
176241
var regParamList = /\(.*\)/,
@@ -206,18 +271,18 @@
206271
var newPartsFrame = partsFrame.replace(partsParamList, stringParam).replace(partsTypeMethod, stringTypeMethod);
207272

208273
// Line
209-
var regLine = new RegExp('\\b:' + selectedLanguage.line + ' \\d+'),
274+
var regLine = new RegExp('\\b:' + languageSet.line + ' \\d+'),
210275
partsLine = String(regLine.exec(lines[i]));
211276

212277
partsLine = partsLine.replace(':', '').trim();
213278

214-
var fileLi = li.replace(selectedLanguage.at + " " + partsFrame, '').trim();
279+
var fileLi = li.replace(languageSet.at + " " + partsFrame, '').trim();
215280

216281
// File => (!) text requires multiline to exec regex, otherwise it will return null.
217-
var regFile = new RegExp(selectedLanguage.in + '\\s.*$', 'm'),
282+
var regFile = new RegExp(languageSet.in + '\\s.*$', 'm'),
218283
partsFile = String(regFile.exec(fileLi));
219284

220-
partsFile = partsFile.replace(selectedLanguage.in + ' ', '').replace(':' + partsLine, '').replace('&lt;---', '');
285+
partsFile = partsFile.replace(languageSet.in + ' ', '').replace(':' + partsLine, '').replace('&lt;---', '');
221286

222287
li = li.replace(partsFrame, '<span class="' + this.settings.frame + '">' + newPartsFrame + '</span>')
223288
.replace(partsFile, '<span class="' + this.settings.file + '">' + partsFile + '</span>')

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "netstack.js",
3-
"version": "2.1.0",
3+
"version": "2.1.1",
44
"description": "A simple and easy JavaScript library for highlighting .NET stack traces",
55
"main": "netstack.js",
66
"scripts": {

test/stacktraces.html

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
<pre><code class="stacktrace-ru">System.ApplicationException: Ошибка в ходе выполнения ---> System.FormatException: Входная строка имела неверный формат. в System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) в System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) в System.Int32.Parse(String s) в MyNamespace.IntParser.Execute(String s) в C:\apps\MyNamespace\IntParser.cs:строка 13 --- Конец трассировка стека из предыдущего расположения, где возникло исключение --- в Elmah.Io.App.Controllers.AccountController.ChangeEmail(String secret) в x:\agent\_work\94\s\src\Elmah.Io.App\Controllers\AccountController.cs:строка 45 в System.Convert.FromBase64CharPtr(Char* inputPtr, Int32 inputLength) --- End of stack trace from previous location where exception was thrown --- в MyNamespace.IntParser.Execute(String s) в C:\apps\MyNamespace\IntParser.cs:строка 17 в MyNamespace.Program.Main(String[] args) в C:\apps\MyNamespace\Program.cs:строка 13</code></pre>
1010
<pre><code class="stacktrace-cn">System.Exception: Could not load file or assembly 'netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. 系统找不到指定的文件。 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在 ClrCustomVisualizerVSHost.VisualizerTargetInternal.&lt;RequestDataAsync&gt;d__10.MoveNext() --- 引发异常的上一位置中堆栈跟踪的末尾 --- 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 在 Microsoft.VisualStudio.OutOfProcessVisualizers.VisualizerTarget.&lt;RequestDataAsync&gt;d__10.MoveNext()</code></pre>
1111

12+
<pre><code class="stacktrace-multilang">Microsoft.Azure.Cosmos.Table.StorageException: The operation was canceled. ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.. ---> System.Net.Sockets.SocketException (995): The I/O operation has been aborted because of either a thread exit or an application request. ved System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token) en System.Net.Security.SslStream.&lt;FillBufferAsync&gt;g__InternalFillBufferAsync|215_0[TReadAdapter](TReadAdapter adap, ValueTask`1 task, Int32 min, Int32 initial) ---> System.Threading.Tasks.TaskCanceledException: The operation was canceled. at System.Net.Security.SslStream.ReadAsyncInternal[TReadAdapter](TReadAdapter adapter, Memory`1 buffer) at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) в System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) bei System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) 在 Microsoft.Azure.Cosmos.Table.RestExecutor.TableCommand.Executor.ExecuteAsync[T](RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext, CancellationToken token) --- End of inner exception stack trace --- at Microsoft.Azure.Cosmos.Table.RestExecutor.TableCommand.Executor.ExecuteAsync[T](RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext, CancellationToken token) at Microsoft.Azure.WebJobs.Logging.Utility.SafeExecuteAsync(CloudTable table, TableBatchOperation batch) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Logging\Internal\Utility.cs:line 178 ved Microsoft.Azure.WebJobs.Logging.Utility.WriteBatchAsync[T](ILogTableProvider logTableProvider, IEnumerable`1 e1) i C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Logging\Internal\Utility.cs:linje 268 bei Microsoft.Azure.WebJobs.Logging.LogWriter.FlushTimelineAggregateAsync(Boolean always) in C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Logging\Internal\LogWriter.cs:Zeile 265 в Microsoft.Azure.WebJobs.Logging.LogWriter.FlushCoreAsync() в C:\projects\azure-webjobs-sdk-rqm4t\src\Microsoft.Azure.WebJobs.Logging\Internal\LogWriter.cs:строка 316</code></pre>
13+
1214
<pre><code class="stacktrace-bug">Azure.Messaging.ServiceBus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance. (MessageLockLost). For troubleshooting information, see https://aka.ms/azsdk/net/servicebus/exceptions/troubleshoot. at Azure.Messaging.ServiceBus.Amqp.AmqpReceiver.ThrowLockLostException() at Azure.Messaging.ServiceBus.Amqp.AmqpReceiver.DisposeMessageAsync(Guid lockToken, Outcome outcome, TimeSpan timeout) at Azure.Messaging.ServiceBus.Amqp.AmqpReceiver.CompleteInternalAsync(Guid lockToken, TimeSpan timeout) at Azure.Messaging.ServiceBus.Amqp.AmqpReceiver.&lt;&gt;c.&lt;&lt;CompleteAsync&gt;b__43_0>d.MoveNext() --- End of stack trace from previous location --- at Azure.Messaging.ServiceBus.ServiceBusRetryPolicy.&lt;&gt;c__22`1.&lt;&lt;RunOperation&gt;b__22_0&gt;d.MoveNext() --- End of stack trace from previous location --- at Azure.Messaging.ServiceBus.ServiceBusRetryPolicy.RunOperation[T1,TResult](Func`4 operation, T1 t1, TransportConnectionScope scope, CancellationToken cancellationToken, Boolean logRetriesAsVerbose) at Azure.Messaging.ServiceBus.ServiceBusRetryPolicy.RunOperation[T1](Func`4 operation, T1 t1, TransportConnectionScope scope, CancellationToken cancellationToken) at Azure.Messaging.ServiceBus.Amqp.AmqpReceiver.CompleteAsync(Guid lockToken, CancellationToken cancellationToken) at Azure.Messaging.ServiceBus.ServiceBusReceiver.CompleteMessageAsync(ServiceBusReceivedMessage message, CancellationToken cancellationToken) at Azure.Messaging.ServiceBus.ReceiverManager.ProcessOneMessage(ServiceBusReceivedMessage triggerMessage, CancellationToken cancellationToken)</code></pre>
1315
<pre><code class="stacktrace-ultimate">System.AggregateException: One or more errors occurred. (One of the identified items was in an invalid format.) (Object reference not set to an instance of an object.) ---&gt; System.FormatException: One of the identified items was in an invalid format. at ConsoleApp.A.X() in C:\projects\ConsoleApp\A.cs:line 13 at Program.&lt;&gt;c.&lt;&lt;Main&gt;Program.&lt;&gt;c.&lt;&lt;Main&gt;Program.&lt;&gt;c.&lt;&lt;Main&gt;$&gt;b__0_0()gt;b__0_0gt;b__0_0() in C:\projects\ConsoleApp\Program.cs:line 12 at Program.&lt;&lt;Main&gt;Program.&lt;&lt;Main&gt;Program.&lt;&lt;Main&gt;$&gt;g__CaptureException|0_1(Action action)gt;g__CaptureException|0_1gt;g__CaptureException|0_1(Action action) in C:\projects\ConsoleApp\Program.cs:line 45 --- End of stack trace from previous location --- at Program.&lt;Main&gt;$(String[] args) in C:\projects\ConsoleApp\Program.cs:line 11 --- End of inner exception stack trace --- ---&gt; (Inner Exception #1) System.NullReferenceException: Object reference not set to an instance of an object. at ConsoleApp.B.Y() in C:\projects\ConsoleApp\A.cs:line 21 at Program.&lt;Main&gt;$(String[] args) in C:\projects\ConsoleApp\Program.cs:line 22&lt;---</code></pre>
1416

0 commit comments

Comments
 (0)