From 04910346617ec3172b0f33d44f773bcb90833cfb Mon Sep 17 00:00:00 2001 From: JesusMendoza Date: Tue, 28 Oct 2025 21:03:55 -0600 Subject: [PATCH 1/4] logging added --- Auth/AuthService.cs | 3 ++- Auth/IAuthService.cs | 5 ++++- Download/DownloadService.cs | 4 +++- Download/IDownloadService.cs | 4 +++- FileStorage/FileStorageService.cs | 17 ++++++++++++----- FileStorage/IFileStorageService.cs | 21 ++++++++++++++++----- IXmlDownloaderService.cs | 1 - Query/IQueryService.cs | 4 +++- Query/QueryService.cs | 4 +++- Verify/IVerifyService.cs | 4 +++- Verify/VerifyService.cs | 4 +++- XmlDownloaderService.cs | 28 ++++++++++++++++------------ 12 files changed, 68 insertions(+), 31 deletions(-) diff --git a/Auth/AuthService.cs b/Auth/AuthService.cs index cb5f654..db72cba 100644 --- a/Auth/AuthService.cs +++ b/Auth/AuthService.cs @@ -19,6 +19,7 @@ using Fiscalapi.XmlDownloader.Auth.Models; using Fiscalapi.XmlDownloader.Common; using Fiscalapi.XmlDownloader.Common.Http; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Auth; @@ -31,7 +32,7 @@ public class AuthService : SatService, IAuthService /// Authenticates with SAT using the provided credential and returns the authentication token /// public async Task AuthenticateAsync(ICredential credential, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { // Generate Sat XML security token ID var uuid = CreateSecurityToken(); diff --git a/Auth/IAuthService.cs b/Auth/IAuthService.cs index cb7346a..ede7c17 100644 --- a/Auth/IAuthService.cs +++ b/Auth/IAuthService.cs @@ -16,6 +16,7 @@ using Fiscalapi.Credentials.Core; using Fiscalapi.XmlDownloader.Auth.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Auth; @@ -29,6 +30,8 @@ public interface IAuthService /// /// /// CancellationToken + /// Logger /// - Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default); + Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default, + ILogger? logger = null); } \ No newline at end of file diff --git a/Download/DownloadService.cs b/Download/DownloadService.cs index ddc16bf..baf086d 100644 --- a/Download/DownloadService.cs +++ b/Download/DownloadService.cs @@ -20,6 +20,7 @@ using Fiscalapi.XmlDownloader.Common; using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Download.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Download; @@ -35,9 +36,10 @@ public class DownloadService : SatService, IDownloadService /// Authentication token /// PackageID /// CancellationToken + /// Logger /// DownloadResponse public async Task DownloadAsync(ICredential credential, Token authToken, string packageId, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { var toDigest = CreateDigest(packageId, credential.Certificate.Rfc); var signature = CreateSignature(credential, toDigest); diff --git a/Download/IDownloadService.cs b/Download/IDownloadService.cs index bade900..c93501c 100644 --- a/Download/IDownloadService.cs +++ b/Download/IDownloadService.cs @@ -17,6 +17,7 @@ using Fiscalapi.Credentials.Core; using Fiscalapi.XmlDownloader.Auth.Models; using Fiscalapi.XmlDownloader.Download.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Download; @@ -32,7 +33,8 @@ public interface IDownloadService /// Authentication token /// PackageID /// CancellationToken + /// Logger /// DownloadResponse Task DownloadAsync(ICredential credential, Token authToken, string packageId, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default, ILogger? logger = null); } \ No newline at end of file diff --git a/FileStorage/FileStorageService.cs b/FileStorage/FileStorageService.cs index 0be799f..9231df5 100644 --- a/FileStorage/FileStorageService.cs +++ b/FileStorage/FileStorageService.cs @@ -1,4 +1,5 @@ using System.IO.Compression; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.FileStorage { @@ -72,8 +73,9 @@ public bool FileExists(string fullFilePath) /// Full path to the ZIP file /// Path to extract files to /// Cancellation token + /// Logger public void ExtractZipFile(string fullFilePath, string? extractToPath, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { if (string.IsNullOrWhiteSpace(fullFilePath)) throw new ArgumentException("ZIP file path cannot be null or empty", nameof(fullFilePath)); @@ -141,8 +143,9 @@ public List GetFiles(string directoryPath, string? fileExtension = /// Full path to the file /// Binary data to write /// Cancellation token + /// Logger public async Task WriteFileAsync(string fullFilePath, byte[] data, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { var directory = Path.GetDirectoryName(fullFilePath); if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) @@ -159,8 +162,9 @@ public async Task WriteFileAsync(string fullFilePath, byte[] data, /// Full path to the file /// Base64 encoded data to write /// Cancellation token + /// Logger public async Task WriteFileAsync(string fullFilePath, string base64Data, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { var data = Convert.FromBase64String(base64Data); await WriteFileAsync(fullFilePath, data, cancellationToken); @@ -171,8 +175,10 @@ public async Task WriteFileAsync(string fullFilePath, string base64Data, /// /// Full path to the file /// Cancellation token + /// Logger /// File content as byte array - public async Task ReadFileAsync(string fullFilePath, CancellationToken cancellationToken = default) + public async Task ReadFileAsync(string fullFilePath, CancellationToken cancellationToken = default, + ILogger? logger = null) { return await File.ReadAllBytesAsync(fullFilePath, cancellationToken); } @@ -182,9 +188,10 @@ public async Task ReadFileAsync(string fullFilePath, CancellationToken c /// /// Full path to the file /// Cancellation token + /// Logger /// File content as string public async Task ReadFileContentAsync(string fullFilePath, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default, ILogger? logger = null) { return await File.ReadAllTextAsync(fullFilePath, cancellationToken); } diff --git a/FileStorage/IFileStorageService.cs b/FileStorage/IFileStorageService.cs index ec417a4..ded2f0e 100644 --- a/FileStorage/IFileStorageService.cs +++ b/FileStorage/IFileStorageService.cs @@ -14,6 +14,8 @@ * ============================================================================ */ +using Microsoft.Extensions.Logging; + namespace Fiscalapi.XmlDownloader.FileStorage; /// @@ -54,9 +56,10 @@ public interface IFileStorageService /// Full path to the ZIP file /// Path to extract files to /// Cancellation token + /// Logger /// Completion Task void ExtractZipFile(string fullFilePath, string extractToPath, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default, ILogger? logger = null); /// /// Get a list of files from the specified directory with optional file extension filter @@ -72,7 +75,9 @@ void ExtractZipFile(string fullFilePath, string extractToPath, /// Full path to the file /// Binary data to write /// Cancellation token - Task WriteFileAsync(string fullFilePath, byte[] data, CancellationToken cancellationToken = default); + /// Logger + Task WriteFileAsync(string fullFilePath, byte[] data, CancellationToken cancellationToken = default, + ILogger? logger = null); /// /// Write base64 encoded data to a file @@ -80,23 +85,29 @@ void ExtractZipFile(string fullFilePath, string extractToPath, /// Full path to the file /// Base64 encoded data to write /// Cancellation token - Task WriteFileAsync(string fullFilePath, string base64Data, CancellationToken cancellationToken = default); + /// Logger + Task WriteFileAsync(string fullFilePath, string base64Data, CancellationToken cancellationToken = default, + ILogger? logger = null); /// /// Read file content as byte array /// /// Full path to the file /// Cancellation token + /// Logger /// File content as byte array - Task ReadFileAsync(string fullFilePath, CancellationToken cancellationToken = default); + Task ReadFileAsync(string fullFilePath, CancellationToken cancellationToken = default, + ILogger? logger = null); /// /// Read file content as string /// /// Full path to the file /// Cancellation token + /// Logger /// File content as string - Task ReadFileContentAsync(string fullFilePath, CancellationToken cancellationToken = default); + Task ReadFileContentAsync(string fullFilePath, CancellationToken cancellationToken = default, + ILogger? logger = null); /// /// Delete a file diff --git a/IXmlDownloaderService.cs b/IXmlDownloaderService.cs index 5acaec6..1fba4fc 100644 --- a/IXmlDownloaderService.cs +++ b/IXmlDownloaderService.cs @@ -20,7 +20,6 @@ using Fiscalapi.XmlDownloader.Download.Models; using Fiscalapi.XmlDownloader.Query.Models; using Fiscalapi.XmlDownloader.Verify.Models; -using System.Runtime.CompilerServices; namespace Fiscalapi.XmlDownloader; diff --git a/Query/IQueryService.cs b/Query/IQueryService.cs index be4a8fb..2f602ac 100644 --- a/Query/IQueryService.cs +++ b/Query/IQueryService.cs @@ -17,6 +17,7 @@ using Fiscalapi.Credentials.Core; using Fiscalapi.XmlDownloader.Auth.Models; using Fiscalapi.XmlDownloader.Query.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Query; @@ -32,7 +33,8 @@ public interface IQueryService /// Authentication token /// Request parameters /// CancellationToken + /// Logger /// QueryResponse Task CreateAsync(ICredential credential, Token authToken, - QueryParameters parameters, CancellationToken cancellationToken); + QueryParameters parameters, CancellationToken cancellationToken, ILogger? logger = null); } \ No newline at end of file diff --git a/Query/QueryService.cs b/Query/QueryService.cs index c02721d..2d8f3ef 100644 --- a/Query/QueryService.cs +++ b/Query/QueryService.cs @@ -20,6 +20,7 @@ using Fiscalapi.XmlDownloader.Common; using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Query.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Query; @@ -35,9 +36,10 @@ public class QueryService : SatService, IQueryService /// Authentication token /// Request parameters /// CancellationToken + /// Logger /// QueryResponse public async Task CreateAsync(ICredential credential, Token authToken, - QueryParameters parameters, CancellationToken cancellationToken = default) + QueryParameters parameters, CancellationToken cancellationToken = default, ILogger? logger = null) { var operationType = DetermineOperationType(parameters); var toDigest = CreateDigest(parameters, operationType); diff --git a/Verify/IVerifyService.cs b/Verify/IVerifyService.cs index d0c952c..dc2e9b9 100644 --- a/Verify/IVerifyService.cs +++ b/Verify/IVerifyService.cs @@ -17,6 +17,7 @@ using Fiscalapi.Credentials.Core; using Fiscalapi.XmlDownloader.Auth.Models; using Fiscalapi.XmlDownloader.Verify.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Verify; @@ -32,7 +33,8 @@ public interface IVerifyService /// Authentication token /// Request ID /// CancellationToken + /// Logger /// VerifyResponse Task VerifyAsync(ICredential credential, Token authToken, string requestId, - CancellationToken cancellationToken = default); + CancellationToken cancellationToken = default, ILogger? logger = null); } \ No newline at end of file diff --git a/Verify/VerifyService.cs b/Verify/VerifyService.cs index ed8ccbb..6a2e6e2 100644 --- a/Verify/VerifyService.cs +++ b/Verify/VerifyService.cs @@ -20,6 +20,7 @@ using Fiscalapi.XmlDownloader.Common; using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Verify.Models; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Verify; @@ -35,9 +36,10 @@ public class VerifyService : SatService, IVerifyService /// Authentication token /// Request ID /// CancellationToken + /// Logger /// VerifyResponse public async Task VerifyAsync(ICredential credential, Token authToken, - string requestId, CancellationToken cancellationToken = default) + string requestId, CancellationToken cancellationToken = default, ILogger? logger = null) { var toDigest = CreateDigest(requestId, credential.Certificate.Rfc); var signature = CreateSignature(credential, toDigest); diff --git a/XmlDownloaderService.cs b/XmlDownloaderService.cs index d3e44d9..733e394 100644 --- a/XmlDownloaderService.cs +++ b/XmlDownloaderService.cs @@ -54,7 +54,6 @@ public class XmlDownloaderService : IXmlDownloaderService /// Verify service /// Download service /// File storage service - /// Logger public XmlDownloaderService( IAuthService authService, IQueryService queryService, @@ -121,7 +120,7 @@ public async Task AuthenticateAsync(string base64Cer, string base6 public async Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default) { - var authResponse = await _authService.AuthenticateAsync(credential, cancellationToken); + var authResponse = await _authService.AuthenticateAsync(credential, cancellationToken, _logger); Token = authResponse.Token; Credential = credential; @@ -143,7 +142,8 @@ public async Task CreateRequestAsync(QueryParameters parameters, credential: Credential!, authToken: Token!, parameters: parameters, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + logger: _logger ); } @@ -161,7 +161,8 @@ public Task VerifyAsync(string requestId, CancellationToken canc credential: Credential!, authToken: Token!, requestId: requestId, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + logger: _logger ); } @@ -179,7 +180,8 @@ public Task DownloadAsync(string packageId, CancellationToken credential: Credential!, authToken: Token!, packageId: packageId, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + logger: _logger ); } @@ -193,7 +195,7 @@ public Task DownloadAsync(string packageId, CancellationToken public async Task WritePackageAsync(string fullFilePath, byte[] bytes, CancellationToken cancellationToken = default) { - await _storageService.WriteFileAsync(fullFilePath, bytes, cancellationToken); + await _storageService.WriteFileAsync(fullFilePath, bytes, cancellationToken, _logger); } /// @@ -207,7 +209,7 @@ public async Task WritePackageAsync(string fullFilePath, byte[] bytes, public async Task WritePackageAsync(string fullFilePath, string base64Package, CancellationToken cancellationToken = default) { - await _storageService.WriteFileAsync(fullFilePath, base64Package, cancellationToken); + await _storageService.WriteFileAsync(fullFilePath, base64Package, cancellationToken, _logger); } /// @@ -220,7 +222,7 @@ public async Task ReadFileAsync(string fullFilePath, CancellationToken c { cancellationToken.ThrowIfCancellationRequested(); - return await _storageService.ReadFileAsync(fullFilePath, cancellationToken); + return await _storageService.ReadFileAsync(fullFilePath, cancellationToken, _logger); } /// @@ -237,7 +239,8 @@ public async IAsyncEnumerable GetComprobantesAsync(string fullFileP _storageService.ExtractZipFile( fullFilePath: fullFilePath, extractToPath: extractToPath, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + logger: _logger ); //Load file details @@ -248,7 +251,7 @@ public async IAsyncEnumerable GetComprobantesAsync(string fullFileP { cancellationToken.ThrowIfCancellationRequested(); - var xml = await _storageService.ReadFileContentAsync(file.FilePath, cancellationToken); + var xml = await _storageService.ReadFileContentAsync(file.FilePath, cancellationToken, _logger); var comprobante = XmlSerializerService.Deserialize(xml); if (comprobante is null) continue; @@ -337,7 +340,8 @@ public async IAsyncEnumerable GetMetadataAsync(string fullFilePath, st _storageService.ExtractZipFile( fullFilePath: fullFilePath, extractToPath: extractToPath, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + logger: _logger ); // Load file details - look for .txt files instead of XML @@ -348,7 +352,7 @@ public async IAsyncEnumerable GetMetadataAsync(string fullFilePath, st { cancellationToken.ThrowIfCancellationRequested(); - var txtContent = await _storageService.ReadFileContentAsync(file.FilePath, cancellationToken); + var txtContent = await _storageService.ReadFileContentAsync(file.FilePath, cancellationToken, _logger); var lines = txtContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); // Skip the header line (first line contains column names) From 2888f51be36fded683d20b9ba70d8fc8b19b56b3 Mon Sep 17 00:00:00 2001 From: JesusMendoza Date: Tue, 28 Oct 2025 22:06:33 -0600 Subject: [PATCH 2/4] logging impprovements added --- Auth/AuthResponseService.cs | 3 +- Auth/AuthService.cs | 23 ++++++++- Auth/IAuthService.cs | 3 +- Common/Http/SatService.cs | 28 ++++++---- Download/DownloadResponseService.cs | 3 +- Download/DownloadService.cs | 23 ++++++++- Download/IDownloadService.cs | 2 +- DownloaderExtensions.cs | 12 +++-- Query/IQueryService.cs | 2 +- Query/QueryResponseService.cs | 3 +- Query/QueryService.cs | 23 ++++++++- Verify/IVerifyService.cs | 2 +- Verify/VerifyResponseService.cs | 3 +- Verify/VerifyService.cs | 23 ++++++++- XmlDownloaderService.cs | 80 +++++++++++++++++++++++------ 15 files changed, 183 insertions(+), 50 deletions(-) diff --git a/Auth/AuthResponseService.cs b/Auth/AuthResponseService.cs index cf3089d..5fe97c8 100644 --- a/Auth/AuthResponseService.cs +++ b/Auth/AuthResponseService.cs @@ -20,12 +20,13 @@ using Fiscalapi.XmlDownloader.Common; using Fiscalapi.XmlDownloader.Common.Enums; using Fiscalapi.XmlDownloader.Common.Http; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Auth; public static class AuthResponseService { - public static AuthResponse Build(SatResponse satResponse, ICredential credential) + public static AuthResponse Build(SatResponse satResponse, ICredential credential, ILogger logger) { /* * diff --git a/Auth/AuthService.cs b/Auth/AuthService.cs index db72cba..38e4046 100644 --- a/Auth/AuthService.cs +++ b/Auth/AuthService.cs @@ -28,11 +28,30 @@ namespace Fiscalapi.XmlDownloader.Auth; /// public class AuthService : SatService, IAuthService { + /// + /// Constructor for dependency injection scenarios + /// + /// HttpClient instance + /// Logger instance + public AuthService(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + /// Constructor for direct instantiation scenarios + /// + /// Logger instance + public AuthService(ILogger logger) + : base(logger) + { + } + /// /// Authenticates with SAT using the provided credential and returns the authentication token /// public async Task AuthenticateAsync(ICredential credential, - CancellationToken cancellationToken = default, ILogger? logger = null) + ILogger logger, CancellationToken cancellationToken = default) { // Generate Sat XML security token ID var uuid = CreateSecurityToken(); @@ -53,7 +72,7 @@ public async Task AuthenticateAsync(ICredential credential, cancellationToken: cancellationToken); // Map response - var authResponse = AuthResponseService.Build(satResponse, credential); + var authResponse = AuthResponseService.Build(satResponse, credential, logger); return authResponse; } diff --git a/Auth/IAuthService.cs b/Auth/IAuthService.cs index ede7c17..e285083 100644 --- a/Auth/IAuthService.cs +++ b/Auth/IAuthService.cs @@ -32,6 +32,5 @@ public interface IAuthService /// CancellationToken /// Logger /// - Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default, - ILogger? logger = null); + Task AuthenticateAsync(ICredential credential, ILogger logger, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Common/Http/SatService.cs b/Common/Http/SatService.cs index 1f5c073..ede9c8f 100644 --- a/Common/Http/SatService.cs +++ b/Common/Http/SatService.cs @@ -16,6 +16,7 @@ using System.Net; using System.Text; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Common.Http; @@ -26,6 +27,7 @@ public abstract class SatService : IDisposable { private readonly HttpClient _httpClient; private readonly bool _ownsHttpClient; + private readonly ILogger _logger; private bool _disposed; protected bool IsDebugEnabled { get; set; } @@ -33,18 +35,20 @@ public abstract class SatService : IDisposable /// /// Constructor for dependency injection scenarios /// - protected SatService(HttpClient httpClient) + protected SatService(HttpClient httpClient, ILogger logger) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _ownsHttpClient = false; } /// /// Constructor for direct instantiation scenarios /// - protected SatService() + protected SatService(ILogger logger) { _httpClient = new HttpClient(); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _ownsHttpClient = true; } @@ -100,6 +104,8 @@ protected async Task SendRequestAsync(string url, string action, st { if (IsDebugEnabled) LogError(ex, url, action); + + _logger.LogError(ex, "Error sending SOAP request to {Url} with action {Action}", url, action); return new SatResponse { @@ -118,7 +124,7 @@ protected async Task SendRequestAsync(string url, string action, st /// Request URL /// SOAP action header /// Request payload - private static void LogRequest(HttpRequestMessage request, string url, string soapAction, string payload) + private void LogRequest(HttpRequestMessage request, string url, string soapAction, string payload) { try { @@ -150,11 +156,11 @@ private static void LogRequest(HttpRequestMessage request, string url, string so sb.AppendLine(payload); sb.AppendLine("=== END REQUEST ==="); - Console.WriteLine(sb.ToString()); + _logger.LogDebug(sb.ToString()); } catch (Exception ex) { - Console.WriteLine($"Error logging request: {ex.Message}"); + _logger.LogError(ex, "Error logging request"); } } @@ -165,7 +171,7 @@ private static void LogRequest(HttpRequestMessage request, string url, string so /// Response content /// Original request URL /// Original SOAP action - private static void LogResponse(HttpResponseMessage response, string responseContent, string url, + private void LogResponse(HttpResponseMessage response, string responseContent, string url, string soapAction) { try @@ -200,11 +206,11 @@ private static void LogResponse(HttpResponseMessage response, string responseCon sb.AppendLine(string.IsNullOrEmpty(responseContent) ? "[Empty Response]" : responseContent); sb.AppendLine("=== END RESPONSE ==="); - Console.WriteLine(sb.ToString()); + _logger.LogDebug(sb.ToString()); } catch (Exception ex) { - Console.WriteLine($"Error logging response: {ex.Message}"); + _logger.LogError(ex, "Error logging response"); } } @@ -214,7 +220,7 @@ private static void LogResponse(HttpResponseMessage response, string responseCon /// Exception that occurred /// Request URL /// SOAP action - private static void LogError(Exception ex, string url, string soapAction) + private void LogError(Exception ex, string url, string soapAction) { try { @@ -235,11 +241,11 @@ private static void LogError(Exception ex, string url, string soapAction) sb.AppendLine(ex.StackTrace); sb.AppendLine("=== END ERROR ==="); - Console.WriteLine(sb.ToString()); + _logger.LogError(sb.ToString()); } catch (Exception logEx) { - Console.WriteLine($"Error logging exception: {logEx.Message}"); + _logger.LogError(logEx, "Error logging exception"); } } diff --git a/Download/DownloadResponseService.cs b/Download/DownloadResponseService.cs index 46f5d71..a097707 100644 --- a/Download/DownloadResponseService.cs +++ b/Download/DownloadResponseService.cs @@ -19,12 +19,13 @@ using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Download.Models; using Fiscalapi.XmlDownloader.Download.Models.Sat; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Download; public static class DownloadResponseService { - public static DownloadResponse Build(SatResponse satResponse) + public static DownloadResponse Build(SatResponse satResponse, ILogger logger) { if (satResponse.IsSuccessStatusCode) { diff --git a/Download/DownloadService.cs b/Download/DownloadService.cs index baf086d..f27878c 100644 --- a/Download/DownloadService.cs +++ b/Download/DownloadService.cs @@ -29,6 +29,25 @@ namespace Fiscalapi.XmlDownloader.Download; /// public class DownloadService : SatService, IDownloadService { + /// + /// Constructor for dependency injection scenarios + /// + /// HttpClient instance + /// Logger instance + public DownloadService(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + /// Constructor for direct instantiation scenarios + /// + /// Logger instance + public DownloadService(ILogger logger) + : base(logger) + { + } + /// /// Downloads a package from SAT using the provided credential, authentication token and package ID.s /// @@ -39,7 +58,7 @@ public class DownloadService : SatService, IDownloadService /// Logger /// DownloadResponse public async Task DownloadAsync(ICredential credential, Token authToken, string packageId, - CancellationToken cancellationToken = default, ILogger? logger = null) + ILogger logger, CancellationToken cancellationToken = default) { var toDigest = CreateDigest(packageId, credential.Certificate.Rfc); var signature = CreateSignature(credential, toDigest); @@ -52,7 +71,7 @@ public async Task DownloadAsync(ICredential credential, Token token: authToken.Value, cancellationToken: cancellationToken); - var downloadResponse = DownloadResponseService.Build(satResponse); + var downloadResponse = DownloadResponseService.Build(satResponse, logger); return downloadResponse; } diff --git a/Download/IDownloadService.cs b/Download/IDownloadService.cs index c93501c..7fd5d12 100644 --- a/Download/IDownloadService.cs +++ b/Download/IDownloadService.cs @@ -36,5 +36,5 @@ public interface IDownloadService /// Logger /// DownloadResponse Task DownloadAsync(ICredential credential, Token authToken, string packageId, - CancellationToken cancellationToken = default, ILogger? logger = null); + ILogger logger, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/DownloaderExtensions.cs b/DownloaderExtensions.cs index e9be0da..a8d2048 100644 --- a/DownloaderExtensions.cs +++ b/DownloaderExtensions.cs @@ -170,11 +170,13 @@ public static TEnum ToEnumElement(this string code) where TEnum : struct, /// The service collection for chaining public static IServiceCollection AddXmlDownloader(this IServiceCollection services) { - // Register the services - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + // Register HttpClient factory for SAT services + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + services.AddHttpClient(); + + // Register non-HTTP services as scoped services.AddScoped(); services.AddScoped(); diff --git a/Query/IQueryService.cs b/Query/IQueryService.cs index 2f602ac..309161d 100644 --- a/Query/IQueryService.cs +++ b/Query/IQueryService.cs @@ -36,5 +36,5 @@ public interface IQueryService /// Logger /// QueryResponse Task CreateAsync(ICredential credential, Token authToken, - QueryParameters parameters, CancellationToken cancellationToken, ILogger? logger = null); + QueryParameters parameters, ILogger logger, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Query/QueryResponseService.cs b/Query/QueryResponseService.cs index 08f65be..60780f8 100644 --- a/Query/QueryResponseService.cs +++ b/Query/QueryResponseService.cs @@ -19,12 +19,13 @@ using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Query.Models; using Fiscalapi.XmlDownloader.Query.Models.Sat; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Query; public static class QueryResponseService { - public static QueryResponse Build(SatResponse satResponse) + public static QueryResponse Build(SatResponse satResponse, ILogger logger) { if (satResponse.IsSuccessStatusCode) { diff --git a/Query/QueryService.cs b/Query/QueryService.cs index 2d8f3ef..0668189 100644 --- a/Query/QueryService.cs +++ b/Query/QueryService.cs @@ -29,6 +29,25 @@ namespace Fiscalapi.XmlDownloader.Query; /// public class QueryService : SatService, IQueryService { + /// + /// Constructor for dependency injection scenarios + /// + /// HttpClient instance + /// Logger instance + public QueryService(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + /// Constructor for direct instantiation scenarios + /// + /// Logger instance + public QueryService(ILogger logger) + : base(logger) + { + } + /// /// Creates a download request based on the provided parameters. /// @@ -39,7 +58,7 @@ public class QueryService : SatService, IQueryService /// Logger /// QueryResponse public async Task CreateAsync(ICredential credential, Token authToken, - QueryParameters parameters, CancellationToken cancellationToken = default, ILogger? logger = null) + QueryParameters parameters, ILogger logger, CancellationToken cancellationToken = default) { var operationType = DetermineOperationType(parameters); var toDigest = CreateDigest(parameters, operationType); @@ -53,7 +72,7 @@ public async Task CreateAsync(ICredential credential, Token authT token: authToken.Value, cancellationToken: cancellationToken); - var requestResponse = QueryResponseService.Build(satResponse); + var requestResponse = QueryResponseService.Build(satResponse, logger); return requestResponse; } diff --git a/Verify/IVerifyService.cs b/Verify/IVerifyService.cs index dc2e9b9..125ea1b 100644 --- a/Verify/IVerifyService.cs +++ b/Verify/IVerifyService.cs @@ -36,5 +36,5 @@ public interface IVerifyService /// Logger /// VerifyResponse Task VerifyAsync(ICredential credential, Token authToken, string requestId, - CancellationToken cancellationToken = default, ILogger? logger = null); + ILogger logger, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Verify/VerifyResponseService.cs b/Verify/VerifyResponseService.cs index bddbb65..7412857 100644 --- a/Verify/VerifyResponseService.cs +++ b/Verify/VerifyResponseService.cs @@ -19,6 +19,7 @@ using Fiscalapi.XmlDownloader.Common.Http; using Fiscalapi.XmlDownloader.Verify.Models; using Fiscalapi.XmlDownloader.Verify.Models.Sat; +using Microsoft.Extensions.Logging; namespace Fiscalapi.XmlDownloader.Verify; @@ -27,7 +28,7 @@ public static class VerifyResponseService /// /// Builds a VerifyResponse from the SAT response. /// - public static VerifyResponse Build(SatResponse satResponse) + public static VerifyResponse Build(SatResponse satResponse, ILogger logger) { if (satResponse.IsSuccessStatusCode) { diff --git a/Verify/VerifyService.cs b/Verify/VerifyService.cs index 6a2e6e2..b019efa 100644 --- a/Verify/VerifyService.cs +++ b/Verify/VerifyService.cs @@ -29,6 +29,25 @@ namespace Fiscalapi.XmlDownloader.Verify; /// public class VerifyService : SatService, IVerifyService { + /// + /// Constructor for dependency injection scenarios + /// + /// HttpClient instance + /// Logger instance + public VerifyService(HttpClient httpClient, ILogger logger) + : base(httpClient, logger) + { + } + + /// + /// Constructor for direct instantiation scenarios + /// + /// Logger instance + public VerifyService(ILogger logger) + : base(logger) + { + } + /// /// Verifies a download request with SAT using the provided credential, authentication token, and request ID. /// @@ -39,7 +58,7 @@ public class VerifyService : SatService, IVerifyService /// Logger /// VerifyResponse public async Task VerifyAsync(ICredential credential, Token authToken, - string requestId, CancellationToken cancellationToken = default, ILogger? logger = null) + string requestId, ILogger logger, CancellationToken cancellationToken = default) { var toDigest = CreateDigest(requestId, credential.Certificate.Rfc); var signature = CreateSignature(credential, toDigest); @@ -52,7 +71,7 @@ public async Task VerifyAsync(ICredential credential, Token auth token: authToken.Value, cancellationToken: cancellationToken); - var verifyResponse = VerifyResponseService.Build(satResponse); + var verifyResponse = VerifyResponseService.Build(satResponse, logger); return verifyResponse; } diff --git a/XmlDownloaderService.cs b/XmlDownloaderService.cs index 733e394..d9fe7c4 100644 --- a/XmlDownloaderService.cs +++ b/XmlDownloaderService.cs @@ -33,7 +33,7 @@ namespace Fiscalapi.XmlDownloader; -public class XmlDownloaderService : IXmlDownloaderService +public class XmlDownloaderService : IXmlDownloaderService, IDisposable { public bool IsDebugEnabled { get; set; } public Token? Token { get; set; } @@ -45,6 +45,9 @@ public class XmlDownloaderService : IXmlDownloaderService private readonly IVerifyService _verifyService; private readonly IDownloadService _downloadService; private readonly IFileStorageService _storageService; + + private readonly bool _ownsServices; + private bool _disposed; /// /// Default constructor for dependency injection @@ -54,6 +57,7 @@ public class XmlDownloaderService : IXmlDownloaderService /// Verify service /// Download service /// File storage service + /// Logger instance public XmlDownloaderService( IAuthService authService, IQueryService queryService, @@ -69,6 +73,7 @@ ILogger logger _downloadService = downloadService; _storageService = storageService; _logger = logger; + _ownsServices = false; } /// @@ -76,18 +81,23 @@ ILogger logger /// public XmlDownloaderService() { - _authService = new AuthService(); - _queryService = new QueryService(); - _verifyService = new VerifyService(); - _downloadService = new DownloadService(); - _storageService = new FileStorageService(); - + // Create logger factory for non-DI scenario var loggerFactory = LoggerFactory.Create(builder => { builder.AddConsole().SetMinimumLevel(LogLevel.Information); }); - + + // Create logger for this service _logger = loggerFactory.CreateLogger(); + + // Create services with their own specific loggers in non-DI scenario + _authService = new AuthService(loggerFactory.CreateLogger()); + _queryService = new QueryService(loggerFactory.CreateLogger()); + _verifyService = new VerifyService(loggerFactory.CreateLogger()); + _downloadService = new DownloadService(loggerFactory.CreateLogger()); + _storageService = new FileStorageService(); + + _ownsServices = true; } /// @@ -120,7 +130,10 @@ public async Task AuthenticateAsync(string base64Cer, string base6 public async Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default) { - var authResponse = await _authService.AuthenticateAsync(credential, cancellationToken, _logger); + var authResponse = await _authService.AuthenticateAsync( + credential: credential, + logger: _logger, + cancellationToken: cancellationToken); Token = authResponse.Token; Credential = credential; @@ -142,8 +155,8 @@ public async Task CreateRequestAsync(QueryParameters parameters, credential: Credential!, authToken: Token!, parameters: parameters, - cancellationToken: cancellationToken, - logger: _logger + logger: _logger, + cancellationToken: cancellationToken ); } @@ -161,8 +174,8 @@ public Task VerifyAsync(string requestId, CancellationToken canc credential: Credential!, authToken: Token!, requestId: requestId, - cancellationToken: cancellationToken, - logger: _logger + logger: _logger, + cancellationToken: cancellationToken ); } @@ -180,8 +193,8 @@ public Task DownloadAsync(string packageId, CancellationToken credential: Credential!, authToken: Token!, packageId: packageId, - cancellationToken: cancellationToken, - logger: _logger + logger: _logger, + cancellationToken: cancellationToken ); } @@ -239,8 +252,8 @@ public async IAsyncEnumerable GetComprobantesAsync(string fullFileP _storageService.ExtractZipFile( fullFilePath: fullFilePath, extractToPath: extractToPath, - cancellationToken: cancellationToken, - logger: _logger + logger: _logger, + cancellationToken: cancellationToken ); //Load file details @@ -465,4 +478,37 @@ private void EnsureAuthToken() throw new InvalidOperationException("Credential is required. Please authenticate first."); } } + + /// + /// Disposes the service, releasing resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the service, releasing resources. + /// + /// Whether the call is from Dispose or finalizer + protected virtual void Dispose(bool disposing) + { + if (!_disposed && disposing && _ownsServices) + { + // Dispose services in non-DI scenarios where we own them + if (_authService is IDisposable authDisposable) + authDisposable.Dispose(); + if (_queryService is IDisposable queryDisposable) + queryDisposable.Dispose(); + if (_verifyService is IDisposable verifyDisposable) + verifyDisposable.Dispose(); + if (_downloadService is IDisposable downloadDisposable) + downloadDisposable.Dispose(); + if (_storageService is IDisposable storageDisposable) + storageDisposable.Dispose(); + } + + _disposed = true; + } } \ No newline at end of file From 10fb922147fc721349c3e22410815721c304d28f Mon Sep 17 00:00:00 2001 From: JesusMendoza Date: Tue, 28 Oct 2025 22:16:21 -0600 Subject: [PATCH 3/4] more logs added --- Auth/AuthResponseService.cs | 40 ++++++++++++++++++- Auth/AuthService.cs | 77 +++++++++++++++++++++++++++---------- XmlDownloaderService.cs | 37 ++++++++++++++++++ 3 files changed, 131 insertions(+), 23 deletions(-) diff --git a/Auth/AuthResponseService.cs b/Auth/AuthResponseService.cs index 5fe97c8..9b1bccf 100644 --- a/Auth/AuthResponseService.cs +++ b/Auth/AuthResponseService.cs @@ -57,24 +57,54 @@ public static AuthResponse Build(SatResponse satResponse, ICredential credential if (satResponse.IsSuccessStatusCode) { var envelope = XmlSerializerService.Deserialize(satResponse.RawResponse!); - return new AuthResponse + var tokenValue = envelope?.Body?.AutenticaResponse?.AutenticaResult; + + var authResponse = new AuthResponse { Succeeded = true, SatStatus = SatStatus.RequestSucceeded, SatStatusCode = SatStatus.RequestSucceeded.ToEnumCode(), SatMessage = "", - TokenValue = envelope?.Body?.AutenticaResponse?.AutenticaResult, + TokenValue = tokenValue, ValidFrom = envelope?.Header?.Security?.Timestamp?.Created, ValidTo = envelope?.Header?.Security?.Timestamp?.Expires, RawRequest = satResponse.RawRequest, RawResponse = satResponse.RawResponse, Tin = credential.Certificate.Rfc }; + + // Critical logging: Verify token was received + if (string.IsNullOrWhiteSpace(tokenValue)) + { + logger.LogWarning( + "Authentication succeeded but token is missing. RFC: {Rfc}, HasEnvelope: {HasEnvelope}, HasBody: {HasBody}, HasAutenticaResponse: {HasAutenticaResponse}", + credential.Certificate.Rfc, + envelope != null, + envelope?.Body != null, + envelope?.Body?.AutenticaResponse != null); + } + else + { + logger.LogInformation( + "Authentication succeeded. RFC: {Rfc}, TokenLength: {TokenLength}, ValidFrom: {ValidFrom}, ValidTo: {ValidTo}", + credential.Certificate.Rfc, + tokenValue.Length, + authResponse.ValidFrom, + authResponse.ValidTo); + } + + return authResponse; } if (satResponse.RawResponse is not null && satResponse.RawResponse.ToLowerInvariant().Contains("fault")) { var faultEnvelope = XmlSerializerService.Deserialize(satResponse.RawResponse); + logger.LogError( + "Authentication failed with SOAP fault. RFC: {Rfc}, FaultCode: {FaultCode}, FaultMessage: {FaultMessage}", + credential.Certificate.Rfc, + faultEnvelope?.Body?.Fault?.FaultCode, + faultEnvelope?.Body?.Fault?.FaultMessage); + return new AuthResponse { Succeeded = false, @@ -86,6 +116,12 @@ public static AuthResponse Build(SatResponse satResponse, ICredential credential }; } + logger.LogError( + "Authentication failed with unexpected status. RFC: {Rfc}, HttpStatusCode: {StatusCode}, ReasonPhrase: {ReasonPhrase}", + credential.Certificate.Rfc, + satResponse.HttpStatusCode, + satResponse.ReasonPhrase); + return new AuthResponse { Succeeded = false, diff --git a/Auth/AuthService.cs b/Auth/AuthService.cs index 38e4046..5f287ba 100644 --- a/Auth/AuthService.cs +++ b/Auth/AuthService.cs @@ -53,28 +53,63 @@ public AuthService(ILogger logger) public async Task AuthenticateAsync(ICredential credential, ILogger logger, CancellationToken cancellationToken = default) { - // Generate Sat XML security token ID - var uuid = CreateSecurityToken(); + logger.LogInformation("Starting SAT authentication process for RFC: {Rfc}", credential.Certificate.Rfc); - // Create digest and signature using unified template - var digest = CreateDigest(credential); - var signature = CreateSignature(digest, credential, uuid); - - // Build SOAP envelope - var authXml = BuildEnvelope(digest, uuid, credential.Certificate.RawDataBytes.ToBase64String(), - signature); - - // Send request - var satResponse = await SendRequestAsync( - url: SatUrl.AuthUrl, - action: SatUrl.AuthAction, - payload: authXml, - cancellationToken: cancellationToken); - - // Map response - var authResponse = AuthResponseService.Build(satResponse, credential, logger); - - return authResponse; + try + { + // Generate Sat XML security token ID + var uuid = CreateSecurityToken(); + logger.LogDebug("Generated security token UUID: {Uuid}", uuid); + + // Create digest and signature using unified template + var digest = CreateDigest(credential); + var signature = CreateSignature(digest, credential, uuid); + + // Build SOAP envelope + var authXml = BuildEnvelope(digest, uuid, credential.Certificate.RawDataBytes.ToBase64String(), + signature); + + // Send request + logger.LogInformation("Sending authentication request to SAT. URL: {Url}", SatUrl.AuthUrl); + var satResponse = await SendRequestAsync( + url: SatUrl.AuthUrl, + action: SatUrl.AuthAction, + payload: authXml, + cancellationToken: cancellationToken); + + logger.LogInformation("SAT authentication response received. Success: {IsSuccessStatusCode}, Status: {StatusCode}", + satResponse.IsSuccessStatusCode, + satResponse.HttpStatusCode); + + // Log complete XML response for debugging + if (!satResponse.IsSuccessStatusCode || string.IsNullOrWhiteSpace(satResponse.RawResponse)) + { + logger.LogError( + "SAT authentication failed. RFC: {Rfc}, StatusCode: {StatusCode}, ReasonPhrase: {ReasonPhrase}, RawResponse: {RawResponse}", + credential.Certificate.Rfc, + satResponse.HttpStatusCode, + satResponse.ReasonPhrase, + satResponse.RawResponse ?? "[Empty Response]"); + } + else + { + logger.LogDebug( + "SAT authentication raw response XML. RFC: {Rfc}, ResponseLength: {Length}, RawResponse: {RawResponse}", + credential.Certificate.Rfc, + satResponse.RawResponse?.Length ?? 0, + satResponse.RawResponse); + } + + // Map response + var authResponse = AuthResponseService.Build(satResponse, credential, logger); + + return authResponse; + } + catch (Exception ex) + { + logger.LogError(ex, "Error during SAT authentication process for RFC: {Rfc}", credential.Certificate.Rfc); + throw; + } } /// diff --git a/XmlDownloaderService.cs b/XmlDownloaderService.cs index d9fe7c4..8fa735c 100644 --- a/XmlDownloaderService.cs +++ b/XmlDownloaderService.cs @@ -130,6 +130,8 @@ public async Task AuthenticateAsync(string base64Cer, string base6 public async Task AuthenticateAsync(ICredential credential, CancellationToken cancellationToken = default) { + _logger.LogInformation("Starting authentication for RFC: {Rfc}", credential.Certificate.Rfc); + var authResponse = await _authService.AuthenticateAsync( credential: credential, logger: _logger, @@ -137,6 +139,27 @@ public async Task AuthenticateAsync(ICredential credential, Token = authResponse.Token; Credential = credential; + + // Critical logging: Authentication result + if (!authResponse.Succeeded) + { + _logger.LogError("Authentication failed. RFC: {Rfc}, Status: {StatusCode}, Message: {Message}", + credential.Certificate.Rfc, + authResponse.SatStatusCode, + authResponse.SatMessage); + } + else if (Token == null) + { + _logger.LogError( + "Authentication succeeded but token is null. RFC: {Rfc}, TokenValue is null or empty: {IsNullOrEmpty}", + credential.Certificate.Rfc, + string.IsNullOrWhiteSpace(authResponse.TokenValue)); + } + else + { + _logger.LogInformation("Authentication succeeded. RFC: {Rfc}, Token is valid", credential.Certificate.Rfc); + } + return authResponse; } @@ -149,8 +172,18 @@ public async Task AuthenticateAsync(ICredential credential, public async Task CreateRequestAsync(QueryParameters parameters, CancellationToken cancellationToken = default) { + // Critical logging: Check token before proceeding + if (Token == null || Credential == null) + { + _logger.LogError("Cannot create download request: Token or Credential is null. Token is null: {TokenIsNull}, Credential is null: {CredentialIsNull}", + Token == null, + Credential == null); + } + EnsureAuthToken(); + _logger.LogInformation("Creating download request for RFC: {Rfc}", Credential!.Certificate.Rfc); + return await _queryService.CreateAsync( credential: Credential!, authToken: Token!, @@ -470,11 +503,15 @@ private void EnsureAuthToken() { if (Token == null) { + _logger.LogError("EnsureAuthToken failed: Token is null. Credential is null: {CredentialIsNull}", + Credential == null); throw new InvalidOperationException("Authentication token is required. Please authenticate first."); } if (Credential == null) { + _logger.LogError("EnsureAuthToken failed: Credential is null. Token is null: {TokenIsNull}", + Token == null); throw new InvalidOperationException("Credential is required. Please authenticate first."); } } From 5a1045e097698e7e8f2b3438816d2269a518352e Mon Sep 17 00:00:00 2001 From: JesusMendoza Date: Tue, 28 Oct 2025 22:41:06 -0600 Subject: [PATCH 4/4] Version 5.0.8 --- XmlDownloader.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XmlDownloader.csproj b/XmlDownloader.csproj index b3dcc9d..e59a008 100644 --- a/XmlDownloader.csproj +++ b/XmlDownloader.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 5.0.7 + 5.0.8 $(Version) $(Version) $(Version)