Fix live log reads and timestamp service output
This commit is contained in:
1 parent
bb1f2d530d
commit
75ef1ed69c
17 files changed
+198
-6
No files matched your search
@@ -62,7 +62,9 @@ operatora:
|
||||
Szczegółowe logowanie włącza parametr `/debug` albo `/verbose` (obsługiwane są też
|
||||
warianty `--debug` i `--verbose`). Wyłącza je brak parametru lub jawny parametr
|
||||
`/nodebug` albo `/noverbose`. Błędy są zapisywane do `stderr` niezależnie od tego
|
||||
ustawienia. W usłudze Windows parametr można włączyć przez odkomentowanie elementu
|
||||
ustawienia. Każdy wiersz `stdout` i `stderr` zaczyna się znacznikiem czasu ISO 8601
|
||||
w UTC, np. `2026-09-18T12:34:56.7890000+00:00`. W usłudze Windows parametr można
|
||||
włączyć przez odkomentowanie elementu
|
||||
`<arguments>/verbose</arguments>` w `daemon-enova/smartb2bsync-enova.xml` i restart usługi.
|
||||
|
||||
W trybie szczegółowym zapis ZO raportuje kolejne etapy: walidację żądania, logowanie
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -52,8 +52,8 @@
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/Microsoft.VisualBasic.dll" to="%BASE%\..\Microsoft.VisualBasic.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/Microsoft.Win32.Primitives.dll" to="%BASE%\..\Microsoft.Win32.Primitives.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/Microsoft.Win32.Registry.dll" to="%BASE%\..\Microsoft.Win32.Registry.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscordaccore.dll" to="%BASE%\..\mscordaccore.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscordaccore_x86_x86_8.0.3126.42015.dll" to="%BASE%\..\mscordaccore_x86_x86_8.0.3126.42015.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscordaccore.dll" to="%BASE%\..\mscordaccore.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscordbi.dll" to="%BASE%\..\mscordbi.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscorlib.dll" to="%BASE%\..\mscorlib.dll" failOnError="false" />
|
||||
<download from="https://demo.smartb2b.eu/files/smartb2b-sync-optima/mscorrc.dll" to="%BASE%\..\mscorrc.dll" failOnError="false" />
|
||||
|
||||
@@ -11,6 +11,7 @@ internal static class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
ConsoleLogger.InstallTimestampedOutput();
|
||||
IDisposable? sentry = null;
|
||||
IErpOrderAdapter? adapter = null;
|
||||
var cancellation = new CancellationTokenSource();
|
||||
|
||||
@@ -11,6 +11,7 @@ internal static class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
ConsoleLogger.InstallTimestampedOutput();
|
||||
IDisposable? sentry = null;
|
||||
IErpOrderAdapter? adapter = null;
|
||||
var cancellation = new CancellationTokenSource();
|
||||
|
||||
@@ -4,6 +4,12 @@ public static class ConsoleLogger
|
||||
{
|
||||
public static bool VerboseEnabled { get; private set; }
|
||||
|
||||
public static void InstallTimestampedOutput()
|
||||
{
|
||||
Console.SetOut(new TimestampedTextWriter(Console.Out));
|
||||
Console.SetError(new TimestampedTextWriter(Console.Error));
|
||||
}
|
||||
|
||||
public static void Configure(bool verboseEnabled) => VerboseEnabled = verboseEnabled;
|
||||
|
||||
public static void Verbose(string message)
|
||||
|
||||
@@ -88,9 +88,16 @@ public sealed partial class DiagnosticsHandler
|
||||
|
||||
var compactDate = date.Replace("-", string.Empty, StringComparison.Ordinal);
|
||||
var path = Path.Combine(_logDirectory, $"{_logPrefix}_{compactDate}.{stream}.log");
|
||||
return File.Exists(path)
|
||||
? await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(false)
|
||||
: null;
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await using var log = new FileStream(
|
||||
path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete,
|
||||
bufferSize: 4096, useAsync: true);
|
||||
using var reader = new StreamReader(log);
|
||||
return await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[GeneratedRegex("^\\d{4}-\\d{2}-\\d{2}$", RegexOptions.CultureInvariant)]
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace SmartB2B.Sync.Service.Diagnostics;
|
||||
|
||||
internal sealed class TimestampedTextWriter : TextWriter
|
||||
{
|
||||
private readonly TextWriter _inner;
|
||||
private readonly object _sync = new();
|
||||
private bool _atLineStart = true;
|
||||
|
||||
public TimestampedTextWriter(TextWriter inner) => _inner = inner;
|
||||
|
||||
public override Encoding Encoding => _inner.Encoding;
|
||||
|
||||
public override void Write(char value)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
WriteCore(value.ToString().AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(string? value)
|
||||
{
|
||||
if (value is null) return;
|
||||
lock (_sync)
|
||||
{
|
||||
WriteCore(value.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(char[] buffer, int index, int count)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(buffer);
|
||||
lock (_sync)
|
||||
{
|
||||
WriteCore(buffer.AsSpan(index, count));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<char> buffer)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
WriteCore(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteLine(string? value)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (value is not null) WriteCore(value.AsSpan());
|
||||
WriteCore(NewLine.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteLine()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
WriteCore(NewLine.AsSpan());
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_inner.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCore(ReadOnlySpan<char> text)
|
||||
{
|
||||
while (!text.IsEmpty)
|
||||
{
|
||||
if (_atLineStart)
|
||||
{
|
||||
_inner.Write(DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture));
|
||||
_inner.Write(' ');
|
||||
_atLineStart = false;
|
||||
}
|
||||
|
||||
var newline = text.IndexOf('\n');
|
||||
var length = newline >= 0 ? newline + 1 : text.Length;
|
||||
_inner.Write(text[..length]);
|
||||
_atLineStart = newline >= 0;
|
||||
text = text[length..];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SmartB2B.Optima.Adapter;
|
||||
using SmartB2B.Sync.Contracts;
|
||||
using SmartB2B.Sync.Service.Diagnostics;
|
||||
using SmartB2B.Sync.Service.Orders;
|
||||
using SmartB2B.Sync.Service.Rpc;
|
||||
using WampSharp.V2.Core.Contracts;
|
||||
@@ -26,7 +28,9 @@ var tests = new (string Name, Func<Task> Run)[]
|
||||
("Wynik Optimy zachowuje kwargs WAMP", HandlerReturnsKeywordResult),
|
||||
("Operacje zamówień są serializowane", ConcurrentOrdersAreSerialized),
|
||||
("Fasada COM działa na jednym wątku STA", FacadeRunsOnSingleStaThread),
|
||||
("Adapter serializuje bezpośrednie wywołania COM", AdapterSerializesComCalls)
|
||||
("Adapter serializuje bezpośrednie wywołania COM", AdapterSerializesComCalls),
|
||||
("Diagnostyka czyta log podczas zapisu", DiagnosticsReadsOpenLogs),
|
||||
("Każdy wiersz logu ma czas ISO", ConsoleOutputHasIsoTimestamps)
|
||||
};
|
||||
|
||||
var failures = 0;
|
||||
@@ -111,6 +115,84 @@ static async Task HandlerMapsOptimaContract()
|
||||
AssertEqual(123m, request.Items[0].UnitPriceGross, "brutto");
|
||||
}
|
||||
|
||||
static async Task DiagnosticsReadsOpenLogs()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), $"smartb2b-log-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(directory);
|
||||
try
|
||||
{
|
||||
var diagnostics = new DiagnosticsHandler("test", "1", ".NET", "test", null, directory, "service");
|
||||
var invocation = new RpcInvocation([], new Dictionary<string, object?> { ["date"] = "2026-09-18" });
|
||||
|
||||
foreach (var stream in new[] { "out", "err" })
|
||||
{
|
||||
var path = Path.Combine(directory, $"service_20260918.{stream}.log");
|
||||
await using var writer = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
|
||||
var content = $"Log {stream} zażółć";
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(content);
|
||||
await writer.WriteAsync(bytes);
|
||||
await writer.FlushAsync();
|
||||
|
||||
var result = stream == "out"
|
||||
? await diagnostics.GetLogAsync(invocation, CancellationToken.None)
|
||||
: await diagnostics.GetErrorLogAsync(invocation, CancellationToken.None);
|
||||
AssertEqual(content, result, $"odczyt otwartego pliku {stream}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
static Task ConsoleOutputHasIsoTimestamps()
|
||||
{
|
||||
var originalOut = Console.Out;
|
||||
var originalError = Console.Error;
|
||||
using var output = new StringWriter();
|
||||
using var error = new StringWriter();
|
||||
try
|
||||
{
|
||||
Console.SetOut(output);
|
||||
Console.SetError(error);
|
||||
ConsoleLogger.InstallTimestampedOutput();
|
||||
|
||||
Console.Write("pierw");
|
||||
Console.WriteLine("szy\ndrugi");
|
||||
ConsoleLogger.Error("błąd\nszczegóły");
|
||||
|
||||
AssertTimestampedLines(output.ToString(), ["pierwszy", "drugi"]);
|
||||
AssertTimestampedLines(error.ToString(), ["błąd", "szczegóły"]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Console.SetError(originalError);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static void AssertTimestampedLines(string output, string[] expectedMessages)
|
||||
{
|
||||
var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
AssertEqual(expectedMessages.Length, lines.Length, "liczba wierszy logu");
|
||||
for (var index = 0; index < lines.Length; index++)
|
||||
{
|
||||
var line = lines[index].TrimEnd('\r');
|
||||
var separator = line.IndexOf(' ');
|
||||
if (separator < 0 ||
|
||||
!DateTimeOffset.TryParseExact(line[..separator], "O", CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None, out var timestamp) ||
|
||||
timestamp.Offset != TimeSpan.Zero)
|
||||
{
|
||||
throw new InvalidOperationException($"Brak czasu ISO UTC w wierszu: {line}");
|
||||
}
|
||||
|
||||
AssertEqual(expectedMessages[index], line[(separator + 1)..], "treść wiersza logu");
|
||||
}
|
||||
}
|
||||
|
||||
static async Task HandlerMapsOptionalExtraFields()
|
||||
{
|
||||
using var adapter = new CapturingAdapter();
|
||||
|
||||
Reference in new issue
Block a user