Add Sentry error reporting

This commit is contained in:
2026-08-26 17:08:50 +00:00
parent a92293eefa
commit 1b42150ed6
20 changed files with 86 additions and 3 deletions

View File

@@ -9,12 +9,13 @@ Dedykowana usługa Windows zastępująca proces Node na stanowiskach z enova365.
- Operacje Enovy są wykonywane pojedynczo i w osobnych sesjach. Zapytania SQL korzystają z puli połączeń i mogą działać równolegle.
- ZO jest zapisywane transakcyjnie, domyślnie do bufora. Powtarzający się numer obcy nie jest blokowany.
- Pierwsza wersja obsługuje PLN, `price_netto` i rabat procentowy `0-100`.
- Sentry raportuje błędy krytyczne procesu oraz nieoczekiwane błędy procedur Enovy i SQL, oznaczone portalem i nazwą procedury WAMP.
## Konfiguracja
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny i tryb zapisu. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
Plik `config/enova.json` zawiera portal WAMP, ścieżkę instalacji, alias bazy, operatora wraz z hasłem, connection string SQL, definicję dokumentu, magazyn domyślny, tryb zapisu oraz DSN Sentry. Repozytorium oraz paczka zawierają tylko `config/enova.json.sample`. Skrypt instalacyjny kopiuje wzorzec do `config/enova.json` wyłącznie wtedy, gdy lokalna konfiguracja jeszcze nie istnieje.
Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
Uzupełnij pola `enova.operator`, `enova.password` i `sql.connectionString` bezpośrednio w lokalnym pliku `config/enova.json`. Parametr `sentry.dsn` wskazuje projekt Sentry; pusty lub pominięty DSN wyłącza wysyłanie błędów. Jeżeli operator lub hasło Enovy nie są podane, usługa nie loguje się do bazy Enovy i nie rejestruje endpointu `eu.smartb2b.place_order`; endpoint SQL oraz diagnostyka pozostają dostępne. Po zmianie konfiguracji trzeba zrestartować usługę. Connection string powinien używać konta SQL o możliwie najmniejszych uprawnieniach; `sql_raw` celowo dopuszcza również polecenia modyfikujące dane dla zgodności z obecną usługą.
## Budowanie i testy

View File

@@ -32,6 +32,7 @@
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Sentry.dll" to="%BASE%\..\Sentry.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />

BIN
release/Sentry.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -9,6 +9,7 @@
"SmartB2B.Enova.Service/1.0.0": {
"dependencies": {
"Newtonsoft.Json": "13.0.3",
"Sentry": "6.9.0",
"SmartB2B.Enova.Contracts": "1.0.0",
"WampSharp.Default.Client": "23.8.1"
},
@@ -57,6 +58,14 @@
}
}
},
"Sentry/6.9.0": {
"runtime": {
"lib/net8.0/Sentry.dll": {
"assemblyVersion": "6.9.0.0",
"fileVersion": "6.9.0.0"
}
}
},
"System.Collections/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
@@ -396,6 +405,13 @@
"path": "newtonsoftmessagepack/0.1.11",
"hashPath": "newtonsoftmessagepack.0.1.11.nupkg.sha512"
},
"Sentry/6.9.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-qQIvEwuvjAB6fDLVLLcDj/5f8n5jOyPyHjj3a/GQ1ogTLLQqsSxgQj1fEEquNT9HQuj4ZTyCg3c1DCBMUIvJGQ==",
"path": "sentry/6.9.0",
"hashPath": "sentry.6.9.0.nupkg.sha512"
},
"System.Collections/4.3.0": {
"type": "package",
"serviceable": true,

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -17,6 +17,9 @@
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
"commandTimeoutSeconds": 30
},
"sentry": {
"dsn": "https://9ae7cbe5e29f4a85b2a748a78e55a868@glitchtip.glebocka.synology.me/10"
},
"diagnostics": {
"logDirectory": "daemon",
"logPrefix": "smartb2bsync-enova"

View File

@@ -32,6 +32,7 @@
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Newtonsoft.Json.dll" to="%BASE%\..\Newtonsoft.Json.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/NewtonsoftMessagePack.dll" to="%BASE%\..\NewtonsoftMessagePack.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/Sentry.dll" to="%BASE%\..\Sentry.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Reactive.dll" to="%BASE%\..\System.Reactive.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/System.Runtime.InteropServices.WindowsRuntime.dll" to="%BASE%\..\System.Runtime.InteropServices.WindowsRuntime.dll" failOnError="false" />
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-enova/WampSharp.dll" to="%BASE%\..\WampSharp.dll" failOnError="false" />

View File

@@ -14,6 +14,8 @@ public sealed class ServiceSettings
public SqlSettings Sql { get; init; } = new();
public SentrySettings Sentry { get; init; } = new();
public DiagnosticsSettings Diagnostics { get; init; } = new();
public static ServiceSettings Load(string path)
@@ -137,6 +139,11 @@ public sealed class SqlSettings
public int CommandTimeoutSeconds { get; init; } = 30;
}
public sealed class SentrySettings
{
public string? Dsn { get; init; }
}
public sealed class DiagnosticsSettings
{
public string LogDirectory { get; init; } = "daemon";

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using Sentry;
using SmartB2B.Enova.Contracts;
using SmartB2B.Enova.Service.Rpc;
@@ -73,6 +74,11 @@ public sealed class PlaceOrderHandler
}
catch (EnovaOperationException exception)
{
SentrySdk.CaptureException(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.place_order");
scope.SetTag("wamp.error_uri", exception.ErrorUri);
});
throw WampErrorFactory.FromEnova(exception);
}

View File

@@ -5,6 +5,7 @@ using SmartB2B.Enova.Service.Orders;
using SmartB2B.Enova.Service.Rpc;
using SmartB2B.Enova.Service.Runtime;
using SmartB2B.Enova.Service.Sql;
using Sentry;
using WampSharp.V2.Rpc;
namespace SmartB2B.Enova.Service;
@@ -13,6 +14,7 @@ internal static class Program
{
public static async Task<int> Main(string[] args)
{
IDisposable? sentry = null;
var cancellation = new CancellationTokenSource();
Console.CancelKeyPress += (_, eventArgs) =>
{
@@ -25,6 +27,7 @@ internal static class Program
{
var commandLine = CommandLineOptions.Parse(args);
var settings = ServiceSettings.Load(commandLine.ConfigurationPath);
sentry = InitializeSentry(settings);
var runtime = settings.ResolveRuntimeSettings();
using var adapterLoader = new EnovaAdapterLoader(runtime.EnovaInstallationPath);
@@ -98,8 +101,34 @@ internal static class Program
catch (Exception exception)
{
Console.Error.WriteLine($"Błąd krytyczny: {exception}");
SentrySdk.CaptureException(exception);
await SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
return 1;
}
finally
{
sentry?.Dispose();
}
}
private static IDisposable? InitializeSentry(ServiceSettings settings)
{
if (string.IsNullOrWhiteSpace(settings.Sentry.Dsn))
{
Console.WriteLine("Sentry wyłączone (brak parametru sentry.dsn).");
return null;
}
var sentry = SentrySdk.Init(options =>
{
options.Dsn = settings.Sentry.Dsn;
options.IsGlobalModeEnabled = true;
options.Release = typeof(Program).Assembly.GetName().Version?.ToString();
});
SentrySdk.ConfigureScope(scope => scope.SetTag("portal", settings.Portal));
Console.WriteLine("Raportowanie błędów do Sentry włączone.");
return sentry;
}
}

View File

@@ -1,6 +1,7 @@
using WampSharp.Core.Serialization;
using WampSharp.V2.Core.Contracts;
using WampSharp.V2.Rpc;
using Sentry;
namespace SmartB2B.Enova.Service.Rpc;
@@ -99,6 +100,9 @@ public sealed class DelegateRpcOperation : IWampRpcOperation
catch (Exception exception)
{
Console.Error.WriteLine($"Nieobsłużony błąd procedury {Procedure}: {exception}");
SentrySdk.CaptureException(
exception,
scope => scope.SetTag("rpc.procedure", Procedure));
var details = formatter.Serialize(new Dictionary<string, object>());
var keywordArguments = formatter.Serialize(new Dictionary<string, object?>
{

View File

@@ -10,6 +10,7 @@
<ItemGroup>
<ProjectReference Include="../SmartB2B.Enova.Contracts/SmartB2B.Enova.Contracts.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Sentry" Version="6.9.0" />
<PackageReference Include="WampSharp.Default.Client" Version="23.8.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,6 @@
using System.Data.Common;
using Newtonsoft.Json;
using Sentry;
using SmartB2B.Enova.Service.Rpc;
namespace SmartB2B.Enova.Service.Sql;
@@ -98,6 +99,11 @@ public sealed class SqlRawHandler
var code = exception.GetType().GetProperty("Number")?.GetValue(exception)
?? exception.ErrorCode;
Console.Error.WriteLine($"Błąd SQL {code}: {exception.Message}");
SentrySdk.CaptureException(exception, scope =>
{
scope.SetTag("rpc.procedure", "eu.smartb2b.sql_raw");
scope.SetExtra("sql.error_code", code);
});
throw WampErrorFactory.Create(
"eu.smartb2b.sql_error",
exception.Message,

View File

@@ -17,6 +17,9 @@
"connectionString": "Server=...;Database=...;User ID=...;Password=...;TrustServerCertificate=True",
"commandTimeoutSeconds": 30
},
"sentry": {
"dsn": "https://9ae7cbe5e29f4a85b2a748a78e55a868@glitchtip.glebocka.synology.me/10"
},
"diagnostics": {
"logDirectory": "daemon",
"logPrefix": "smartb2bsync-enova"

View File

@@ -207,6 +207,9 @@ static Task SecretsComeFromConfigurationFile()
"connectionString": "Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
"commandTimeoutSeconds": 30
},
"sentry": {
"dsn": "https://public@example.com/1"
},
"diagnostics": {
"logDirectory": "C:/logs",
"logPrefix": "test"
@@ -214,8 +217,10 @@ static Task SecretsComeFromConfigurationFile()
}
""");
var runtime = ServiceSettings.Load(path).ResolveRuntimeSettings();
var settings = ServiceSettings.Load(path);
var runtime = settings.ResolveRuntimeSettings();
AssertEqual("https://public@example.com/1", settings.Sentry.Dsn, "DSN Sentry");
AssertEqual(
"haslo-z-pliku",
runtime.EnovaConfiguration?.Connection.Password,