Skip to content

Commit

Permalink
Multilanguage option added
Browse files Browse the repository at this point in the history
  • Loading branch information
ZsharE committed Jan 21, 2025
1 parent 7493b35 commit 1c57e58
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 30 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ const stack = new netStack('.stacktrace', {
prettyprint: true
});
```
##### multilanguage: boolean
Default: false.
Find multiple languages and render them accordingly.
```javascript
const stack = new netStack('.stacktrace', {
multilanguage: true
});
```

#### Ready to go css
```css
Expand Down
123 changes: 94 additions & 29 deletions netstack.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* netStack v2.1.0
* netStack v2.1.1
* A simple and easy JavaScript library for highlighting .NET stack traces
* License: Apache 2
* Author: https://elmah.io
Expand Down Expand Up @@ -34,6 +34,7 @@
// Default values for classes
this.settings = extend({
prettyprint: false,
multilanguage: false,
frame: 'st-frame',
type: 'st-type',
method: 'st-method',
Expand Down Expand Up @@ -76,6 +77,8 @@
return null;
};

netStack.prototype.allEqual = arr => arr.every(val => val === arr[0]);

netStack.prototype.replacer = function(args, at_language) {
if (args[0].substring(0).match(/(-{3}>)/)) {
return '\r\n ' + args[0];
Expand All @@ -86,7 +89,7 @@
}
};

netStack.prototype.formatException = function(exceptionMessage, at_language) {
netStack.prototype.formatException = function(exceptionMessage, at_language, loop, position) {
var result = exceptionMessage || '';
var searchReplaces = [
{
Expand All @@ -104,7 +107,12 @@
];

var self = this;
searchReplaces.forEach(function(item) {
searchReplaces.forEach(function(item, index) {
// multilanguage, skip --- lines
if (loop === true && position > 0 && index === 1) {
return;
}

if (item.repl == null) {
result = result.replace(item.find, function() {
return self.replacer(arguments, at_language);
Expand All @@ -116,6 +124,22 @@
return result;
};

netStack.prototype.detectLanguagesInOrder = function(input, regexes) {
let matches = [];

for (let [language, regex] of Object.entries(regexes)) {
let match;
while ((match = regex.exec(input)) !== null) {
matches.push({ language, index: match.index });
}
regex.lastIndex = 0;
}

matches.sort((a, b) => a.index - b.index);

return matches.map((match) => match.language);
};

netStack.prototype.init = function() {
// Get the stacktrace, sanitize it, and split it into lines
var stacktrace = this.element.textContent,
Expand All @@ -124,53 +148,94 @@
lang = '',
clone = '';

// look for the language
for (var i = 0; i < lines.length; ++i) {
if (lang === '') {
var regexes = {
english: /\s+at .*\)/,
danish: /\s+ved .*\)/,
german: /\s+bei .*\)/,
spanish: /\s+en .*\)/,
russian: /\s+в .*\)/,
chinese: /\s+ .*\)/
};

for (var key in regexes) {
if (regexes[key].test(lines[i])) {
lang = key;
break;
var languagesRegex = {
english: /\s+at .*?\)/g,
danish: /\s+ved .*?\)/g,
german: /\s+bei .*?\)/g,
spanish: /\s+en .*?\)/g,
russian: /\s+в .*?\)/g,
chinese: /\s+ .*?\)/g
};

// look for the language(s) in the stack trace
if (this.settings.multilanguage) {
lang = this.detectLanguagesInOrder(lines, languagesRegex);
} else {
for (var i = 0; i < lines.length; ++i) {
if (lang === '') {
for (var key in languagesRegex) {
if (languagesRegex[key].test(lines[i])) {
lang = key;
break;
}
}
}
}
}

if (lang === '') return;

var selectedLanguage = this.search(lang, this.languages);
this.language = selectedLanguage.name;
// if multiline option is true, check if the language is the same for all lines
if (typeof lang === 'object') {
if (this.allEqual(lang)) {
lang = lang[0];
}
}

// if lang is an array, we have multiple languages
if (Array.isArray(lang)) {
var selectedLanguage = [];
for (var i = 0; i < lang.length; ++i) {
selectedLanguage.push(this.search(lang[i], this.languages));
}
this.language = 'multilanguage';
} else if (typeof lang === 'string') {
var selectedLanguage = this.search(lang, this.languages);
this.language = selectedLanguage.name;
}

// Pritty print result if is set to true
if (this.settings.prettyprint) {
sanitizedStack = this.formatException(sanitizedStack, selectedLanguage.at);
if (Array.isArray(selectedLanguage)) {
var sanitizedStacks = sanitizedStack;
const selectedLanguages = [...new Set(selectedLanguage)];
selectedLanguages.forEach((language, index) => {
sanitizedStacks = this.formatException(sanitizedStacks, language.at, true, index);
});
sanitizedStack = sanitizedStacks;
} else {
sanitizedStack = this.formatException(sanitizedStack, selectedLanguage.at);
}

lines = sanitizedStack.split('\n');
}

if (Array.isArray(selectedLanguage)) {
var langContor = 0;
}

for (var i = 0; i < lines.length; ++i) {
var li = lines[i],
hli = new RegExp('(\\S*)' + selectedLanguage.at + ' .*\\)');
hli = new RegExp('(\\S*)' + selectedLanguage.at + ' .*\\)'),
languageSet = selectedLanguage;

if (Array.isArray(selectedLanguage)) {
hli = new RegExp('(\\S*)' + selectedLanguage[langContor].at + ' .*\\)');
languageSet = selectedLanguage[langContor];
hli.test(lines[i]) ? langContor++ : langContor;
}

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

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

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

partsFrame = partsFrame.replace(selectedLanguage.at + ' ', '');
partsFrame = partsFrame.replace(languageSet.at + ' ', '');

// Frame -> ParameterList
var regParamList = /\(.*\)/,
Expand Down Expand Up @@ -206,18 +271,18 @@
var newPartsFrame = partsFrame.replace(partsParamList, stringParam).replace(partsTypeMethod, stringTypeMethod);

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

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

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

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

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

li = li.replace(partsFrame, '<span class="' + this.settings.frame + '">' + newPartsFrame + '</span>')
.replace(partsFile, '<span class="' + this.settings.file + '">' + partsFile + '</span>')
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "netstack.js",
"version": "2.1.0",
"version": "2.1.1",
"description": "A simple and easy JavaScript library for highlighting .NET stack traces",
"main": "netstack.js",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions test/stacktraces.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<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>
<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>

<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>

<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>
<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>

Expand Down
Loading

0 comments on commit 1c57e58

Please sign in to comment.