Skip to content

Commit 037e618

Browse files
authored
Better syntax error handling (#332)
* Debugging: Add tasks and launch #!components: grid-bot,grid-bot-recovery #!deployable-components: grid-bot ~ Add build tasks for debug and full debug builds. ~ Add launch scripts for local debugging. * Projects: Bump Discord.Net Bumps Discord.Net to 3.15.3 * Update to OnLogMessage Support logging GatewayReconnectException, WebSocketException, WebSocketClosedException, and TaskCanceledException in debug builds * Feature: Script logging Implement logging of scripts to a Discord channel, helps with future debugging of issues that arrise. Has duplication detection via hash checking of script contents. * LuaVM Suit changes made to parsing system * ExecuteScript: Syntax error and script logging ~ Log scripts just before preprocessing. ~ Add detection of grid-server level syntax errors. ~ Add detection of invalid json return data. ~ Add detection of invalid ASCII return data. * Update Runner.cs ~ Add script logger to dependency injection container. * Update build.yml Signed-off-by: Nikita Petko <petko@vmminfra.net> * Update deploy.yml Signed-off-by: Nikita Petko <petko@vmminfra.net> * ScriptLogger: Persistence ~ Do not persist script hashes immediately, as this could cause a major influx of Vault writes. * Update deploy.yml Signed-off-by: Nikita Petko <petko@vmminfra.net> * Update .component.yaml Signed-off-by: Nikita Petko <petko@vmminfra.net> * Fix for hashes --------- Signed-off-by: Nikita Petko <petko@vmminfra.net>
1 parent c275ba0 commit 037e618

File tree

18 files changed

+332
-27
lines changed

18 files changed

+332
-27
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,12 +463,10 @@ jobs:
463463
let deployableComponentsMap = '';
464464
465465
for (const component of components) {
466-
let [name, version] = component.split(':');
467-
468-
if (!version) version = 'latest';
466+
const [name] = component.split(':');
469467
470468
if (deployableComponents.includes(name)) {
471-
deployableComponentsMap += `${component}:${version},`;
469+
deployableComponentsMap += `${component},`;
472470
}
473471
}
474472

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
component-search-directories: services
4848

4949
- name: Components to Nomad Jobs
50-
uses: mfdlabs/component-nomad-parser-action@v11
50+
uses: mfdlabs/component-nomad-parser-action@v14
5151
id: components-to-nomad-jobs
5252
env:
5353
NOMAD_ENVIRONMENT: ${{ github.event.inputs.nomad_environment }}

.vscode/launch.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Grid.Bot",
6+
"type": "coreclr",
7+
"request": "launch",
8+
"preLaunchTask": "build",
9+
"program": "${workspaceFolder}/services/grid-bot/src/bin/Debug/net8.0/Grid.Bot.dll",
10+
"cwd": "${workspaceFolder}/services/grid-bot/src/",
11+
"console": "internalConsole",
12+
"stopAtEntry": false,
13+
"logging": {
14+
"moduleLoad": false
15+
},
16+
"requireExactSource": false,
17+
"envFile": "${workspaceFolder}/.env"
18+
}
19+
]
20+
}

.vscode/tasks.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/services/grid-bot/src/Grid.Bot.csproj",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary",
13+
"/p:Configuration=Debug",
14+
"/p:Platform=AnyCPU",
15+
],
16+
"problemMatcher": "$msCompile"
17+
},
18+
{
19+
"label": "build-full",
20+
"command": "dotnet",
21+
"type": "process",
22+
"args": [
23+
"build",
24+
"${workspaceFolder}/services/grid-bot/src/Grid.Bot.csproj",
25+
"/property:GenerateFullPaths=true",
26+
"/consoleloggerparameters:NoSummary",
27+
"/p:Configuration=Debug",
28+
"/p:Platform=AnyCPU",
29+
"/p:LocalBuild=true"
30+
],
31+
"problemMatcher": "$msCompile"
32+
}
33+
]
34+
}

services/grid-bot/.component.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ build:
1818

1919
deployment:
2020
count: 1
21+
namespace: grid-bot
2122

2223
job: grid-bot-${{ env.NOMAD_SHORT_ENVIRONMENT }}
2324

services/grid-bot/lib/commands/Modules/ExecuteScript.cs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace Grid.Bot.Interactions.Public;
22

33
using System;
44
using System.IO;
5+
using System.Xml;
56
using System.Linq;
67
using System.Text;
78
using System.Diagnostics;
@@ -41,6 +42,7 @@ namespace Grid.Bot.Interactions.Public;
4142
/// <param name="jobManager">The <see cref="IJobManager"/>.</param>
4243
/// <param name="adminUtility">The <see cref="IAdminUtility"/>.</param>
4344
/// <param name="discordWebhookAlertManager">The <see cref="IDiscordWebhookAlertManager"/>.</param>
45+
/// <param name="scriptLogger">The <see cref="IScriptLogger"/>.</param>
4446
/// <param name="gridServerFileHelper">The <see cref="IGridServerFileHelper"/>.</param>
4547
/// <exception cref="ArgumentNullException">
4648
/// - <paramref name="logger"/> cannot be null.
@@ -52,6 +54,7 @@ namespace Grid.Bot.Interactions.Public;
5254
/// - <paramref name="jobManager"/> cannot be null.
5355
/// - <paramref name="adminUtility"/> cannot be null.
5456
/// - <paramref name="discordWebhookAlertManager"/> cannot be null.
57+
/// - <paramref name="scriptLogger"/> cannot be null.
5558
/// - <paramref name="gridServerFileHelper"/> cannot be null.
5659
/// </exception>
5760
[Group("execute", "Commands used for executing Luau code.")]
@@ -65,6 +68,7 @@ public partial class ExecuteScript(
6568
IJobManager jobManager,
6669
IAdminUtility adminUtility,
6770
IDiscordWebhookAlertManager discordWebhookAlertManager,
71+
IScriptLogger scriptLogger,
6872
IGridServerFileHelper gridServerFileHelper
6973
) : InteractionModuleBase<ShardedInteractionContext>
7074
{
@@ -83,13 +87,18 @@ IGridServerFileHelper gridServerFileHelper
8387
private readonly IJobManager _jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
8488
private readonly IAdminUtility _adminUtility = adminUtility ?? throw new ArgumentNullException(nameof(adminUtility));
8589
private readonly IDiscordWebhookAlertManager _discordWebhookAlertManager = discordWebhookAlertManager ?? throw new ArgumentNullException(nameof(discordWebhookAlertManager));
90+
private readonly IScriptLogger _scriptLogger = scriptLogger ?? throw new ArgumentNullException(nameof(scriptLogger));
8691
private readonly IGridServerFileHelper _gridServerFileHelper = gridServerFileHelper ?? throw new ArgumentNullException(nameof(gridServerFileHelper));
8792

8893

89-
[GeneratedRegex(@"```(.*?)\s(.*?)```", RegexOptions.IgnoreCase | RegexOptions.Singleline, "en-GB")]
94+
[GeneratedRegex(@"```(.*?)\s(.*?)```", RegexOptions.IgnoreCase | RegexOptions.Singleline)]
9095
private static partial Regex CodeBlockRegex();
91-
[GeneratedRegex("[\"“‘”]", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-GB")]
96+
[GeneratedRegex("[\"“‘”]", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
9297
private static partial Regex QuotesRegex();
98+
[GeneratedRegex(@"Execute Script:(\d+): (.+)", RegexOptions.IgnoreCase | RegexOptions.Compiled)]
99+
private static partial Regex GridSyntaxErrorRegex();
100+
101+
private const string _ErrorConvertingToJson = "Can't convert to JSON";
93102

94103
/// <inheritdoc cref="InteractionModuleBase{TContext}.BeforeExecuteAsync(ICommandInfo)"/>
95104
public override async Task BeforeExecuteAsync(ICommandInfo command)
@@ -164,6 +173,9 @@ private static string GetCodeBlockContents(string s)
164173
return (input, null);
165174
}
166175

176+
private async Task LuaErrorAsync(string error)
177+
=> await HandleResponseAsync(null, new() { ErrorMessage = error, ExecutionTime = 0, Success = false });
178+
167179
private async Task HandleResponseAsync(string result, ReturnMetadata metadata)
168180
{
169181
var builder = new EmbedBuilder()
@@ -284,6 +296,8 @@ string script
284296

285297
var originalScript = script;
286298

299+
await _scriptLogger.LogScriptAsync(script, Context);
300+
287301
if (ContainsUnicode(script))
288302
{
289303
await FollowupAsync("The script cannot contain unicode characters as grid-servers cannot support unicode in transit.");
@@ -360,12 +374,47 @@ string script
360374

361375
Task.Run(() => _jobManager.CloseJob(job, false));
362376

363-
await AlertForSystem(script, originalScript, scriptId, scriptName, ex);
364-
365377
if (ex is FaultException)
366378
{
367-
// Needs to be reported, get the original script, the fully constructed script and all information about channels, users, etc.
368-
await FollowupAsync("There was an internal error, please try again later.");
379+
var message = ex.Message;
380+
if (GridSyntaxErrorRegex().IsMatch(message))
381+
{
382+
var match = GridSyntaxErrorRegex().Match(message);
383+
var line = match.Groups[1].Value;
384+
var error = match.Groups[2].Value;
385+
386+
// We need to subtract the lines that the template adds (otherwise for one liners it will appear to be on line like 500 and something)
387+
if (_scriptsSettings.LuaVMEnabled)
388+
{
389+
const string _marker = "{0}";
390+
391+
var template = _luaUtility.LuaVMTemplate;
392+
var templateLines = template.Split('\n');
393+
394+
var lineIndex = Array.FindIndex(templateLines, line => line.StartsWith(_marker));
395+
396+
if (lineIndex != -1)
397+
line = (int.Parse(line) - lineIndex).ToString();
398+
}
399+
400+
await LuaErrorAsync($"Line {line}: {error}");
401+
402+
return;
403+
}
404+
405+
if (message.Contains(_ErrorConvertingToJson))
406+
{
407+
await LuaErrorAsync("The script returned a value that could not be converted to JSON.");
408+
409+
return;
410+
}
411+
}
412+
413+
// If ex.InnerException.InnerException is a XmlException, it's likely that the script returned invalid ASCII characters.
414+
// Catch this and alert the user (only in the case of ex is CommunicationException, ex.InnerException is InvalidOperationException and ex.InnerException.InnerException is XmlException)
415+
if (ex is CommunicationException && ex.InnerException is InvalidOperationException && ex.InnerException.InnerException is XmlException)
416+
{
417+
await LuaErrorAsync("The script returned invalid ASCII characters.");
369418

370419
return;
371420
}
@@ -377,6 +426,8 @@ string script
377426
return;
378427
}
379428

429+
await AlertForSystem(script, originalScript, scriptId, scriptName, ex);
430+
380431
throw;
381432
}
382433
finally

services/grid-bot/lib/commands/Shared.Commands.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
</ItemGroup>
3333

3434
<ItemGroup>
35-
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.2" />
36-
<PackageReference Include="Discord.Net.Commands" Version="3.15.2" />
37-
<PackageReference Include="Discord.Net.Interactions" Version="3.15.2" />
35+
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.3" />
36+
<PackageReference Include="Discord.Net.Commands" Version="3.15.3" />
37+
<PackageReference Include="Discord.Net.Interactions" Version="3.15.3" />
3838
<PackageReference Include="Loretta.CodeAnalysis.Lua" Version="0.2.11" />
3939
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.11.0" />
4040
<PackageReference Include="Microsoft.CodeAnalysis.Scripting.Common" Version="3.11.0" />

services/grid-bot/lib/events/Events/OnLogMessage.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public Task Invoke(LogMessage message)
9393
if (message.Exception != null)
9494
{
9595
#if DEBUG || DEBUG_LOGGING_IN_PROD
96+
#if !DEBUG // Don't log these exceptions outside of debug mode.
9697
if (message.Exception is GatewayReconnectException)
9798
return Task.CompletedTask;
9899

@@ -106,6 +107,7 @@ public Task Invoke(LogMessage message)
106107
if (message.Exception is TaskCanceledException &&
107108
!_settings.DebugAllowTaskCanceledExceptions)
108109
return Task.CompletedTask;
110+
#endif
109111

110112
_logger.Error(
111113
"Source = {0}, Message = {1}, Exception = {2}",

services/grid-bot/lib/events/Shared.Events.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.2" />
14-
<PackageReference Include="Discord.Net.Commands" Version="3.15.2" />
15-
<PackageReference Include="Discord.Net.Interactions" Version="3.15.2" />
13+
<PackageReference Include="Discord.Net.WebSocket" Version="3.15.3" />
14+
<PackageReference Include="Discord.Net.Commands" Version="3.15.3" />
15+
<PackageReference Include="Discord.Net.Interactions" Version="3.15.3" />
1616

1717
<PackageReference Include="prometheus-net" Version="8.0.1" />
1818
</ItemGroup>

services/grid-bot/lib/settings/Providers/ScriptsSettings.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace Grid.Bot;
22

3+
using System;
4+
35
/// <summary>
46
/// Settings provider for all script execution related stuff.
57
/// </summary>
@@ -31,4 +33,40 @@ public class ScriptsSettings : BaseSettingsProvider
3133
nameof(LuaVMEnabled),
3234
true
3335
);
36+
37+
/// <summary>
38+
/// Gets the percentage to use for logging scripts.
39+
/// </summary>
40+
public int ScriptLoggingPercentage => GetOrDefault(
41+
nameof(ScriptLoggingPercentage),
42+
0
43+
);
44+
45+
/// <summary>
46+
/// Gets a Discord webhook URL to send script logs to.
47+
/// </summary>
48+
public string ScriptLoggingDiscordWebhookUrl => GetOrDefault(
49+
nameof(ScriptLoggingDiscordWebhookUrl),
50+
string.Empty
51+
);
52+
53+
/// <summary>
54+
/// Gets or sets a list of hashes of scripts that have already been logged and should not be logged again.
55+
/// </summary>
56+
public string[] LoggedScriptHashes
57+
{
58+
get => GetOrDefault(
59+
nameof(LoggedScriptHashes),
60+
Array.Empty<string>()
61+
);
62+
set => Set(nameof(LoggedScriptHashes), value);
63+
}
64+
65+
/// <summary>
66+
/// Gets the interval to persist the logged script hashes.
67+
/// </summary>
68+
public TimeSpan LoggedScriptHashesPersistInterval => GetOrDefault(
69+
nameof(LoggedScriptHashesPersistInterval),
70+
TimeSpan.FromMinutes(5)
71+
);
3472
}

0 commit comments

Comments
 (0)