From fc31843bc2d688f45dfdc2f2f93bc3faba7765b0 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 15 Mar 2020 17:04:26 +0100 Subject: [PATCH 1/9] Initial implementation for ILogger Added Microsoft.Extensions.Logging.Abstractions package Implemented basic logging in RequestHandler Set up a constants file with all the event IDs --- .../Objects/Core/LoggingEvents.cs | 34 ++++++++++++++ src/PVOutput.Net/PVOutput.Net.csproj | 1 + src/PVOutput.Net/PVOutputClient.cs | 32 ++++++++----- .../Requests/Handler/RequestHandler.cs | 46 +++++++++++++++++-- tests/PVOutput.Net.Tests/Utils/TestUtility.cs | 4 +- 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 src/PVOutput.Net/Objects/Core/LoggingEvents.cs diff --git a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs new file mode 100644 index 0000000..1c2134f --- /dev/null +++ b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace PVOutput.Net.Objects.Core +{ + internal class LoggingEvents + { + /* + * RequestHandler base events + */ + public const int ExecuteRequest = 10001; + public const int ReceivedResponseContent = 10002; + public const int RequestStatusSuccesful = 10003; + public const int RequestStatusFailed = 10004; + + + /* + * Module specific event IDs + */ + public const int ExtendedService_ = 20101; + public const int FavouriteService_ = 20201; + public const int InsolationService_ = 20301; + public const int MissingService_ = 20401; + public const int OutputService_ = 20501; + public const int SearchService_ = 20601; + public const int StatisticsService_ = 20701; + public const int StatusService_ = 20801; + public const int SupplyService_ = 20901; + public const int SystemService_ = 21001; + public const int TeamService_ = 21101; + + } +} diff --git a/src/PVOutput.Net/PVOutput.Net.csproj b/src/PVOutput.Net/PVOutput.Net.csproj index d288d0c..fb78247 100644 --- a/src/PVOutput.Net/PVOutput.Net.csproj +++ b/src/PVOutput.Net/PVOutput.Net.csproj @@ -43,6 +43,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/PVOutput.Net/PVOutputClient.cs b/src/PVOutput.Net/PVOutputClient.cs index f055eb2..a5cfc42 100644 --- a/src/PVOutput.Net/PVOutputClient.cs +++ b/src/PVOutput.Net/PVOutputClient.cs @@ -1,4 +1,6 @@ -using PVOutput.Net.Modules; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using PVOutput.Net.Modules; using PVOutput.Net.Requests; using PVOutput.Net.Requests.Handler; @@ -12,6 +14,8 @@ public sealed class PVOutputClient internal const string PVOutputBaseUri = @"https://pvoutput.org/service/r2/"; internal IHttpClientProvider HttpClientProvider { get; } + internal ILogger Logger { get; } + /// ApiKey to use with authenticating. public string ApiKey { get; set; } @@ -92,15 +96,27 @@ public sealed class PVOutputClient /// /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. - public PVOutputClient(string apiKey, int ownedSystemId) : this(new HttpClientProvider()) + public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSystemId, null) + { + + } + + /// + /// Creates a new PVOutputClient, with a ILogger attached. + /// + /// ApiKey to use with authenticating. + /// Id of the currently owned system used for authenticating. + /// The ILogger implementation, used for logging purposes. + public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) : this(apiKey, ownedSystemId, new HttpClientProvider(), logger) { - ApiKey = apiKey; - OwnedSystemId = ownedSystemId; } - internal PVOutputClient(IHttpClientProvider httpClientProvider) + internal PVOutputClient(string apiKey, int ownedSystemId, IHttpClientProvider httpClientProvider, ILogger logger) { + ApiKey = apiKey; + OwnedSystemId = ownedSystemId; HttpClientProvider = httpClientProvider ?? new HttpClientProvider(); + Logger = logger ?? NullLogger.Instance; Output = new OutputService(this); System = new SystemService(this); @@ -114,11 +130,5 @@ internal PVOutputClient(IHttpClientProvider httpClientProvider) Supply = new SupplyService(this); Search = new SearchService(this); } - - internal PVOutputClient(string apiKey, int ownedSystemId, IHttpClientProvider httpClientProvider) : this(httpClientProvider) - { - ApiKey = apiKey; - OwnedSystemId = ownedSystemId; - } } } diff --git a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs index dc4a379..c0b6428 100644 --- a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs +++ b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using PVOutput.Net.Objects; using PVOutput.Net.Objects.Core; using PVOutput.Net.Objects.Factories; @@ -19,6 +20,8 @@ internal class RequestHandler { private readonly PVOutputClient _client; + private ILogger Logger => _client.Logger; + public RequestHandler(PVOutputClient client) { _client = client; @@ -44,7 +47,7 @@ internal async Task> ExecuteSingleItemReq return result; } - Objects.Core.IObjectStringReader reader = StringFactoryContainer.CreateObjectReader(); + IObjectStringReader reader = StringFactoryContainer.CreateObjectReader(); TResponseContentType content = await reader.ReadObjectAsync(responseStream, cancellationToken).ConfigureAwait(false); result.IsSuccess = true; @@ -132,10 +135,12 @@ private bool ResponseIsErrorResponse(HttpResponseMessage responseMessage, Stream return false; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, Stream responseStream) { if (response.IsSuccessStatusCode) { + Logger.LogInformation(LoggingEvents.RequestStatusSuccesful, "Request successful - Status {StatusCode}", response.StatusCode); return null; } @@ -160,6 +165,8 @@ private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, S } } + Logger.LogError(LoggingEvents.RequestStatusFailed, "Request failed - Status {StatusCode} - Content - {Message} ", error.StatusCode, error.Message); + if (_client.ThrowResponseExceptions) { throw new PVOutputException(error.StatusCode, error.Message); @@ -211,9 +218,40 @@ private static PVOutputApiRateInformation GetApiRateInformationfromResponse(Http return result; } - private static Task GetResponseContentStreamAsync(HttpResponseMessage response) + private async Task GetResponseContentStreamAsync(HttpResponseMessage response) { - return response.Content != null ? response.Content.ReadAsStreamAsync() : Task.FromResult(default(Stream)); + if (response.Content == null) + { + return default; + } + + if (Logger.IsEnabled(LogLevel.Debug)) + { + return await LogResponseContentStreamAsync(response).ConfigureAwait(false); + } + + return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + } + + private async Task LogResponseContentStreamAsync(HttpResponseMessage response) + { + var cloneStream = new MemoryStream(); + + var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + stream.CopyTo(cloneStream); + stream.Seek(0, SeekOrigin.Begin); + + using (var stringReader = new StreamReader(cloneStream)) + { + string completeContent = stringReader.ReadToEnd(); + + if (completeContent.Length > 0) + { + Logger.LogDebug(LoggingEvents.ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", stringReader.ReadToEnd()); + } + } + + return stream; } private static HttpRequestMessage CreateRequestMessage(IRequest request) @@ -234,9 +272,11 @@ private static string CreateUrl(IRequest request) return $"{PVOutputClient.PVOutputBaseUri}{apiUri}"; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Logging")] internal Task ExecuteRequestAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { SetRequestHeaders(requestMessage); + Logger.LogDebug(LoggingEvents.ExecuteRequest, "Executing request - {RequestUri}", requestMessage.RequestUri); return _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken); } diff --git a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs index 9e632fa..e4b6a18 100644 --- a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs +++ b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs @@ -38,7 +38,7 @@ public static PVOutputClient GetMockClient(string uri, string mockResponseConten var provider = new TestHttpClientProvider(); provider.When(uri, mockResponseContent); provider.MockHttpMessageHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider); + return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, null); } public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandler) @@ -46,7 +46,7 @@ public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandle var provider = new TestHttpClientProvider(); mockHandler = provider.MockHttpMessageHandler; mockHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider); + return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, null); } } } From 762ed060011d764411dc90427fc4355d7f07b395 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 15 Mar 2020 20:38:42 +0100 Subject: [PATCH 2/9] Fixed logging the response content --- src/PVOutput.Net/Requests/Handler/RequestHandler.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs index c0b6428..245eb79 100644 --- a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs +++ b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs @@ -238,16 +238,17 @@ private async Task LogResponseContentStreamAsync(HttpResponseMessage res var cloneStream = new MemoryStream(); var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - stream.CopyTo(cloneStream); + await stream.CopyToAsync(cloneStream).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); + cloneStream.Seek(0, SeekOrigin.Begin); - using (var stringReader = new StreamReader(cloneStream)) + using (TextReader textReader = new StreamReader(cloneStream)) { - string completeContent = stringReader.ReadToEnd(); + string completeContent = textReader.ReadToEnd(); if (completeContent.Length > 0) { - Logger.LogDebug(LoggingEvents.ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", stringReader.ReadToEnd()); + Logger.LogDebug(LoggingEvents.ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", completeContent); } } From 85e2b857f5b07c95d6095b8d6473f96f5b52d26f Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 17 Mar 2020 00:20:13 +0100 Subject: [PATCH 3/9] Added scoped logging to all requests - Added a TestOutputLogger for logging the NUnit output to console. This way it's readable in the additional test output. - TestOutputLogger is now used in all unit test clients - Changed line endings to LF via .editorconfig --- .editorconfig | 1 + src/PVOutput.Net/Modules/ExtendedService.cs | 18 ++- src/PVOutput.Net/Modules/FavouriteService.cs | 9 +- src/PVOutput.Net/Modules/InsolationService.cs | 28 +++- src/PVOutput.Net/Modules/MissingService.cs | 11 +- src/PVOutput.Net/Modules/OutputService.cs | 64 ++++++++- src/PVOutput.Net/Modules/SearchService.cs | 9 +- src/PVOutput.Net/Modules/StatisticsService.cs | 24 +++- src/PVOutput.Net/Modules/StatusService.cs | 53 +++++++- src/PVOutput.Net/Modules/SupplyService.cs | 10 +- src/PVOutput.Net/Modules/SystemService.cs | 20 ++- src/PVOutput.Net/Modules/TeamService.cs | 25 +++- .../Objects/Core/LoggingEvents.cs | 83 +++++++++--- .../Requests/Handler/RequestHandler.cs | 122 ++++++++++-------- .../Utils/TestOutputLogger.cs | 53 ++++++++ tests/PVOutput.Net.Tests/Utils/TestUtility.cs | 4 +- 16 files changed, 428 insertions(+), 106 deletions(-) create mode 100644 tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs diff --git a/.editorconfig b/.editorconfig index bea9175..3c2395d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,7 @@ indent_style = space indent_size = 4 insert_final_newline = true charset = utf-8-bom +end_of_line = lf # XML project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] diff --git a/src/PVOutput.Net/Modules/ExtendedService.cs b/src/PVOutput.Net/Modules/ExtendedService.cs index 8e92580..a212f8f 100644 --- a/src/PVOutput.Net/Modules/ExtendedService.cs +++ b/src/PVOutput.Net/Modules/ExtendedService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Dawn; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -30,8 +31,13 @@ internal ExtendedService(PVOutputClient client) : base(client) /// Most recent extended data. public Task> GetRecentExtendedDataAsync(CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.ExtendedService_GetRecentExtendedData + }; + var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new ExtendedRequest(), cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new ExtendedRequest(), loggingScope, cancellationToken); } /// @@ -45,11 +51,19 @@ public Task> GetRecentExtendedDataAsync(Cancellation /// List of extended data objects. public Task> GetExtendedDataForPeriodAsync(DateTime fromDate, DateTime toDate, int? limit = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.ExtendedService_GetExtendedDataForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate, + [LoggingEvents.Parameter_Limit] = limit + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate); Guard.Argument(limit, nameof(limit)).LessThan(50); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new ExtendedRequest() { FromDate = fromDate, ToDate = toDate, Limit = limit }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new ExtendedRequest() { FromDate = fromDate, ToDate = toDate, Limit = limit }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/FavouriteService.cs b/src/PVOutput.Net/Modules/FavouriteService.cs index 31634bd..5ce1877 100644 --- a/src/PVOutput.Net/Modules/FavouriteService.cs +++ b/src/PVOutput.Net/Modules/FavouriteService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -28,8 +29,14 @@ internal FavouriteService(PVOutputClient client) : base(client) /// List of favourites. public Task> GetFavouritesAsync(int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.FavouriteService_GetFavourites, + [LoggingEvents.Parameter_SystemId] = systemId + }; + var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new FavouriteRequest() { SystemId = systemId }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new FavouriteRequest() { SystemId = systemId }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/InsolationService.cs b/src/PVOutput.Net/Modules/InsolationService.cs index 05b1cb9..ea0a827 100644 --- a/src/PVOutput.Net/Modules/InsolationService.cs +++ b/src/PVOutput.Net/Modules/InsolationService.cs @@ -3,7 +3,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -30,8 +32,14 @@ internal InsolationService(PVOutputClient client) : base(client) /// Insolation data for the owned system. public Task> GetInsolationForOwnSystemAsync(DateTime? date = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.InsolationService_GetInsolationForOwnSystem, + [LoggingEvents.Parameter_Date] = date + }; + var handler = new RequestHandler(Client); - var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { Date = date }, cancellationToken); + var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { Date = date }, loggingScope, cancellationToken); if (date.HasValue) { @@ -50,8 +58,15 @@ public Task> GetInsolationForOwnSystemAsync(D /// Insolation data for the requested system. public Task> GetInsolationForSystemAsync(int systemId, DateTime? date = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.InsolationService_GetInsolationForSystem, + [LoggingEvents.Parameter_SystemId] = systemId, + [LoggingEvents.Parameter_Date] = date + }; + var handler = new RequestHandler(Client); - var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { SystemId = systemId, Date = date }, cancellationToken); + var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { SystemId = systemId, Date = date }, loggingScope, cancellationToken); if (date.HasValue) { @@ -70,8 +85,15 @@ public Task> GetInsolationForSystemAsync(int /// Insolation data for the requested location. public Task> GetInsolationForLocationAsync(PVCoordinate coordinate, DateTime? date = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.InsolationService_GetInsolationForLocation, + [LoggingEvents.Parameter_Coordinate] = coordinate, + [LoggingEvents.Parameter_Date] = date + }; + var handler = new RequestHandler(Client); - var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { Coordinate = coordinate, Date = date }, cancellationToken); + var response = handler.ExecuteArrayRequestAsync(new InsolationRequest { Coordinate = coordinate, Date = date }, loggingScope, cancellationToken); if (date.HasValue) { diff --git a/src/PVOutput.Net/Modules/MissingService.cs b/src/PVOutput.Net/Modules/MissingService.cs index bed652b..dc2b2d2 100644 --- a/src/PVOutput.Net/Modules/MissingService.cs +++ b/src/PVOutput.Net/Modules/MissingService.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Dawn; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -29,10 +31,17 @@ internal MissingService(PVOutputClient client) : base(client) /// List of missing dates public Task> GetMissingDaysInPeriodAsync(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.MissingService_GetMissingDaysInPeriod, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate); var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new MissingRequest { FromDate = fromDate, ToDate = toDate }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new MissingRequest { FromDate = fromDate, ToDate = toDate }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/OutputService.cs b/src/PVOutput.Net/Modules/OutputService.cs index 9bfa4fd..ec14892 100644 --- a/src/PVOutput.Net/Modules/OutputService.cs +++ b/src/PVOutput.Net/Modules/OutputService.cs @@ -32,10 +32,18 @@ internal OutputService(PVOutputClient client) : base(client) /// Output for the requested date. public Task> GetOutputForDateAsync(DateTime date, bool getInsolation = false, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_GetOutputForDate, + [LoggingEvents.Parameter_Date] = date, + [LoggingEvents.Parameter_GetInsolation] = getInsolation, + [LoggingEvents.Parameter_SystemId] = systemId + }; + Guard.Argument(date, nameof(date)).Max(DateTime.Today); var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new OutputRequest { FromDate = date, ToDate = date, SystemId = systemId, Insolation = getInsolation }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new OutputRequest { FromDate = date, ToDate = date, SystemId = systemId, Insolation = getInsolation }, loggingScope, cancellationToken); } /// @@ -49,11 +57,20 @@ public Task> GetOutputForDateAsync(DateTime date, bool /// Outputs for the requested period. public Task> GetOutputsForPeriodAsync(DateTime fromDate, DateTime toDate, bool getInsolation = false, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_GetOutputsForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate, + [LoggingEvents.Parameter_GetInsolation] = getInsolation, + [LoggingEvents.Parameter_SystemId] = systemId + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate).IsNoFutureDate().NoTimeComponent(); Guard.Argument(fromDate, nameof(fromDate)).NoTimeComponent(); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, SystemId = systemId, Insolation = getInsolation }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, SystemId = systemId, Insolation = getInsolation }, loggingScope, cancellationToken); } /// @@ -65,10 +82,17 @@ public Task> GetOutputsForPeriodAsync(DateTime fr /// Team output for the requested date. public Task> GetTeamOutputForDateAsync(DateTime date, int teamId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_GetTeamOutputForDate, + [LoggingEvents.Parameter_Date] = date, + [LoggingEvents.Parameter_TeamId] = teamId + }; + Guard.Argument(date, nameof(date)).Max(DateTime.Today); var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new OutputRequest { FromDate = date, ToDate = date, TeamId = teamId }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new OutputRequest { FromDate = date, ToDate = date, TeamId = teamId }, loggingScope, cancellationToken); } /// @@ -81,11 +105,19 @@ public Task> GetTeamOutputForDateAsync(DateTime da /// Team outputs Outputs for the requested period. public Task> GetTeamOutputsForPeriodAsync(DateTime fromDate, DateTime toDate, int teamId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_GetTeamOutputsForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate, + [LoggingEvents.Parameter_TeamId] = teamId + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate).IsNoFutureDate().NoTimeComponent(); Guard.Argument(fromDate, nameof(fromDate)).NoTimeComponent(); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, TeamId = teamId }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, TeamId = teamId }, loggingScope, cancellationToken); } /// @@ -98,11 +130,19 @@ public Task> GetTeamOutputsForPeriodAsync(Dat /// Aggregated outputs for the requested period. public Task> GetAggregatedOutputsAsync(DateTime fromDate, DateTime toDate, AggregationPeriod period, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_GetAggregatedOutputs, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate, + [LoggingEvents.Parameter_AggregationPeriod] = period + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate).IsNoFutureDate().NoTimeComponent(); Guard.Argument(fromDate, nameof(fromDate)).NoTimeComponent(); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, Aggregation = period }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new OutputRequest { FromDate = fromDate, ToDate = toDate, Aggregation = period }, loggingScope, cancellationToken); } /// @@ -114,10 +154,15 @@ public Task> GetAggregatedOutputsAsync( /// If the operation succeeded. public Task AddOutputAsync(IOutputPost output, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_AddOutput + }; + Guard.Argument(output, nameof(output)).NotNull(); var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new AddOutputRequest() { Output = output }, cancellationToken); + return handler.ExecutePostRequestAsync(new AddOutputRequest() { Output = output }, loggingScope, cancellationToken); } /// @@ -129,10 +174,15 @@ public Task AddOutputAsync(IOutputPost output, Cancellati /// If the operation succeeded. public Task AddBatchOutputAsync(IEnumerable outputs, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.OutputService_AddBatchOutput + }; + Guard.Argument(outputs, nameof(outputs)).NotNull().NotEmpty(); var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new AddBatchOutputRequest() { Outputs = outputs }, cancellationToken); + return handler.ExecutePostRequestAsync(new AddBatchOutputRequest() { Outputs = outputs }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/SearchService.cs b/src/PVOutput.Net/Modules/SearchService.cs index e1bea17..8c1c06c 100644 --- a/src/PVOutput.Net/Modules/SearchService.cs +++ b/src/PVOutput.Net/Modules/SearchService.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Dawn; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -32,10 +33,16 @@ internal SearchService(PVOutputClient client) : base(client) /// A list of search results. public Task> SearchAsync(string searchQuery, PVCoordinate? coordinate = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.SearchService_Search, + [LoggingEvents.Parameter_Coordinate] = coordinate + }; + Guard.Argument(searchQuery, nameof(searchQuery)).NotEmpty().NotNull(); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new SearchRequest { SearchQuery = searchQuery, Coordinate = coordinate }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new SearchRequest { SearchQuery = searchQuery, Coordinate = coordinate }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/StatisticsService.cs b/src/PVOutput.Net/Modules/StatisticsService.cs index e546d21..04dacc5 100644 --- a/src/PVOutput.Net/Modules/StatisticsService.cs +++ b/src/PVOutput.Net/Modules/StatisticsService.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Dawn; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -30,8 +32,16 @@ internal StatisticsService(PVOutputClient client) : base(client) /// Lifetime statistical information public Task> GetLifetimeStatisticsAsync(bool includeConsumptionAndImport = false, bool includeCreditDebit = false, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatisticsService_GetLifetimeStatistics, + [LoggingEvents.Parameter_IncludeConsumptionAndImport] = includeConsumptionAndImport, + [LoggingEvents.Parameter_IncludeCreditDebit] = includeCreditDebit, + [LoggingEvents.Parameter_SystemId] = systemId + }; + var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new StatisticRequest { SystemId = systemId, IncludeConsumptionImport = includeConsumptionAndImport, IncludeCreditDebit = includeCreditDebit }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new StatisticRequest { SystemId = systemId, IncludeConsumptionImport = includeConsumptionAndImport, IncludeCreditDebit = includeCreditDebit }, loggingScope, cancellationToken); } /// @@ -47,10 +57,20 @@ public Task> GetLifetimeStatisticsAsync(bool includ /// Statistical information for the requested period. public Task> GetStatisticsForPeriodAsync(DateTime fromDate, DateTime toDate, bool includeConsumptionAndImport = false, bool includeCreditDebit = false, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatisticsService_GetStatisticsForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDate, + [LoggingEvents.Parameter_ToDate] = toDate, + [LoggingEvents.Parameter_IncludeConsumptionAndImport] = includeConsumptionAndImport, + [LoggingEvents.Parameter_IncludeCreditDebit] = includeCreditDebit, + [LoggingEvents.Parameter_SystemId] = systemId + }; + Guard.Argument(toDate, nameof(toDate)).GreaterThan(fromDate); var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new StatisticPeriodRequest { FromDate = fromDate, ToDate = toDate, SystemId = systemId, IncludeConsumptionImport = includeConsumptionAndImport, IncludeCreditDebit = includeCreditDebit }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new StatisticPeriodRequest { FromDate = fromDate, ToDate = toDate, SystemId = systemId, IncludeConsumptionImport = includeConsumptionAndImport, IncludeCreditDebit = includeCreditDebit }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/StatusService.cs b/src/PVOutput.Net/Modules/StatusService.cs index 1f1da8b..9785df8 100644 --- a/src/PVOutput.Net/Modules/StatusService.cs +++ b/src/PVOutput.Net/Modules/StatusService.cs @@ -30,10 +30,17 @@ internal StatusService(PVOutputClient client) : base(client) /// Status at the specified moment. public Task> GetStatusForDateTimeAsync(DateTime moment, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_GetStatusForDateTime, + [LoggingEvents.Parameter_Moment] = moment, + [LoggingEvents.Parameter_SystemId] = systemId + }; + Guard.Argument(moment, nameof(moment)).IsNoFutureDate(); var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new GetStatusRequest { Date = moment, SystemId = systemId }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new GetStatusRequest { Date = moment, SystemId = systemId }, loggingScope, cancellationToken); } /// @@ -49,11 +56,22 @@ public Task> GetStatusForDateTimeAsync(DateTime moment /// Statusses for the specified period. public Task> GetHistoryForPeriodAsync(DateTime fromDateTime, DateTime toDateTime, bool ascending = false, int? systemId = null, bool extendedData = false, int? limit = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_GetHistoryForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDateTime, + [LoggingEvents.Parameter_ToDate] = toDateTime, + [LoggingEvents.Parameter_Ascending] = ascending, + [LoggingEvents.Parameter_SystemId] = systemId, + [LoggingEvents.Parameter_ExtendedData] = extendedData, + [LoggingEvents.Parameter_Limit] = limit + }; + Guard.Argument(toDateTime, nameof(toDateTime)).GreaterThan(fromDateTime).IsNoFutureDate(); var handler = new RequestHandler(Client); return handler.ExecuteArrayRequestAsync( - new GetStatusRequest { Date = fromDateTime.Date, From = fromDateTime, To = toDateTime, Ascending = ascending, SystemId = systemId, Extended = extendedData, Limit = limit, History = true }, cancellationToken); + new GetStatusRequest { Date = fromDateTime.Date, From = fromDateTime, To = toDateTime, Ascending = ascending, SystemId = systemId, Extended = extendedData, Limit = limit, History = true }, loggingScope, cancellationToken); } /// @@ -66,10 +84,18 @@ public Task> GetHistoryForPeriodAsync(Date /// Day statistics for the specified period. public Task> GetDayStatisticsForPeriodAsync(DateTime fromDateTime, DateTime toDateTime, int? systemId = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_GetDayStatisticsForPeriod, + [LoggingEvents.Parameter_FromDate] = fromDateTime, + [LoggingEvents.Parameter_ToDate] = toDateTime, + [LoggingEvents.Parameter_SystemId] = systemId + }; + Guard.Argument(toDateTime, nameof(toDateTime)).GreaterThan(fromDateTime).IsNoFutureDate(); var handler = new RequestHandler(Client); - var response = handler.ExecuteSingleItemRequestAsync(new GetDayStatisticsRequest { Date = fromDateTime.Date, From = fromDateTime, To = toDateTime, SystemId = systemId }, cancellationToken); + var response = handler.ExecuteSingleItemRequestAsync(new GetDayStatisticsRequest { Date = fromDateTime.Date, From = fromDateTime, To = toDateTime, SystemId = systemId }, loggingScope, cancellationToken); return response.ContinueWith(antecedent => AddRequestedDate(antecedent, fromDateTime.Date), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); } @@ -96,10 +122,15 @@ private static PVOutputResponse AddRequestedDate(TaskIf the operation succeeded. public Task AddStatusAsync(IStatusPost status, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_AddStatus + }; + Guard.Argument(status, nameof(status)).NotNull(); var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new AddStatusRequest() { StatusPost = status }, cancellationToken); + return handler.ExecutePostRequestAsync(new AddStatusRequest() { StatusPost = status }, loggingScope, cancellationToken); } /// @@ -111,10 +142,15 @@ public Task AddStatusAsync(IStatusPost status, Cancellati /// If the operation succeeded. public Task> AddBatchStatusAsync(IEnumerable statuses, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_AddBatchStatus + }; + Guard.Argument(statuses, nameof(statuses)).NotNull().NotEmpty(); var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new AddBatchStatusRequest() { StatusPosts = statuses }, cancellationToken); ; + return handler.ExecuteArrayRequestAsync(new AddBatchStatusRequest() { StatusPosts = statuses }, loggingScope, cancellationToken); ; } /// @@ -126,10 +162,15 @@ public Task> AddBatchStatusAsync(I /// If the operation succeeded. public Task DeleteStatusAsync(DateTime moment, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.StatusService_DeleteStatus + }; + Guard.Argument(moment, nameof(moment)).IsNoFutureDate(); var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new DeleteStatusRequest() { Timestamp = moment }, cancellationToken); + return handler.ExecutePostRequestAsync(new DeleteStatusRequest() { Timestamp = moment }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/SupplyService.cs b/src/PVOutput.Net/Modules/SupplyService.cs index fc565bb..eb2e894 100644 --- a/src/PVOutput.Net/Modules/SupplyService.cs +++ b/src/PVOutput.Net/Modules/SupplyService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -30,8 +31,15 @@ internal SupplyService(PVOutputClient client) : base(client) /// List of supply information public Task> GetSupplyAsync(string timeZone = null, string regionKey = null, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.SupplyService_GetSupply, + [LoggingEvents.Parameter_TimeZone] = timeZone, + [LoggingEvents.Parameter_RegionKey] = regionKey + }; + var handler = new RequestHandler(Client); - return handler.ExecuteArrayRequestAsync(new SupplyRequest { TimeZone = timeZone, RegionKey = regionKey }, cancellationToken); + return handler.ExecuteArrayRequestAsync(new SupplyRequest { TimeZone = timeZone, RegionKey = regionKey }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/SystemService.cs b/src/PVOutput.Net/Modules/SystemService.cs index b06497d..9f1567e 100644 --- a/src/PVOutput.Net/Modules/SystemService.cs +++ b/src/PVOutput.Net/Modules/SystemService.cs @@ -1,6 +1,8 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -24,8 +26,14 @@ internal SystemService(PVOutputClient client) : base(client) /// Information of the owned system. public Task> GetOwnSystemAsync(CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.SystemService_GetOwnSystem + + }; + var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new SystemRequest(), cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new SystemRequest(), loggingScope, cancellationToken); } /// @@ -37,8 +45,14 @@ public Task> GetOwnSystemAsync(CancellationToken cance /// Information of the requested system. public Task> GetOtherSystemAsync(int systemId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.SystemService_GetOtherSystem, + [LoggingEvents.Parameter_SystemId] = systemId + }; + var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new SystemRequest { SystemId = systemId, MonthlyEstimates = false }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new SystemRequest { SystemId = systemId, MonthlyEstimates = false }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Modules/TeamService.cs b/src/PVOutput.Net/Modules/TeamService.cs index 1aa00c8..7f4fdf7 100644 --- a/src/PVOutput.Net/Modules/TeamService.cs +++ b/src/PVOutput.Net/Modules/TeamService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using PVOutput.Net.Objects; +using PVOutput.Net.Objects.Core; using PVOutput.Net.Requests.Handler; using PVOutput.Net.Requests.Modules; using PVOutput.Net.Responses; @@ -29,8 +30,14 @@ internal TeamService(PVOutputClient client) : base(client) /// Team information. public Task> GetTeamAsync(int teamId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.TeamService_GetTeam, + [LoggingEvents.Parameter_TeamId] = teamId + }; + var handler = new RequestHandler(Client); - return handler.ExecuteSingleItemRequestAsync(new TeamRequest { TeamId = teamId }, cancellationToken); + return handler.ExecuteSingleItemRequestAsync(new TeamRequest { TeamId = teamId }, loggingScope, cancellationToken); } /// @@ -42,8 +49,14 @@ public Task> GetTeamAsync(int teamId, CancellationToken /// If the operation succeeded. public Task JoinTeamAsync(int teamId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.TeamService_JoinTeam, + [LoggingEvents.Parameter_TeamId] = teamId + }; + var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new JoinTeamRequest() { TeamId = teamId }, cancellationToken); + return handler.ExecutePostRequestAsync(new JoinTeamRequest() { TeamId = teamId }, loggingScope, cancellationToken); } /// @@ -55,8 +68,14 @@ public Task JoinTeamAsync(int teamId, CancellationToken c /// If the operation succeeded. public Task LeaveTeamAsync(int teamId, CancellationToken cancellationToken = default) { + var loggingScope = new Dictionary() + { + [LoggingEvents.RequestId] = LoggingEvents.TeamService_LeaveTeam, + [LoggingEvents.Parameter_TeamId] = teamId + }; + var handler = new RequestHandler(Client); - return handler.ExecutePostRequestAsync(new LeaveTeamRequest() { TeamId = teamId }, cancellationToken); + return handler.ExecutePostRequestAsync(new LeaveTeamRequest() { TeamId = teamId }, loggingScope, cancellationToken); } } } diff --git a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs index 1c2134f..9949574 100644 --- a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs +++ b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs @@ -7,28 +7,75 @@ namespace PVOutput.Net.Objects.Core internal class LoggingEvents { /* - * RequestHandler base events - */ - public const int ExecuteRequest = 10001; - public const int ReceivedResponseContent = 10002; - public const int RequestStatusSuccesful = 10003; - public const int RequestStatusFailed = 10004; + * Logging scope (parameter) keywords + */ + public const string RequestId = "RequestId"; + public const string Parameter_Date = "Date"; + public const string Parameter_FromDate = "FromDate"; + public const string Parameter_ToDate = "ToDate"; + public const string Parameter_Moment = "Moment"; + public const string Parameter_Limit = "Limit"; + public const string Parameter_SystemId = "SystemId"; + public const string Parameter_TeamId = "TeamId"; + public const string Parameter_Coordinate = "Coordinate"; + public const string Parameter_AggregationPeriod = "AggregationPeriod"; + public const string Parameter_ExtendedData = "ExtendedData"; + public const string Parameter_Ascending = "Ascending"; + public const string Parameter_IncludeConsumptionAndImport = "IncludeConsumptionAndImport"; + public const string Parameter_IncludeCreditDebit = "IncludeCreditDebit"; + public const string Parameter_GetInsolation = "GetInsolation"; + public const string Parameter_TimeZone = "TimeZone"; + public const string Parameter_RegionKey = "RegionKey"; + /* + * RequestHandler base events + */ + public const int Handler_ExecuteRequest = 10001; + public const int Handler_ReceivedResponseContent = 10002; + public const int Handler_RequestStatusSuccesful = 10003; + public const int Handler_RequestStatusFailed = 10004; /* - * Module specific event IDs + * Module specific event IDs per request */ - public const int ExtendedService_ = 20101; - public const int FavouriteService_ = 20201; - public const int InsolationService_ = 20301; - public const int MissingService_ = 20401; - public const int OutputService_ = 20501; - public const int SearchService_ = 20601; - public const int StatisticsService_ = 20701; - public const int StatusService_ = 20801; - public const int SupplyService_ = 20901; - public const int SystemService_ = 21001; - public const int TeamService_ = 21101; + public const int ExtendedService_GetRecentExtendedData = 20101; + public const int ExtendedService_GetExtendedDataForPeriod = 20102; + + public const int FavouriteService_GetFavourites = 20201; + + public const int InsolationService_GetInsolationForOwnSystem = 20301; + public const int InsolationService_GetInsolationForSystem = 20302; + public const int InsolationService_GetInsolationForLocation = 20303; + + public const int MissingService_GetMissingDaysInPeriod = 20401; + + public const int OutputService_GetOutputForDate = 20501; + public const int OutputService_GetOutputsForPeriod = 20502; + public const int OutputService_GetTeamOutputForDate = 20503; + public const int OutputService_GetTeamOutputsForPeriod = 20504; + public const int OutputService_GetAggregatedOutputs = 20505; + public const int OutputService_AddOutput = 20506; + public const int OutputService_AddBatchOutput = 20507; + + public const int SearchService_Search = 20601; + + public const int StatisticsService_GetLifetimeStatistics = 20701; + public const int StatisticsService_GetStatisticsForPeriod = 20702; + + public const int StatusService_GetStatusForDateTime = 20801; + public const int StatusService_GetHistoryForPeriod = 20802; + public const int StatusService_GetDayStatisticsForPeriod = 20803; + public const int StatusService_AddStatus = 20804; + public const int StatusService_AddBatchStatus = 20805; + public const int StatusService_DeleteStatus = 20806; + + public const int SupplyService_GetSupply = 20901; + + public const int SystemService_GetOwnSystem = 21001; + public const int SystemService_GetOtherSystem = 21002; + public const int TeamService_GetTeam = 21101; + public const int TeamService_JoinTeam = 21102; + public const int TeamService_LeaveTeam = 21103; } } diff --git a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs index 245eb79..1fbdb30 100644 --- a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs +++ b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -18,41 +19,44 @@ namespace PVOutput.Net.Requests.Handler { internal class RequestHandler { - private readonly PVOutputClient _client; + private PVOutputClient Client { get; } - private ILogger Logger => _client.Logger; + private ILogger Logger => Client.Logger; public RequestHandler(PVOutputClient client) { - _client = client; + Client = client; } - internal async Task> ExecuteSingleItemRequestAsync(IRequest request, CancellationToken cancellationToken) + internal async Task> ExecuteSingleItemRequestAsync(IRequest request, Dictionary loggingScope, CancellationToken cancellationToken) { HttpResponseMessage responseMessage = null; try { - using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + using (Logger.BeginScope(loggingScope)) { - responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); - } - Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + { + responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); + } + Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); - var result = new PVOutputResponse(); - result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); + var result = new PVOutputResponse(); + result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); - if (ResponseIsErrorResponse(responseMessage, responseStream, result)) - { - return result; - } + if (ResponseIsErrorResponse(responseMessage, responseStream, result)) + { + return result; + } - IObjectStringReader reader = StringFactoryContainer.CreateObjectReader(); - TResponseContentType content = await reader.ReadObjectAsync(responseStream, cancellationToken).ConfigureAwait(false); + IObjectStringReader reader = StringFactoryContainer.CreateObjectReader(); + TResponseContentType content = await reader.ReadObjectAsync(responseStream, cancellationToken).ConfigureAwait(false); - result.IsSuccess = true; - result.Value = content; - return result; + result.IsSuccess = true; + result.Value = content; + return result; + } } finally { @@ -60,32 +64,35 @@ internal async Task> ExecuteSingleItemReq } } - internal async Task> ExecuteArrayRequestAsync(IRequest request, CancellationToken cancellationToken) + internal async Task> ExecuteArrayRequestAsync(IRequest request, Dictionary loggingScope, CancellationToken cancellationToken) { HttpResponseMessage responseMessage = null; try { - using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + using (Logger.BeginScope(loggingScope)) { - responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); - } - Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + { + responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); + } + Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); - var result = new PVOutputArrayResponse(); - result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); + var result = new PVOutputArrayResponse(); + result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); - if (ResponseIsErrorResponse(responseMessage, responseStream, result)) - { - return result; - } + if (ResponseIsErrorResponse(responseMessage, responseStream, result)) + { + return result; + } - IArrayStringReader reader = StringFactoryContainer.CreateArrayReader(); - IEnumerable content = await reader.ReadArrayAsync(responseStream, cancellationToken).ConfigureAwait(false); + IArrayStringReader reader = StringFactoryContainer.CreateArrayReader(); + IEnumerable content = await reader.ReadArrayAsync(responseStream, cancellationToken).ConfigureAwait(false); - result.IsSuccess = true; - result.Values = content; - return result; + result.IsSuccess = true; + result.Values = content; + return result; + } } finally { @@ -93,29 +100,32 @@ internal async Task> ExecuteArrayReq } } - internal async Task ExecutePostRequestAsync(IRequest request, CancellationToken cancellationToken) + internal async Task ExecutePostRequestAsync(IRequest request, Dictionary loggingScope, CancellationToken cancellationToken) { HttpResponseMessage responseMessage = null; try { - using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + using (Logger.BeginScope(loggingScope)) { - responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); - } - Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + using (HttpRequestMessage requestMessage = CreateRequestMessage(request)) + { + responseMessage = await ExecuteRequestAsync(requestMessage, cancellationToken).ConfigureAwait(false); + } + Stream responseStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); - var result = new PVOutputBasicResponse(); - result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); + var result = new PVOutputBasicResponse(); + result.ApiRateInformation = GetApiRateInformationfromResponse(responseMessage); - if (ResponseIsErrorResponse(responseMessage, responseStream, result)) - { + if (ResponseIsErrorResponse(responseMessage, responseStream, result)) + { + return result; + } + + result.IsSuccess = true; + result.SuccesMessage = GetBasicResponseState(responseStream); return result; } - - result.IsSuccess = true; - result.SuccesMessage = GetBasicResponseState(responseStream); - return result; } finally { @@ -140,7 +150,7 @@ private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, S { if (response.IsSuccessStatusCode) { - Logger.LogInformation(LoggingEvents.RequestStatusSuccesful, "Request successful - Status {StatusCode}", response.StatusCode); + Logger.LogInformation(LoggingEvents.Handler_RequestStatusSuccesful, "Request successful - Status {StatusCode}", response.StatusCode); return null; } @@ -165,9 +175,9 @@ private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, S } } - Logger.LogError(LoggingEvents.RequestStatusFailed, "Request failed - Status {StatusCode} - Content - {Message} ", error.StatusCode, error.Message); + Logger.LogError(LoggingEvents.Handler_RequestStatusFailed, "Request failed - Status {StatusCode} - Content - {Message} ", error.StatusCode, error.Message); - if (_client.ThrowResponseExceptions) + if (Client.ThrowResponseExceptions) { throw new PVOutputException(error.StatusCode, error.Message); } @@ -248,7 +258,7 @@ private async Task LogResponseContentStreamAsync(HttpResponseMessage res if (completeContent.Length > 0) { - Logger.LogDebug(LoggingEvents.ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", completeContent); + Logger.LogDebug(LoggingEvents.Handler_ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", completeContent); } } @@ -277,14 +287,14 @@ private static string CreateUrl(IRequest request) internal Task ExecuteRequestAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { SetRequestHeaders(requestMessage); - Logger.LogDebug(LoggingEvents.ExecuteRequest, "Executing request - {RequestUri}", requestMessage.RequestUri); - return _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken); + Logger.LogDebug(LoggingEvents.Handler_ExecuteRequest, "Executing request - {RequestUri}", requestMessage.RequestUri); + return Client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken); } protected void SetRequestHeaders(HttpRequestMessage request) { - request.Headers.Add("X-Pvoutput-Apikey", _client.ApiKey); - request.Headers.Add("X-Pvoutput-SystemId", FormatHelper.GetValueAsString(_client.OwnedSystemId)); + request.Headers.Add("X-Pvoutput-Apikey", Client.ApiKey); + request.Headers.Add("X-Pvoutput-SystemId", FormatHelper.GetValueAsString(Client.OwnedSystemId)); request.Headers.Add("X-Rate-Limit", "1"); } } diff --git a/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs b/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs new file mode 100644 index 0000000..f0e058f --- /dev/null +++ b/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Logging; + +namespace PVOutput.Net.Tests.Utils +{ + internal class TestOutputLogger : ILogger, IDisposable + { + private readonly Stack _scopeStack = new Stack(); + + private void AppendLog(string line) => Console.WriteLine(line); + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + AppendLog(formatter(state, exception)); + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public IDisposable BeginScope(TState state) + { + string stackText = StateToString(state); + _scopeStack.Push(stackText); + AppendLog("BeginScope: " + stackText); + return this; + } + + private static string StateToString(TState state) + { + if (state is Dictionary dictionary) + { + var sb = new StringBuilder(); + foreach (KeyValuePair kvp in dictionary) + { + sb.Append('['); + sb.Append(kvp.Key); + sb.Append(';'); + sb.Append(kvp.Value == null ? "" : kvp.Value.ToString()); + sb.Append("],"); + } + return sb.ToString().TrimEnd(','); + } + return state.ToString(); + } + + public void Dispose() + { + var stackText = _scopeStack.Pop(); + AppendLog("EndScope: " + stackText); + } + } +} diff --git a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs index e4b6a18..22c5822 100644 --- a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs +++ b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs @@ -38,7 +38,7 @@ public static PVOutputClient GetMockClient(string uri, string mockResponseConten var provider = new TestHttpClientProvider(); provider.When(uri, mockResponseContent); provider.MockHttpMessageHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, null); + return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, new TestOutputLogger()); } public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandler) @@ -46,7 +46,7 @@ public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandle var provider = new TestHttpClientProvider(); mockHandler = provider.MockHttpMessageHandler; mockHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, null); + return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, new TestOutputLogger()); } } } From 062bd082ed96e06a6545d0c8ba9d6490deb702d8 Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 25 Mar 2020 00:01:50 +0100 Subject: [PATCH 4/9] Updated documation to include logging Set response content logging to LogLevel.Trace instead of debug. Formatted the logged text lines. Added logging for the API rate information. --- docfx/index.md | 20 +++++++++++++++++++ .../Requests/Handler/RequestHandler.cs | 16 ++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docfx/index.md b/docfx/index.md index 48c7471..1d4bb5c 100644 --- a/docfx/index.md +++ b/docfx/index.md @@ -58,6 +58,26 @@ var response = await client.Status.AddStatusAsync(status); } ``` +### How to log calls made by the library + +The client also supports logging through the standard .NET Core ILogger interface. +In a web app or hosted service you can supply the `ILogger` through dependency injection (DI). +For non-host console applications, use the `LoggerFactory` to instantiate the logger and then provide it to the relevant constructor overload. + +See the official [Logging in .NET Core and ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1) for more information. + +The various logging levels: + +- **Information**: this will only the success state of requests that are made with the used service and parameters; including errors. +- **Debug**: also log the exact requested uri (including query parameters), and api rate information. +- **Trace**: also log 'raw' response content, at a minimal overhead (duplicating memory streams). + +**NOTE**: + +Your API key and owned system ID are always sent through headers, not the request uri. None of the logging levels also log headers and never will, so logging information should never contain those two aspects. However it is always good to inspect logs before sharing them with anyone, including the maintainer of this project. + +### API reference + See the [API reference](api/PVOutput.Net.yml) for details on all the classes provided by this library. ## Contribute diff --git a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs index 1fbdb30..2e9b403 100644 --- a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs +++ b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs @@ -150,7 +150,7 @@ private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, S { if (response.IsSuccessStatusCode) { - Logger.LogInformation(LoggingEvents.Handler_RequestStatusSuccesful, "Request successful - Status {StatusCode}", response.StatusCode); + Logger.LogInformation(LoggingEvents.Handler_RequestStatusSuccesful, "[RequestSuccessful] Status: {StatusCode}", response.StatusCode); return null; } @@ -175,7 +175,7 @@ private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, S } } - Logger.LogError(LoggingEvents.Handler_RequestStatusFailed, "Request failed - Status {StatusCode} - Content - {Message} ", error.StatusCode, error.Message); + Logger.LogError(LoggingEvents.Handler_RequestStatusFailed, "[RequestFailed] Status: {StatusCode} Content: {Message} ", error.StatusCode, error.Message); if (Client.ThrowResponseExceptions) { @@ -205,8 +205,9 @@ private static string GetBasicResponseState(Stream responseStream) return null; } - - private static PVOutputApiRateInformation GetApiRateInformationfromResponse(HttpResponseMessage response) + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] + private PVOutputApiRateInformation GetApiRateInformationfromResponse(HttpResponseMessage response) { var result = new PVOutputApiRateInformation(); @@ -225,6 +226,7 @@ private static PVOutputApiRateInformation GetApiRateInformationfromResponse(Http result.LimitResetAt = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(response.Headers.GetValues("X-Rate-Limit-Reset").First(), CultureInfo.CreateSpecificCulture("en-US"))).DateTime; } + Logger.LogDebug("[API-rate] Remaining: {LimitRemaining} Limit: {CurrentLimit}: ResetAt: {LimitResetAt}", result.LimitRemaining, result.CurrentLimit, result.LimitResetAt); return result; } @@ -235,7 +237,7 @@ private async Task GetResponseContentStreamAsync(HttpResponseMessage res return default; } - if (Logger.IsEnabled(LogLevel.Debug)) + if (Logger.IsEnabled(LogLevel.Trace)) { return await LogResponseContentStreamAsync(response).ConfigureAwait(false); } @@ -258,7 +260,7 @@ private async Task LogResponseContentStreamAsync(HttpResponseMessage res if (completeContent.Length > 0) { - Logger.LogDebug(LoggingEvents.Handler_ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", completeContent); + Logger.LogTrace(LoggingEvents.Handler_ReceivedResponseContent, "Response content" + Environment.NewLine + "{content}", completeContent); } } @@ -287,7 +289,7 @@ private static string CreateUrl(IRequest request) internal Task ExecuteRequestAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { SetRequestHeaders(requestMessage); - Logger.LogDebug(LoggingEvents.Handler_ExecuteRequest, "Executing request - {RequestUri}", requestMessage.RequestUri); + Logger.LogTrace(LoggingEvents.Handler_ExecuteRequest, "[ExecuteRequest] Uri: {RequestUri}", requestMessage.RequestUri); return Client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken); } From ee992ffb63316620b0414a42ea46d0078b251016 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2020 21:23:53 +0100 Subject: [PATCH 5/9] Added codecov.yml configuration Default settings, except for the comment bot. It has a smaller comment (only header and diff). --- codecov.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..45a92d8 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,20 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70..100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header,diff" + behavior: default + require_changes: false From 240ce1ca1518d69e620bd5285e1826df099c97a7 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2020 22:16:22 +0100 Subject: [PATCH 6/9] Improved logged compatibility with ASP.Net Core DI New constructors Restructured client options to inject the options with DI too --- ...iceOptions.cs => PVOutputClientOptions.cs} | 2 +- .../PVOutputServiceExtensions.cs | 16 ++++++++--- src/PVOutput.Net/PVOutputClient.cs | 27 +++++++++++++++---- .../Requests/Handler/RequestHandler.cs | 6 ++--- 4 files changed, 38 insertions(+), 13 deletions(-) rename src/PVOutput.Net/DependencyInjection/{PVOutputServiceOptions.cs => PVOutputClientOptions.cs} (91%) diff --git a/src/PVOutput.Net/DependencyInjection/PVOutputServiceOptions.cs b/src/PVOutput.Net/DependencyInjection/PVOutputClientOptions.cs similarity index 91% rename from src/PVOutput.Net/DependencyInjection/PVOutputServiceOptions.cs rename to src/PVOutput.Net/DependencyInjection/PVOutputClientOptions.cs index 5823a2f..2cbbaec 100644 --- a/src/PVOutput.Net/DependencyInjection/PVOutputServiceOptions.cs +++ b/src/PVOutput.Net/DependencyInjection/PVOutputClientOptions.cs @@ -3,7 +3,7 @@ /// /// Options used to create a PVOutputClient through Microsoft's Dependency Injection. /// - public class PVOutputServiceOptions + public class PVOutputClientOptions { /// ApiKey to use with authenticating. public string ApiKey { get; set; } diff --git a/src/PVOutput.Net/DependencyInjection/PVOutputServiceExtensions.cs b/src/PVOutput.Net/DependencyInjection/PVOutputServiceExtensions.cs index d884c6f..b1b87e5 100644 --- a/src/PVOutput.Net/DependencyInjection/PVOutputServiceExtensions.cs +++ b/src/PVOutput.Net/DependencyInjection/PVOutputServiceExtensions.cs @@ -15,11 +15,19 @@ public static class PVOutputServiceExtensions /// /// The servicecollection to add the client to. /// An action to configure the provided options. - public static void AddPVOutputClient(this IServiceCollection services, Action optionsAction) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Exception messages are non translatable for now")] + public static void AddPVOutputClient(this IServiceCollection services, Action optionsAction) { - var options = new PVOutputServiceOptions(); - optionsAction?.Invoke(options); - services.AddSingleton(sp => new PVOutputClient(options.ApiKey, options.OwnedSystemId)); + if (optionsAction == null) + { + throw new ArgumentNullException(nameof(optionsAction), "Please provide options to the PVOutputClient."); + } + + var options = new PVOutputClientOptions(); + optionsAction.Invoke(options); + + services.AddSingleton(options); + services.AddSingleton(); } } } diff --git a/src/PVOutput.Net/PVOutputClient.cs b/src/PVOutput.Net/PVOutputClient.cs index a5cfc42..0180abb 100644 --- a/src/PVOutput.Net/PVOutputClient.cs +++ b/src/PVOutput.Net/PVOutputClient.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using PVOutput.Net.DependencyInjection; using PVOutput.Net.Modules; using PVOutput.Net.Requests; using PVOutput.Net.Requests.Handler; @@ -7,7 +8,7 @@ namespace PVOutput.Net { /// - /// The client used to talk to PVOutput with. + /// The client used to communicate with the PVOutput service. /// public sealed class PVOutputClient { @@ -88,7 +89,7 @@ public sealed class PVOutputClient /// /// The search service /// See the official API information. - /// . + /// public SearchService Search { get; } /// @@ -96,9 +97,8 @@ public sealed class PVOutputClient /// /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. - public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSystemId, null) + public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSystemId, null) { - } /// @@ -107,7 +107,24 @@ public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSyst /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. /// The ILogger implementation, used for logging purposes. - public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) : this(apiKey, ownedSystemId, new HttpClientProvider(), logger) + public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) : this(apiKey, ownedSystemId, new HttpClientProvider(), logger) + { + } + + /// + /// Creates a new PVOutputClient, with a ILogger attached. + /// + /// Options to use for the client, containing the ApiKey and Id of the owned system. + public PVOutputClient(PVOutputClientOptions options) : this(options?.ApiKey, options.OwnedSystemId, null) + { + } + + /// + /// Creates a new PVOutputClient, with a ILogger attached. + /// + /// Options to use for the client, containing the ApiKey and Id of the owned system. + /// The ILogger implementation, used for logging purposes. + public PVOutputClient(PVOutputClientOptions options, ILogger logger) : this(options?.ApiKey, options.OwnedSystemId, new HttpClientProvider(), logger) { } diff --git a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs index 2e9b403..83e4bba 100644 --- a/src/PVOutput.Net/Requests/Handler/RequestHandler.cs +++ b/src/PVOutput.Net/Requests/Handler/RequestHandler.cs @@ -145,7 +145,7 @@ private bool ResponseIsErrorResponse(HttpResponseMessage responseMessage, Stream return false; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Logging is non translatable for now")] private PVOutputApiError ProcessHttpErrorResults(HttpResponseMessage response, Stream responseStream) { if (response.IsSuccessStatusCode) @@ -206,7 +206,7 @@ private static string GetBasicResponseState(Stream responseStream) return null; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Logging is non translatable for now")] private PVOutputApiRateInformation GetApiRateInformationfromResponse(HttpResponseMessage response) { var result = new PVOutputApiRateInformation(); @@ -285,7 +285,7 @@ private static string CreateUrl(IRequest request) return $"{PVOutputClient.PVOutputBaseUri}{apiUri}"; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Logging")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Logging is non translatable for now")] internal Task ExecuteRequestAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default) { SetRequestHeaders(requestMessage); From 4780ed893a0d6217d448c2870248a2561dfe0db1 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2020 22:28:06 +0100 Subject: [PATCH 7/9] Restructured constructors some more, removed one --- src/PVOutput.Net/PVOutputClient.cs | 53 +++++++++++-------- .../Utils/TestOutputLogger.cs | 2 +- tests/PVOutput.Net.Tests/Utils/TestUtility.cs | 8 ++- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/PVOutput.Net/PVOutputClient.cs b/src/PVOutput.Net/PVOutputClient.cs index 0180abb..14f4abc 100644 --- a/src/PVOutput.Net/PVOutputClient.cs +++ b/src/PVOutput.Net/PVOutputClient.cs @@ -13,7 +13,19 @@ namespace PVOutput.Net public sealed class PVOutputClient { internal const string PVOutputBaseUri = @"https://pvoutput.org/service/r2/"; - internal IHttpClientProvider HttpClientProvider { get; } + + private IHttpClientProvider _httpClientProvider; + internal IHttpClientProvider HttpClientProvider + { + get + { + return _httpClientProvider ?? (_httpClientProvider = new HttpClientProvider()); + } + set + { + _httpClientProvider = value; + } + } internal ILogger Logger { get; } @@ -107,8 +119,23 @@ public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSyst /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. /// The ILogger implementation, used for logging purposes. - public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) : this(apiKey, ownedSystemId, new HttpClientProvider(), logger) + public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) { + ApiKey = apiKey; + OwnedSystemId = ownedSystemId; + Logger = logger ?? NullLogger.Instance; + + Output = new OutputService(this); + System = new SystemService(this); + Status = new StatusService(this); + Statistics = new StatisticsService(this); + Missing = new MissingService(this); + Team = new TeamService(this); + Extended = new ExtendedService(this); + Favourite = new FavouriteService(this); + Insolation = new InsolationService(this); + Supply = new SupplyService(this); + Search = new SearchService(this); } /// @@ -124,28 +151,8 @@ public PVOutputClient(PVOutputClientOptions options) : this(options?.ApiKey, opt /// /// Options to use for the client, containing the ApiKey and Id of the owned system. /// The ILogger implementation, used for logging purposes. - public PVOutputClient(PVOutputClientOptions options, ILogger logger) : this(options?.ApiKey, options.OwnedSystemId, new HttpClientProvider(), logger) + public PVOutputClient(PVOutputClientOptions options, ILogger logger) : this(options?.ApiKey, options.OwnedSystemId, logger) { } - - internal PVOutputClient(string apiKey, int ownedSystemId, IHttpClientProvider httpClientProvider, ILogger logger) - { - ApiKey = apiKey; - OwnedSystemId = ownedSystemId; - HttpClientProvider = httpClientProvider ?? new HttpClientProvider(); - Logger = logger ?? NullLogger.Instance; - - Output = new OutputService(this); - System = new SystemService(this); - Status = new StatusService(this); - Statistics = new StatisticsService(this); - Missing = new MissingService(this); - Team = new TeamService(this); - Extended = new ExtendedService(this); - Favourite = new FavouriteService(this); - Insolation = new InsolationService(this); - Supply = new SupplyService(this); - Search = new SearchService(this); - } } } diff --git a/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs b/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs index f0e058f..6b8e72a 100644 --- a/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs +++ b/tests/PVOutput.Net.Tests/Utils/TestOutputLogger.cs @@ -5,7 +5,7 @@ namespace PVOutput.Net.Tests.Utils { - internal class TestOutputLogger : ILogger, IDisposable + internal class TestOutputLogger : ILogger, IDisposable { private readonly Stack _scopeStack = new Stack(); diff --git a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs index 22c5822..0c35529 100644 --- a/tests/PVOutput.Net.Tests/Utils/TestUtility.cs +++ b/tests/PVOutput.Net.Tests/Utils/TestUtility.cs @@ -38,7 +38,9 @@ public static PVOutputClient GetMockClient(string uri, string mockResponseConten var provider = new TestHttpClientProvider(); provider.When(uri, mockResponseContent); provider.MockHttpMessageHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, new TestOutputLogger()); + var client = new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, new TestOutputLogger()); + client.HttpClientProvider = provider; + return client; } public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandler) @@ -46,7 +48,9 @@ public static PVOutputClient GetMockClient(out MockHttpMessageHandler mockHandle var provider = new TestHttpClientProvider(); mockHandler = provider.MockHttpMessageHandler; mockHandler.Fallback.RespondPlainText(""); - return new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, provider, new TestOutputLogger()); + var client = new PVOutputClient(TestConstants.PVOUTPUT_API_KEY, TestConstants.PVOUTPUT_SYSTEM_ID, new TestOutputLogger()); + client.HttpClientProvider = provider; + return client; } } } From 5a83d7e8132c787038c0b275a97002fd7f7cda74 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2020 22:51:58 +0100 Subject: [PATCH 8/9] Final constructor clean up At the cost of read only properties, the constructors are easier to understand and maintain now Implemented lazy Logger property Added unit tests for the constructors, using NSubstitute --- src/PVOutput.Net/PVOutputClient.cs | 94 ++++++++++++------- .../Client/PVOutputClientTests.cs | 69 ++++++++++++++ .../PVOutput.Net.Tests.csproj | 1 + 3 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 tests/PVOutput.Net.Tests/Client/PVOutputClientTests.cs diff --git a/src/PVOutput.Net/PVOutputClient.cs b/src/PVOutput.Net/PVOutputClient.cs index 14f4abc..0af2ea1 100644 --- a/src/PVOutput.Net/PVOutputClient.cs +++ b/src/PVOutput.Net/PVOutputClient.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using Dawn; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using PVOutput.Net.DependencyInjection; using PVOutput.Net.Modules; @@ -27,7 +28,18 @@ internal IHttpClientProvider HttpClientProvider } } - internal ILogger Logger { get; } + private ILogger _logger; + internal ILogger Logger + { + get + { + return _logger ?? (_logger = NullLogger.Instance); + } + set + { + _logger = value; + } + } /// ApiKey to use with authenticating. public string ApiKey { get; set; } @@ -42,75 +54,79 @@ internal IHttpClientProvider HttpClientProvider /// The output service. /// See the official API information. /// - public OutputService Output { get; } + public OutputService Output { get; private set; } /// /// The system service. /// See the official API information. /// - public SystemService System { get; } + public SystemService System { get; private set; } /// /// The status service. /// See the official API information. /// - public StatusService Status { get; } + public StatusService Status { get; private set; } /// /// The statistic service. /// See the official API information. /// - public StatisticsService Statistics { get; } + public StatisticsService Statistics { get; private set; } /// /// The missing service. /// See the official API information. /// - public MissingService Missing { get; } + public MissingService Missing { get; private set; } /// /// The team service. /// See the official API information. /// - public TeamService Team { get; } + public TeamService Team { get; private set; } /// /// The extended service. /// See the official API information. /// - public ExtendedService Extended { get; } + public ExtendedService Extended { get; private set; } /// /// The favourite service. /// See the official API information. /// - public FavouriteService Favourite { get; } + public FavouriteService Favourite { get; private set; } /// /// The insolation service. /// See the official API information. /// - public InsolationService Insolation { get; } + public InsolationService Insolation { get; private set; } /// /// The supply service. /// See the official API information. /// - public SupplyService Supply { get; } + public SupplyService Supply { get; private set; } /// /// The search service /// See the official API information. /// - public SearchService Search { get; } + public SearchService Search { get; private set; } /// /// Creates a new PVOutputClient. /// /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. - public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSystemId, null) - { + public PVOutputClient(string apiKey, int ownedSystemId) + { + ApiKey = apiKey; + OwnedSystemId = ownedSystemId; + + CreateServices(); } /// @@ -119,31 +135,25 @@ public PVOutputClient(string apiKey, int ownedSystemId) : this(apiKey, ownedSyst /// ApiKey to use with authenticating. /// Id of the currently owned system used for authenticating. /// The ILogger implementation, used for logging purposes. - public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) + public PVOutputClient(string apiKey, int ownedSystemId, ILogger logger) : this(apiKey, ownedSystemId) { - ApiKey = apiKey; - OwnedSystemId = ownedSystemId; - Logger = logger ?? NullLogger.Instance; - - Output = new OutputService(this); - System = new SystemService(this); - Status = new StatusService(this); - Statistics = new StatisticsService(this); - Missing = new MissingService(this); - Team = new TeamService(this); - Extended = new ExtendedService(this); - Favourite = new FavouriteService(this); - Insolation = new InsolationService(this); - Supply = new SupplyService(this); - Search = new SearchService(this); + Logger = logger; } /// - /// Creates a new PVOutputClient, with a ILogger attached. + /// Creates a new PVOutputClient. /// /// Options to use for the client, containing the ApiKey and Id of the owned system. - public PVOutputClient(PVOutputClientOptions options) : this(options?.ApiKey, options.OwnedSystemId, null) + public PVOutputClient(PVOutputClientOptions options) { + Guard.Argument(options).NotNull(); + +#pragma warning disable CA1062 // Validate arguments of public methods + ApiKey = options.ApiKey; + OwnedSystemId = options.OwnedSystemId; +#pragma warning restore CA1062 // Validate arguments of public methods + + CreateServices(); } /// @@ -151,8 +161,24 @@ public PVOutputClient(PVOutputClientOptions options) : this(options?.ApiKey, opt /// /// Options to use for the client, containing the ApiKey and Id of the owned system. /// The ILogger implementation, used for logging purposes. - public PVOutputClient(PVOutputClientOptions options, ILogger logger) : this(options?.ApiKey, options.OwnedSystemId, logger) + public PVOutputClient(PVOutputClientOptions options, ILogger logger) : this(options) { + Logger = logger; + } + + private void CreateServices() + { + Output = new OutputService(this); + System = new SystemService(this); + Status = new StatusService(this); + Statistics = new StatisticsService(this); + Missing = new MissingService(this); + Team = new TeamService(this); + Extended = new ExtendedService(this); + Favourite = new FavouriteService(this); + Insolation = new InsolationService(this); + Supply = new SupplyService(this); + Search = new SearchService(this); } } } diff --git a/tests/PVOutput.Net.Tests/Client/PVOutputClientTests.cs b/tests/PVOutput.Net.Tests/Client/PVOutputClientTests.cs new file mode 100644 index 0000000..820aa4f --- /dev/null +++ b/tests/PVOutput.Net.Tests/Client/PVOutputClientTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NSubstitute; +using NUnit.Framework; +using PVOutput.Net.DependencyInjection; + +namespace PVOutput.Net.Tests.Client +{ + [TestFixture] + public class PVOutputClientTests + { + [Test] + public void Create_ClientWithValidParameters_CreatesCorrectClient() + { + var client = new PVOutputClient("apikey", 1234); + + Assert.Multiple(() => + { + Assert.AreEqual("apikey", client.ApiKey); + Assert.AreEqual(1234, client.OwnedSystemId); + Assert.AreEqual(NullLogger.Instance, client.Logger); + }); + } + + [Test] + public void Create_ClientWithLogger_CreatesClientWithLogger() + { + var loggerSubstitute = Substitute.For>(); + var client = new PVOutputClient("apikey", 1234, loggerSubstitute); + + Assert.Multiple(() => + { + Assert.AreEqual("apikey", client.ApiKey); + Assert.AreEqual(1234, client.OwnedSystemId); + Assert.AreNotEqual(NullLogger.Instance, client.Logger); + }); + } + + + [Test] + public void Create_ClientWithValidOptions_CreatesCorrectClient() + { + var client = new PVOutputClient(new PVOutputClientOptions() { ApiKey = "apikey", OwnedSystemId = 1234 }); + + Assert.Multiple(() => + { + Assert.AreEqual("apikey", client.ApiKey); + Assert.AreEqual(1234, client.OwnedSystemId); + }); + } + + [Test] + public void Create_ClientWithOptionsAndLogger_CreatesClientWithLogger() + { + var loggerSubstitute = Substitute.For>(); + var client = new PVOutputClient(new PVOutputClientOptions() { ApiKey = "apikey", OwnedSystemId = 1234 }, loggerSubstitute); + + Assert.Multiple(() => + { + Assert.AreEqual("apikey", client.ApiKey); + Assert.AreEqual(1234, client.OwnedSystemId); + Assert.AreNotEqual(NullLogger.Instance, client.Logger); + }); + } + } +} diff --git a/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj b/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj index f2e255e..ede32b0 100644 --- a/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj +++ b/tests/PVOutput.Net.Tests/PVOutput.Net.Tests.csproj @@ -10,6 +10,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + From c894c92d965a49710ef8a4ebb3116c0185e44856 Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2020 22:56:00 +0100 Subject: [PATCH 9/9] Fixed incorrect unit test for Leave Team Found it through codecov! --- tests/PVOutput.Net.Tests/Modules/Team/TeamServiceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PVOutput.Net.Tests/Modules/Team/TeamServiceTests.cs b/tests/PVOutput.Net.Tests/Modules/Team/TeamServiceTests.cs index 00f2508..f9bff5e 100644 --- a/tests/PVOutput.Net.Tests/Modules/Team/TeamServiceTests.cs +++ b/tests/PVOutput.Net.Tests/Modules/Team/TeamServiceTests.cs @@ -51,11 +51,11 @@ public async Task TeamService_LeaveTeam_CallsCorrectUri() { PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider); - testProvider.ExpectUriFromBase(JOINTEAM_URL) + testProvider.ExpectUriFromBase(LEAVETEAM_URL) .WithQueryString("tid=340") .Respond(HttpStatusCode.OK, "text/plain", "You have left team [team_name]"); - var response = await client.Team.JoinTeamAsync(340); + var response = await client.Team.LeaveTeamAsync(340); testProvider.VerifyNoOutstandingExpectation(); Assert.Multiple(() =>