Enhance logging in FdsSqlOptions and related classes
- Updated FdsSqlOptions to accept an optional ILogger parameter for improved error logging. - Modified FdsMfr and FdsMfrClient classes to pass the logger instance to FdsSqlOptions. - Added detailed error logging in various methods to capture SQL execution issues and file handling errors. - Improved documentation for FdsSqlOptions to clarify logging behavior. - Updated Archive class to log compression errors, enhancing traceability of failures. - Adjusted project configuration to suppress specific warnings related to transitive dependencies. - Added NuGet.config to define package sources for dependency management. - Updated submodule references for OCORE and related projects.
This commit is contained in:
+42
-14
@@ -13,15 +13,26 @@ using static OCORE.commons;
|
||||
|
||||
namespace fds;
|
||||
|
||||
/// <summary>
|
||||
/// Logs every SQL error both to the worker's structured <see cref="ILogger"/> (so it reaches
|
||||
/// whatever sink/OTel export is configured) and to <see cref="FdsDebug.DebugLog"/> (SQL/local-file
|
||||
/// debug trail). Passing <paramref name="logger"/> is optional for call sites that don't have one
|
||||
/// in scope, but every caller that does have one in scope should pass it — otherwise a failing
|
||||
/// stored procedure only ever shows up in the debug trail, never in the worker's own logs.
|
||||
/// </summary>
|
||||
public class FdsSqlOptions : sqloptions
|
||||
{
|
||||
private Dictionary<string, object>? _baseInfo;
|
||||
|
||||
public FdsSqlOptions(Dictionary<string, object>? dic = null)
|
||||
public FdsSqlOptions(Dictionary<string, object>? dic = null, ILogger? logger = null)
|
||||
{
|
||||
_baseInfo = dic;
|
||||
OnError = (procedure, ex, data) =>
|
||||
{
|
||||
logger?.LogError(ex, "SQL error in {Procedure}: {Message} — ctrl_nfo={CtrlInfo} sql={Data}",
|
||||
procedure, ex.Message, dic, data);
|
||||
FdsDebug.DebugLog(procedure, exc: ex, data: $"ctrl_nfo={dic}, sql={data}");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +99,7 @@ public class FdsMfr : IFdsMfr
|
||||
if (debugDetails) FdsDebug.DebugToFile("GetInvoiceFiles_async - start", filename: "DebugDetail.txt");
|
||||
var dtbl = await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fds__fn_getMFRInvoicesWithoutfiles];",
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: null, options: new FdsSqlOptions());
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: null, options: new FdsSqlOptions(logger: _logger));
|
||||
if (dtbl.Count > 0)
|
||||
{
|
||||
var rows = dtbl.DataTable.Rows.Cast<DataRow>()
|
||||
@@ -115,7 +126,7 @@ public class FdsMfr : IFdsMfr
|
||||
SQL_VarChar("@Id", r.id),
|
||||
SQL_VarChar("@filename", r.docName),
|
||||
new SqlParameter("@file", fl) { SqlDbType = SqlDbType.VarBinary }),
|
||||
options: new FdsSqlOptions());
|
||||
options: new FdsSqlOptions(logger: _logger));
|
||||
Interlocked.Increment(ref downloaded);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +153,7 @@ public class FdsMfr : IFdsMfr
|
||||
var pl = new List<SqlParameter> { SQL_VarChar("@reportid", reportid) };
|
||||
var sqldt = Task.Run(async () => await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fds__getReportDocument] @reportid;",
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: pl, options: new FdsSqlOptions())).Result;
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: pl, options: new FdsSqlOptions(logger: _logger))).Result;
|
||||
|
||||
if (sqldt.Count > 0)
|
||||
{
|
||||
@@ -166,7 +177,7 @@ public class FdsMfr : IFdsMfr
|
||||
SQL_VarChar("@Id", reportid),
|
||||
SQL_VarChar("@filename", fln),
|
||||
new SqlParameter("@file", fl) { SqlDbType = SqlDbType.VarBinary }),
|
||||
options: new FdsSqlOptions())).Wait();
|
||||
options: new FdsSqlOptions(logger: _logger))).Wait();
|
||||
}
|
||||
catch (Exception fsex) { FdsDebug.DebugLog("getReportDoc - mfr storefile", exc: fsex); }
|
||||
}
|
||||
@@ -197,7 +208,7 @@ public class FdsMfr : IFdsMfr
|
||||
var pl = new List<SqlParameter> { SQL_VarChar("@type", type), SQL_VarChar("@reportid", reportid) };
|
||||
var sqldt = Task.Run(async () => await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fds__getFDSDocument] @type, @reportid;",
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: pl, options: new FdsSqlOptions())).Result;
|
||||
FdsShared.FDSConnectionString(), SqlParameterList: pl, options: new FdsSqlOptions(logger: _logger))).Result;
|
||||
|
||||
if (sqldt.Count > 0)
|
||||
{
|
||||
@@ -236,14 +247,14 @@ public class FdsMfr : IFdsMfr
|
||||
SQL_Bit("@files", includeFiles), SQL_VarChar("@authuser", authUser)
|
||||
},
|
||||
tablenames: new[] { "admin", "inv", "buchungen", "debitoren" },
|
||||
options: new FdsSqlOptions())).Result;
|
||||
options: new FdsSqlOptions(logger: _logger))).Result;
|
||||
|
||||
var bediFiles = new List<DatevDocument>();
|
||||
|
||||
if (dset.Count >= 4)
|
||||
{
|
||||
var admin = dset.Tables("admin").Rows[0].toObjectDictionary();
|
||||
DateTime startdate = (DateTime)admin["startdate"], enddate = (DateTime)admin["enddate"];
|
||||
DateTime startdate = (DateTime)admin["startdate"]!, enddate = (DateTime)admin["enddate"]!;
|
||||
string register = tgtdate.ToString("yyyy\\/MM") + (mode.ToLower() == "w" ? "_w" + tgtdate.ToString("dd") : "");
|
||||
var fls = new Dictionary<string, byte[]>();
|
||||
|
||||
@@ -251,10 +262,10 @@ public class FdsMfr : IFdsMfr
|
||||
{
|
||||
Formatkategorie = DatevFormatkategorie.Buchungsstapel,
|
||||
Formatversion = DatevFormatversion.Buchungsstapel_9,
|
||||
Beraternummer = (int)admin["beraternummer"],
|
||||
Mandantennummer = (int)admin["mandantennummer"],
|
||||
WJBeginn = (DateTime)admin["WJ-Beginn"],
|
||||
Sachkontenlänge = (int)admin["Sachkontenlänge"],
|
||||
Beraternummer = (int)admin["beraternummer"]!,
|
||||
Mandantennummer = (int)admin["mandantennummer"]!,
|
||||
WJBeginn = (DateTime)admin["WJ-Beginn"]!,
|
||||
Sachkontenlänge = (int)admin["Sachkontenlänge"]!,
|
||||
DatumVon = startdate,
|
||||
DatumBis = enddate,
|
||||
Bezeichnung = "fds_" + mode + tgtdate.ToString("yyMMdd")
|
||||
@@ -284,7 +295,14 @@ public class FdsMfr : IFdsMfr
|
||||
else
|
||||
fl = irow.no("file", null) as byte[];
|
||||
}
|
||||
catch { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Silently omitting this file from the DATEV export previously left no
|
||||
// trace at all — the export "succeeded" while quietly missing a document.
|
||||
_logger.LogWarning(ex,
|
||||
"getDatevZip: failed to fetch invoice file — invoiceId={InvoiceId} fileName={FileName}",
|
||||
irow.nz("InvoiceId"), fln);
|
||||
}
|
||||
if (fl != null && !string.IsNullOrEmpty(fln))
|
||||
{
|
||||
fls.Add(fln, fl);
|
||||
@@ -308,8 +326,18 @@ public class FdsMfr : IFdsMfr
|
||||
stream!.Position = 0;
|
||||
return archiveFile;
|
||||
}
|
||||
_logger.LogError(
|
||||
"getDatevZip: CompressToStream returned false — archiveFile={ArchiveFile} fileCount={FileCount}",
|
||||
archiveFile.FullName, fls.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Previously swallowed entirely: a DATEV export could fail at the archive
|
||||
// step with zero trace anywhere, not even the DB debug log.
|
||||
_logger.LogError(ex,
|
||||
"getDatevZip: archive creation failed — archiveFile={ArchiveFile} fileCount={FileCount}",
|
||||
archiveFile.FullName, fls.Count);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class FdsMfrClient : IDisposable
|
||||
public Dictionary<string, Dictionary<string, string>> NavProperties { get; } = new();
|
||||
private readonly DataSet _tableSet;
|
||||
|
||||
public DatabaseSchema(EntityTypes et)
|
||||
public DatabaseSchema(EntityTypes et, ILogger? logger = null)
|
||||
{
|
||||
_et = et;
|
||||
var dset = Task.Run(async () => await getSQLDataSet_async(
|
||||
@@ -72,7 +72,7 @@ public class FdsMfrClient : IDisposable
|
||||
FdsShared.FDSConnectionString(),
|
||||
SqlParameterList: new List<SqlParameter> { new("@tgttype", ThisEntityName) },
|
||||
tablenames: new[] { "entity", "complex_types", "navigation_properties", "tables" },
|
||||
options: new FdsSqlOptions())).Result;
|
||||
options: new FdsSqlOptions(logger: logger))).Result;
|
||||
|
||||
IsValid = dset.Count > 0;
|
||||
HasEntity = dset.Tables("entity").Rows.Count == 1;
|
||||
@@ -98,7 +98,7 @@ public class FdsMfrClient : IDisposable
|
||||
string.Join(Environment.NewLine, sqlList),
|
||||
FdsShared.FDSConnectionString(),
|
||||
tablenames: tableNames.ToArray(),
|
||||
options: new FdsSqlOptions())).Result.DataSet;
|
||||
options: new FdsSqlOptions(logger: logger))).Result.DataSet;
|
||||
}
|
||||
|
||||
public DataSet TgtDataset(string setId)
|
||||
@@ -147,7 +147,7 @@ public class FdsMfrClient : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var schema = schemaDic != null && schemaDic.TryGetValue(thisEntityName, out var s) ? s : new DatabaseSchema(et);
|
||||
var schema = schemaDic != null && schemaDic.TryGetValue(thisEntityName, out var s) ? s : new DatabaseSchema(et, _logger);
|
||||
if (!schema.IsValid)
|
||||
{
|
||||
dlg("Schema not found", "", "");
|
||||
@@ -179,7 +179,7 @@ public class FdsMfrClient : IDisposable
|
||||
{
|
||||
var lastDate = await getSQLValue_async(
|
||||
schema.EntityConfig.nz("DateSQL").ne($"SELECT MAX([{schema.EntityConfig["DateColumn"]}]) FROM [dbo].[{schema.EntityTableName}];"),
|
||||
FdsShared.FDSConnectionString(), options: new FdsSqlOptions());
|
||||
FdsShared.FDSConnectionString(), options: new FdsSqlOptions(logger: _logger));
|
||||
filter.Add(
|
||||
updateNeed == FdsMfr.UpdateNeed.Short && lastDate.Result is DateTime dt
|
||||
? $"$filter={schema.EntityConfig["DateColumn"]} gt DateTime'{dt:yyyy-MM-dd}T00:00:00'"
|
||||
@@ -395,7 +395,7 @@ public class FdsMfrClient : IDisposable
|
||||
{
|
||||
string sql = "SELECT * FROM [dbo].[fds__getUpdateableTables]()"
|
||||
+ (tgtEntityType.HasValue ? $" WHERE [entity_name] = '{tgtEntityType.Value}'" : "") + ";";
|
||||
var updateableTables = await getSQLDatatable_async(sql, FdsShared.FDSConnectionString(), options: new FdsSqlOptions());
|
||||
var updateableTables = await getSQLDatatable_async(sql, FdsShared.FDSConnectionString(), options: new FdsSqlOptions(logger: _logger));
|
||||
dtf("UpdateableTables", updateableTables.Exception ?? "", $"({(updateableTables.Count > 0 ? updateableTables.DataTable.Rows.Count.ToString() : " no")} Rows)", null);
|
||||
|
||||
if (updateableTables.Count > 0 && updateableTables.DataTable.Columns.Contains("updateneed"))
|
||||
@@ -446,7 +446,7 @@ public class FdsMfrClient : IDisposable
|
||||
{
|
||||
var updateableRequests = await getSQLDatatable_async(
|
||||
"SELECT * FROM [dbo].[fds__getUpdateableRequests]();",
|
||||
FdsShared.FDSConnectionString(), options: new FdsSqlOptions());
|
||||
FdsShared.FDSConnectionString(), options: new FdsSqlOptions(logger: _logger));
|
||||
dtf("UpdateableRequests", updateableRequests.Exception ?? "", $"({(updateableRequests.Count > 0 ? updateableRequests.DataTable.Rows.Count.ToString() : " no")} Rows)", null);
|
||||
|
||||
if (updateableRequests.Count > 0)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace fds;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the application <see cref="IConfiguration"/> built from appsettings.json.
|
||||
/// Call <see cref="Initialize"/> once at startup before accessing <see cref="Current"/>.
|
||||
/// Call <see cref="Initialize()"/> once at startup before accessing <see cref="Current"/>.
|
||||
/// </summary>
|
||||
public static class FdsConfig
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace fds;
|
||||
|
||||
public class Archive : IDisposable
|
||||
{
|
||||
public event Action? Saving;
|
||||
public event Action? FileSaved;
|
||||
public event Action? FileStreamCreated;
|
||||
|
||||
@@ -140,8 +139,12 @@ public class Archive : IDisposable
|
||||
_zipIn = null;
|
||||
ZipInOK = false;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Previously silent: callers (e.g. HandleDatevZip) saw only a bare failed result
|
||||
// with no way to tell a compression error from any other reason ExitOK is false.
|
||||
_logger.LogError(ex, "Archive.Compress failed — archiveFile={ArchiveFile} fileCount={FileCount}",
|
||||
archiveFile.FullName, files.Count);
|
||||
ExitOK = false;
|
||||
}
|
||||
archiveFile.Refresh();
|
||||
@@ -179,8 +182,11 @@ public class Archive : IDisposable
|
||||
_zipIn = null;
|
||||
ZipInOK = false;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Previously silent: the DATEV export ZIP-to-stream path failed with no
|
||||
// trace anywhere, only a bare false return.
|
||||
_logger.LogError(ex, "Archive.CompressToStream failed — fileCount={FileCount}", files.Count);
|
||||
ExitOK = false;
|
||||
}
|
||||
return ExitOK;
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DocumentationFile>Fuchs_DataService.xml</DocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<!-- NU1608: transitive AngleSharp.Css/AngleSharp conflict inherited from OCORE_web -->
|
||||
<NoWarn>1591;NU1608</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DocumentationFile>Fuchs_DataService.xml</DocumentationFile>
|
||||
<NoWarn>1591</NoWarn>
|
||||
<!-- NU1608: transitive AngleSharp.Css/AngleSharp conflict inherited from OCORE_web -->
|
||||
<NoWarn>1591;NU1608</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="appsettings.json">
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
<name>Fuchs_DataService</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:fds.FdsSqlOptions">
|
||||
<summary>
|
||||
Logs every SQL error both to the worker's structured <see cref="T:Microsoft.Extensions.Logging.ILogger"/> (so it reaches
|
||||
whatever sink/OTel export is configured) and to <see cref="M:fds.FdsDebug.DebugLog(System.String,Microsoft.Data.SqlClient.SqlConnection,System.Exception,System.String,System.Object)"/> (SQL/local-file
|
||||
debug trail). Passing <paramref name="logger"/> is optional for call sites that don't have one
|
||||
in scope, but every caller that does have one in scope should pass it — otherwise a failing
|
||||
stored procedure only ever shows up in the debug trail, never in the worker's own logs.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:fds.FdsMfr.InvoiceFileDownloadConcurrency">
|
||||
<summary>Max parallel invoice-file downloads (independent per file).</summary>
|
||||
</member>
|
||||
|
||||
Reference in New Issue
Block a user