diff --git a/.circleci/config.yml b/.circleci/config.yml index 925d13c90..a361b04be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,7 +61,7 @@ jobs: parameters: dotnet-image: type: string - default: &default-dotnet-image "mcr.microsoft.com/dotnet/sdk:7.0" + default: &default-dotnet-image "mcr.microsoft.com/dotnet/sdk:8.0" dotnet-target-version: type: string default: "netstandard2.1" @@ -123,18 +123,20 @@ jobs: shell: bash steps: - checkout - - run: choco install influxdb1 --version=1.8.0 + - run: | + choco feature enable -n allowGlobalConfirmation + choco install influxdb1 --version=1.8.0 - run: export INFLUXDB_HTTP_FLUX_ENABLED=true - run: name: "Start InfluxDB" command: /c/influxdata/influxdb-1.8.0-1/influxd.exe -config "Scripts/influxdb.conf" background: true - run: | - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Test/Client.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Examples/Examples.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Test/Client.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Examples/Examples.csproj - run: dotnet sln remove Examples/ExampleBlazor/ExampleBlazor.csproj - run: dotnet nuget locals --clear all - run: dotnet restore --no-cache --force -s https://api.nuget.org/v3/index.json @@ -147,16 +149,16 @@ jobs: steps: - checkout - run: | - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Test/Client.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Examples/Examples.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Test/Client.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Examples/Examples.csproj - run: name: Check compilation warnings command: | dotnet clean --configuration Release - dotnet build --configuration Release -warnAsError -warnAsMessage:CS0618,NETSDK1138 + dotnet build --configuration Release -warnAsError -warnAsMessage:CS0419,CS0618,CS1591,CS1573,CS1574,NETSDK1138 check-code-formatting: docker: @@ -223,6 +225,9 @@ workflows: dotnet-image: "mcr.microsoft.com/dotnet/sdk:6.0" - tests-dotnet: name: dotnet-7.0 + dotnet-image: "mcr.microsoft.com/dotnet/sdk:7.0" + - tests-dotnet: + name: dotnet-8.0 - tests-windows: name: dotnet-windows - deploy-preview: @@ -234,6 +239,7 @@ workflows: - dotnet-5.0 - dotnet-6.0 - dotnet-7.0 + - dotnet-8.0 - dotnet-windows filters: branches: @@ -243,4 +249,4 @@ workflows: when: equal: [ scheduled_pipeline, << pipeline.trigger_source >> ] jobs: - - tests-dotnet \ No newline at end of file + - tests-dotnet diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 03eba1889..4c3f395bf 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,5 +3,5 @@ updates: - package-ecosystem: "nuget" directory: "/" schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 10 diff --git a/CHANGELOG.md b/CHANGELOG.md index bfb4a267a..5683da693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,118 @@ +## 4.19.0 [unreleased] + +### Dependencies + +⚠️ Important Notice: Starting from this release, we won’t be listing every dependency change in our changelog. This helps us maintain the project faster and focus on important features for our InfluxDB client. + +### CI +1. [#681](https://github.com/influxdata/influxdb-client-csharp/pull/681): Add build for `dotnet8` + +## 4.18.0 [2024-09-13] + +### Features: +1. [#658](https://github.com/influxdata/influxdb-client-csharp/pull/658): Add HttpHeaders as `IEnumerable` to `HttpException` and facilitate access in `WriteErrorEvent`. Includes new example `HttpErrorHandling`. + +### Dependencies +Update dependencies: + +#### Build: + - [#659](https://github.com/influxdata/influxdb-client-csharp/pull/659): `RestSharp` to `112.0.0` + +#### Test: + - [#663](https://github.com/influxdata/influxdb-client-csharp/pull/663): `WireMock.Net` to `1.6.1` + +## 4.17.0 [2024-08-12] + +### Breaking Changes + +#### API + +- `ApiResponse` headers has been changed to `IEnumerable<(string Name, string Value)>` + +### Bug Fixes +1. [#649](https://github.com/influxdata/influxdb-client-csharp/pull/649): Use HttpCompletionOption.ResponseHeadersRead for asynchronous QueryApi + +### Dependencies +Update dependencies: + +#### Build: + - [#650](https://github.com/influxdata/influxdb-client-csharp/pull/650): `RestSharp` to `111.4.0` + - [#662](https://github.com/influxdata/influxdb-client-csharp/pull/662): `Microsoft.Extensions.ObjectPool` to `8.0.8` + +#### Test: + - [#652](https://github.com/influxdata/influxdb-client-csharp/pull/652): `NUnit3TestAdapter` to `4.6.0` + - [#654](https://github.com/influxdata/influxdb-client-csharp/pull/654): `WireMock.Net` to `1.5.62` + - [#661](https://github.com/influxdata/influxdb-client-csharp/pull/661): `Microsoft.NET.Test.Sdk` to `17.11.0` + +#### Examples: + - [#664](https://github.com/influxdata/influxdb-client-csharp/pull/664): `Radzen.Blazor` to `5.1.6` + +## 4.16.0 [2024-06-24] + +### Features: + - [#633](https://github.com/influxdata/influxdb-client-csharp/pull/633): Add package XML documentation. + +### Dependencies +Update dependencies: + +#### Build: + - [#634](https://github.com/influxdata/influxdb-client-csharp/pull/634): `CsvHelper` to `32.0.3` + - [#635](https://github.com/influxdata/influxdb-client-csharp/pull/635): `Microsoft.Extensions.ObjectPool` to `8.0.6` + - [#638](https://github.com/influxdata/influxdb-client-csharp/pull/638): `System.Reactive` to `6.0.1` + +#### Test: + - [#639](https://github.com/influxdata/influxdb-client-csharp/pull/639): `Microsoft.NET.Test.Sdk` to `17.10.0` + - [#641](https://github.com/influxdata/influxdb-client-csharp/pull/641): `WireMock.Net` to `1.5.56` + +#### Examples: + - [#636](https://github.com/influxdata/influxdb-client-csharp/pull/636): `Radzen.Blazor` to `4.32.3` + +## 4.15.0 [2024-05-17] + +### Bug Fixes +1. [#632](https://github.com/influxdata/influxdb-client-csharp/pull/632): Use HttpCompletionOption.ResponseHeadersRead for streaming + +### Dependencies +Update dependencies: + +#### Build: + - [#619](https://github.com/influxdata/influxdb-client-csharp/pull/619): `CsvHelper` to `31.0.2` + - [#629](https://github.com/influxdata/influxdb-client-csharp/pull/629): `Microsoft.Extensions.ObjectPool` to `8.0.3` + +#### Test: + - [#608](https://github.com/influxdata/influxdb-client-csharp/pull/608): `NUnit` to `3.14.0` + - [#616](https://github.com/influxdata/influxdb-client-csharp/pull/616): `Microsoft.NET.Test.Sdk` to `17.9.0` + - [#626](https://github.com/influxdata/influxdb-client-csharp/pull/626): `coverlet.collector` to `6.0.2` + - [#628](https://github.com/influxdata/influxdb-client-csharp/pull/628): `WireMock.Net` to `1.5.51` + +#### Examples: + - [#627](https://github.com/influxdata/influxdb-client-csharp/pull/627): `Radzen.Blazor` to `4.29.1` + - [#622](https://github.com/influxdata/influxdb-client-csharp/pull/622): `NodaTime` to `3.1.11` + +## 4.14.0 [2023-11-07] + +### Features + +1. [#590](https://github.com/influxdata/influxdb-client-csharp/pull/590): Allows disable Trace verbose messages + +### Dependencies +Update dependencies: + +#### Build: + - [#597](https://github.com/influxdata/influxdb-client-csharp/pull/597): `Microsoft.Extensions.ObjectPool` to `8.0.0` + - [#572](https://github.com/influxdata/influxdb-client-csharp/pull/572): `NodaTime.Serialization.JsonNet` to `3.1.0` + - [#595](https://github.com/influxdata/influxdb-client-csharp/pull/595): `System.Collections.Immutable` to `8.0.0` + - [#599](https://github.com/influxdata/influxdb-client-csharp/pull/599): `System.Configuration.ConfigurationManager` to `8.0.0` + +#### Examples: + - [#593](https://github.com/influxdata/influxdb-client-csharp/pull/593): `Radzen.Blazor` to `4.22.1` + +#### Test: + - [#596](https://github.com/influxdata/influxdb-client-csharp/pull/596): `WireMock.Net` to `1.5.40` + - [#598](https://github.com/influxdata/influxdb-client-csharp/pull/598): `Microsoft.NET.Test.Sdk` to `17.8.0` + - [#568](https://github.com/influxdata/influxdb-client-csharp/pull/568): `Moq` to `4.20.69` + - [#594](https://github.com/influxdata/influxdb-client-csharp/pull/594): `Tomlyn.Signed` to `0.17.0` + ## 4.13.0 [2023-07-28] ### Features diff --git a/Client.Core.Test/AbstractTest.cs b/Client.Core.Test/AbstractTest.cs index d37163755..fffd93f3e 100644 --- a/Client.Core.Test/AbstractTest.cs +++ b/Client.Core.Test/AbstractTest.cs @@ -11,7 +11,11 @@ namespace InfluxDB.Client.Core.Test { public class AbstractTest { - private static readonly TraceListener ConsoleOutListener = new TextWriterTraceListener(Console.Out); + private static readonly TraceListener ConsoleOutListener = new TextWriterTraceListener(Console.Out) + { + Filter = InfluxDBTraceFilter.SuppressInfluxVerbose() + }; + private static readonly int DefaultWait = 10; private static readonly int DefaultInfluxDBSleep = 100; diff --git a/Client.Core.Test/Client.Core.Test.csproj b/Client.Core.Test/Client.Core.Test.csproj index bdbfbc2e6..d504ffbf8 100644 --- a/Client.Core.Test/Client.Core.Test.csproj +++ b/Client.Core.Test/Client.Core.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -13,10 +13,10 @@ - - - - + + + + @@ -24,11 +24,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Core/Client.Core.csproj b/Client.Core/Client.Core.csproj index ef6b2ef9d..69b59f496 100644 --- a/Client.Core/Client.Core.csproj +++ b/Client.Core/Client.Core.csproj @@ -7,8 +7,8 @@ InfluxDB Client Core - exceptions, validations, REST client. influxdb-client-csharp Contributors InfluxDB.Client.Core - 4.13.0 - + 4.19.0 + dev InfluxDB.Client.Core influxdata;timeseries;flux;influxdb @@ -27,16 +27,20 @@ true + + true + + - + - - - + + + diff --git a/Client.Core/Exceptions/InfluxException.cs b/Client.Core/Exceptions/InfluxException.cs index d413f25c5..ef679c58b 100644 --- a/Client.Core/Exceptions/InfluxException.cs +++ b/Client.Core/Exceptions/InfluxException.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using InfluxDB.Client.Core.Internal; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -56,6 +57,13 @@ public HttpException(string message, int status, Exception exception = null) : b /// public int? RetryAfter { get; set; } +#nullable enable + /// + /// The response headers + /// + public IEnumerable? Headers { get; private set; } +#nullable disable + public static HttpException Create(RestResponse requestResult, object body) { Arguments.CheckNotNull(requestResult, nameof(requestResult)); @@ -162,6 +170,7 @@ public static HttpException Create(object content, IEnumerable err.ErrorBody = errorBody; err.RetryAfter = retryAfter; + err.Headers = headers; return err; } diff --git a/Client.Core/InfluxDBTraceFilter.cs b/Client.Core/InfluxDBTraceFilter.cs new file mode 100644 index 000000000..3eb5814c5 --- /dev/null +++ b/Client.Core/InfluxDBTraceFilter.cs @@ -0,0 +1,67 @@ +using System.Diagnostics; +using System.Linq; + +namespace InfluxDB.Client.Core +{ + /// + /// The is used to filter client trace messages by category. + /// + public class InfluxDBTraceFilter : TraceFilter + { + public const string CategoryInflux = "influx-client"; + public const string CategoryInfluxError = "influx-client-error"; + public const string CategoryInfluxQuery = "influx-client-query"; + public const string CategoryInfluxQueryError = "influx-client-query-error"; + public const string CategoryInfluxWrite = "influx-client-write"; + public const string CategoryInfluxWriteError = "influx-client-write-error"; + public const string CategoryInfluxLogger = "influx-client-logger"; + + private readonly string[] _categoryToFilter; + private readonly bool _keep; + + public InfluxDBTraceFilter(string[] categoryToFilter, bool keep) + { + _categoryToFilter = categoryToFilter; + _keep = keep; + } + + public override bool ShouldTrace(TraceEventCache eventCache, string source, TraceEventType eventType, int id, + string formatOrMessage, object[] args, object data, object[] dataArray) + { + return _categoryToFilter.Any(x => x == source) ^ _keep; + } + + /// + /// Suppress all client trace messages. + /// + /// Trace Filter + public static InfluxDBTraceFilter SuppressInflux() + { + return new InfluxDBTraceFilter(new string[] + { + CategoryInflux, + CategoryInfluxError, + CategoryInfluxQuery, + CategoryInfluxQueryError, + CategoryInfluxWrite, + CategoryInfluxWriteError, + CategoryInfluxLogger + }, false); + } + + /// + /// Suppress all client trace messages except , , . + /// + /// Trace Filter + public static InfluxDBTraceFilter SuppressInfluxVerbose() + { + return new InfluxDBTraceFilter(new string[] + { + CategoryInflux, + CategoryInfluxQuery, + CategoryInfluxWrite, + CategoryInfluxLogger + }, false); + } + } +} \ No newline at end of file diff --git a/Client.Core/Internal/AbstractQueryClient.cs b/Client.Core/Internal/AbstractQueryClient.cs index c4d65b749..b184be46b 100644 --- a/Client.Core/Internal/AbstractQueryClient.cs +++ b/Client.Core/Internal/AbstractQueryClient.cs @@ -13,6 +13,7 @@ using InfluxDB.Client.Core.Flux.Internal; using Newtonsoft.Json.Linq; using RestSharp; +using RestSharp.Interceptors; namespace InfluxDB.Client.Core.Internal { @@ -39,7 +40,7 @@ protected AbstractQueryClient(IFluxResultMapper mapper, FluxCsvParser csvParser) _csvParser = csvParser; } - protected Task Query(Func, RestRequest> queryFn, + protected Task Query(RestRequest query, FluxCsvParser.IFluxResponseConsumer responseConsumer, Action onError, Action onComplete, CancellationToken cancellationToken) @@ -56,10 +57,10 @@ void Consumer(Stream bufferedStream) } } - return Query(queryFn, Consumer, onError, onComplete, cancellationToken); + return Query(query, Consumer, onError, onComplete, cancellationToken); } - protected Task QueryRaw(Func, RestRequest> queryFn, + protected Task QueryRaw(RestRequest query, Action onResponse, Action onError, Action onComplete, CancellationToken cancellationToken) @@ -76,10 +77,10 @@ void Consumer(Stream bufferedStream) } } - return Query(queryFn, Consumer, onError, onComplete, cancellationToken); + return Query(query, Consumer, onError, onComplete, cancellationToken); } - protected void QuerySync(Func, RestRequest> queryFn, + protected void QuerySync(RestRequest query, FluxCsvParser.IFluxResponseConsumer responseConsumer, Action onError, Action onComplete, @@ -97,21 +98,21 @@ void Consumer(CancellationToken cancellable, Stream bufferedStream) } } - QuerySync(queryFn, Consumer, onError, onComplete, cancellationToken); + QuerySync(query, Consumer, onError, onComplete, cancellationToken); } - private async Task Query(Func, RestRequest> queryFn, + private async Task Query(RestRequest query, Action consumer, Action onError, Action onComplete, CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, "queryFn"); + Arguments.CheckNotNull(query, "query"); Arguments.CheckNotNull(consumer, "consumer"); Arguments.CheckNotNull(onError, "onError"); Arguments.CheckNotNull(onComplete, "onComplete"); try { - var query = queryFn.Invoke((response, request) => + query.AdvancedResponseWriter = (response, request) => { var result = GetStreamFromResponse(response, cancellationToken); result = AfterIntercept((int)response.StatusCode, @@ -122,7 +123,7 @@ private async Task Query(Func, RestRequest> queryFn, + private void QuerySync(RestRequest query, Action consumer, Action onError, Action onComplete, CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, "queryFn"); + Arguments.CheckNotNull(query, "query"); Arguments.CheckNotNull(consumer, "consumer"); Arguments.CheckNotNull(onError, "onError"); Arguments.CheckNotNull(onComplete, "onComplete"); try { - var query = queryFn.Invoke((response, request) => + query.AdvancedResponseWriter = (response, request) => { var result = GetStreamFromResponse(response, cancellationToken); result = AfterIntercept((int)response.StatusCode, @@ -166,7 +167,7 @@ private void QuerySync(Func consumer(cancellationToken, result); return FromHttpResponseMessage(response, request); - }); + }; BeforeIntercept(query); @@ -188,32 +189,21 @@ private void QuerySync(Func } protected async IAsyncEnumerable QueryEnumerable( - Func, RestRequest> queryFn, + RestRequest query, Func convert, [EnumeratorCancellation] CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, nameof(queryFn)); + Arguments.CheckNotNull(query, nameof(query)); - Stream stream = null; - var query = queryFn.Invoke((response, request) => + query.Interceptors = new List { - stream = GetStreamFromResponse(response, cancellationToken); - stream = AfterIntercept((int)response.StatusCode, - () => response.Headers.ToHeaderParameters(response.Content.Headers), stream); - - RaiseForInfluxError(response, stream); - - return FromHttpResponseMessage(response, request); - }); - - BeforeIntercept(query); - - var restResponse = await RestClient.ExecuteAsync(query, cancellationToken).ConfigureAwait(false); - if (restResponse.ErrorException != null) - { - throw restResponse.ErrorException; - } + new RequestBeforeAfterInterceptor( + BeforeIntercept, + (statusCode, headers, body) => AfterIntercept(statusCode, headers, body) + ) + }; + var stream = await RestClient.DownloadStreamAsync(query, cancellationToken).ConfigureAwait(false); await foreach (var (_, record) in _csvParser .ParseFluxResponseAsync(new StreamReader(stream), cancellationToken) .ConfigureAwait(false)) @@ -321,8 +311,9 @@ protected void CatchOrPropagateException(Exception exception, // if (IsCloseException(exception)) { - Trace.WriteLine("Socket closed by remote server or end of data"); - Trace.WriteLine(exception); + Trace.WriteLine("Socket closed by remote server or end of data", + InfluxDBTraceFilter.CategoryInfluxQueryError); + Trace.WriteLine(exception, InfluxDBTraceFilter.CategoryInfluxQueryError); } else { @@ -408,4 +399,42 @@ private Stream GetStreamFromResponse(HttpResponseMessage response, CancellationT return streamFromResponse; } } + + /// + /// The interceptor that is called before and after the request. + /// + internal class RequestBeforeAfterInterceptor : Interceptor + { + private readonly Action _beforeRequest; + private readonly Action>, T> _afterRequest; + + /// + /// Construct the interceptor. + /// + /// Intercept request before HTTP call + /// Intercept response before parsing resutlts + internal RequestBeforeAfterInterceptor( + Action beforeRequest = null, + Action>, T> afterRequest = null) + { + _beforeRequest = beforeRequest; + _afterRequest = afterRequest; + } + + public override ValueTask BeforeRequest(RestRequest request, CancellationToken cancellationToken) + { + _beforeRequest?.Invoke(request); + return base.BeforeRequest(request, cancellationToken); + } + + public override ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, + CancellationToken cancellationToken) + { + _afterRequest?.Invoke( + (int)responseMessage.StatusCode, + () => responseMessage.Headers.ToHeaderParameters(responseMessage.Content.Headers), + default); + return base.AfterHttpRequest(responseMessage, cancellationToken); + } + } } \ No newline at end of file diff --git a/Client.Core/Internal/AbstractRestClient.cs b/Client.Core/Internal/AbstractRestClient.cs index 60692aae7..eee5220e9 100644 --- a/Client.Core/Internal/AbstractRestClient.cs +++ b/Client.Core/Internal/AbstractRestClient.cs @@ -25,7 +25,7 @@ protected async Task PingAsync(Task request) } catch (Exception e) { - Trace.WriteLine($"Error: {e.Message}"); + Trace.WriteLine($"Error: {e.Message}", InfluxDBTraceFilter.CategoryInfluxError); return false; } } diff --git a/Client.Core/Internal/EnumConverter.cs b/Client.Core/Internal/EnumConverter.cs index 39044aaa6..72cf8016b 100644 --- a/Client.Core/Internal/EnumConverter.cs +++ b/Client.Core/Internal/EnumConverter.cs @@ -16,7 +16,8 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } catch (JsonSerializationException e) { - Trace.WriteLine($"Error converting enum value. Returning null. {e}"); + Trace.WriteLine($"Error converting enum value. Returning null. {e}", + InfluxDBTraceFilter.CategoryInfluxError); return null; } diff --git a/Client.Core/Internal/LoggingHandler.cs b/Client.Core/Internal/LoggingHandler.cs index 478167934..c5ae2c6e7 100644 --- a/Client.Core/Internal/LoggingHandler.cs +++ b/Client.Core/Internal/LoggingHandler.cs @@ -27,7 +27,7 @@ public void BeforeIntercept(RestRequest request) var isBody = Level == LogLevel.Body; var isHeader = isBody || Level == LogLevel.Headers; - Trace.WriteLine($"--> {request.Method} {request.Resource}"); + Trace.WriteLine($"--> {request.Method} {request.Resource}", InfluxDBTraceFilter.CategoryInfluxLogger); if (isHeader) { @@ -56,12 +56,12 @@ public void BeforeIntercept(RestRequest request) stringBody = body.Value.ToString(); } - Trace.WriteLine($"--> Body: {stringBody}"); + Trace.WriteLine($"--> Body: {stringBody}", InfluxDBTraceFilter.CategoryInfluxLogger); } } - Trace.WriteLine("--> END"); - Trace.WriteLine("-->"); + Trace.WriteLine("--> END", InfluxDBTraceFilter.CategoryInfluxLogger); + Trace.WriteLine("-->", InfluxDBTraceFilter.CategoryInfluxLogger); } public object AfterIntercept(int statusCode, Func> headers, object body) @@ -75,7 +75,7 @@ public object AfterIntercept(int statusCode, Func> var isBody = Level == LogLevel.Body; var isHeader = isBody || Level == LogLevel.Headers; - Trace.WriteLine($"<-- {statusCode}"); + Trace.WriteLine($"<-- {statusCode}", InfluxDBTraceFilter.CategoryInfluxLogger); if (isHeader) { @@ -101,11 +101,11 @@ public object AfterIntercept(int statusCode, Func> if (!string.IsNullOrEmpty(stringBody)) { - Trace.WriteLine($"<-- Body: {stringBody}"); + Trace.WriteLine($"<-- Body: {stringBody}", InfluxDBTraceFilter.CategoryInfluxLogger); } } - Trace.WriteLine("<-- END"); + Trace.WriteLine("<-- END", InfluxDBTraceFilter.CategoryInfluxLogger); return freshBody; } @@ -131,7 +131,7 @@ private void LogHeaders(IEnumerable headers, string direction, var value = string.Equals(emp.Name, "Authorization", StringComparison.OrdinalIgnoreCase) ? "***" : emp.Value; - Trace.WriteLine($"{direction} {type}: {emp.Name}={value}"); + Trace.WriteLine($"{direction} {type}: {emp.Name}={value}", InfluxDBTraceFilter.CategoryInfluxLogger); } } } diff --git a/Client.Core/Internal/RestSharpExtensions.cs b/Client.Core/Internal/RestSharpExtensions.cs index fcb01b114..72e1c5541 100644 --- a/Client.Core/Internal/RestSharpExtensions.cs +++ b/Client.Core/Internal/RestSharpExtensions.cs @@ -1,11 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; -using System.Reflection; using System.Threading; -using System.Threading.Tasks; using RestSharp; namespace InfluxDB.Client.Core.Internal @@ -27,20 +23,10 @@ internal static IEnumerable ToHeaderParameters(this HttpHeaders .Select(x => new HeaderParameter(x.Key, x.y)); } - internal static RestRequest AddAdvancedResponseHandler(this RestRequest restRequest, - Func advancedResponseWriter) - { - var field = restRequest.GetType() - .GetField("_advancedResponseHandler", BindingFlags.Instance | BindingFlags.NonPublic); - field!.SetValue(restRequest, advancedResponseWriter); - - return restRequest; - } - internal static RestResponse ExecuteSync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) { - return client.Execute(request, cancellationToken); + return client.Execute(request); } } } \ No newline at end of file diff --git a/Client.Legacy.Test/Client.Legacy.Test.csproj b/Client.Legacy.Test/Client.Legacy.Test.csproj index 0db09e135..6f7dc4d54 100644 --- a/Client.Legacy.Test/Client.Legacy.Test.csproj +++ b/Client.Legacy.Test/Client.Legacy.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -11,8 +11,8 @@ - - + + @@ -21,11 +21,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Legacy/Client.Legacy.csproj b/Client.Legacy/Client.Legacy.csproj index f1c1b500b..b10393a7f 100644 --- a/Client.Legacy/Client.Legacy.csproj +++ b/Client.Legacy/Client.Legacy.csproj @@ -6,8 +6,8 @@ The client that allow perform Flux Query against the InfluxDB 1.7+. influxdb-client-csharp Contributors InfluxDB.Client.Flux - 4.13.0 - + 4.19.0 + dev InfluxDB.Client.Flux influxdata;timeseries;flux;influxdb @@ -27,6 +27,10 @@ true + + true + + diff --git a/Client.Legacy/FluxClient.cs b/Client.Legacy/FluxClient.cs index 1c6e935dd..e5bfe6f0e 100644 --- a/Client.Legacy/FluxClient.cs +++ b/Client.Legacy/FluxClient.cs @@ -423,10 +423,9 @@ private RestRequest PingRequest() return new RestRequest("ping"); } - private Func, RestRequest> QueryRequest(string query) + private RestRequest QueryRequest(string query) { - return advancedResponseWriter => new RestRequest("api/v2/query", Method.Post) - .AddAdvancedResponseHandler(advancedResponseWriter) + return new RestRequest("api/v2/query", Method.Post) .AddParameter(new BodyParameter("application/json", query, "application/json")); } } diff --git a/Client.Linq.Test/Client.Linq.Test.csproj b/Client.Linq.Test/Client.Linq.Test.csproj index 2504fa715..bdb960d6d 100644 --- a/Client.Linq.Test/Client.Linq.Test.csproj +++ b/Client.Linq.Test/Client.Linq.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -11,9 +11,9 @@ - - - + + + @@ -23,11 +23,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Linq/Client.Linq.csproj b/Client.Linq/Client.Linq.csproj index b66e67c7a..fcadbde5f 100644 --- a/Client.Linq/Client.Linq.csproj +++ b/Client.Linq/Client.Linq.csproj @@ -7,8 +7,8 @@ The library supports querying InfluxDB 2.x by LINQ expressions. influxdb-client-csharp Contributors InfluxDB.Client.Linq - 4.13.0 - + 4.19.0 + dev InfluxDB.Client.Linq influxdata;timeseries;flux;influxdb;linq @@ -28,6 +28,10 @@ true + + true + + diff --git a/Client.Test/Client.Test.csproj b/Client.Test/Client.Test.csproj index d5562456e..1a4ca903c 100644 --- a/Client.Test/Client.Test.csproj +++ b/Client.Test/Client.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -12,10 +12,10 @@ - - - - + + + + @@ -24,11 +24,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Test/InfluxExceptionTest.cs b/Client.Test/InfluxExceptionTest.cs new file mode 100644 index 000000000..2c7e6a73c --- /dev/null +++ b/Client.Test/InfluxExceptionTest.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using InfluxDB.Client.Core.Exceptions; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using RestSharp; + +namespace InfluxDB.Client.Test +{ + [TestFixture] + public class InfluxExceptionTest + { + [Test] + public void ExceptionHeadersTest() + { + try + { + throw HttpException.Create( + JObject.Parse("{\"callId\": \"123456789\", \"message\": \"error in content object\"}"), + new List + { + new HeaderParameter("Trace-Id", "123456789ABCDEF0"), + new HeaderParameter("X-Influx-Version", "1.0.0"), + new HeaderParameter("X-Platform-Error-Code", "unavailable"), + new HeaderParameter("Retry-After", "60000") + }, + null, + HttpStatusCode.ServiceUnavailable); + } + catch (HttpException he) + { + Assert.AreEqual("error in content object", he?.Message); + + Assert.AreEqual(4, he?.Headers.Count()); + var headers = new Dictionary(); + foreach (var header in he?.Headers) headers.Add(header.Name, header.Value); + Assert.AreEqual("123456789ABCDEF0", headers["Trace-Id"]); + Assert.AreEqual("1.0.0", headers["X-Influx-Version"]); + Assert.AreEqual("unavailable", headers["X-Platform-Error-Code"]); + Assert.AreEqual("60000", headers["Retry-After"]); + } + } + } +} \ No newline at end of file diff --git a/Client.Test/ItErrorEventsTest.cs b/Client.Test/ItErrorEventsTest.cs new file mode 100644 index 000000000..e604200a5 --- /dev/null +++ b/Client.Test/ItErrorEventsTest.cs @@ -0,0 +1,103 @@ +using System; +using NUnit.Framework; +using System.Collections.Generic; +using System.Threading.Tasks; +using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Writes; + +namespace InfluxDB.Client.Test +{ + [TestFixture] + public class ItErrorEventsTest : AbstractItClientTest + { + private Organization _org; + private Bucket _bucket; + private string _token; + private InfluxDBClientOptions _options; + + [SetUp] + public new async Task SetUp() + { + _org = await FindMyOrg(); + _bucket = await Client.GetBucketsApi() + .CreateBucketAsync(GenerateName("fliers"), null, _org); + + // + // Add Permissions to read and write to the Bucket + // + var resource = new PermissionResource(PermissionResource.TypeBuckets, _bucket.Id, null, + _org.Id); + + var readBucket = new Permission(Permission.ActionEnum.Read, resource); + var writeBucket = new Permission(Permission.ActionEnum.Write, resource); + + var loggedUser = await Client.GetUsersApi().MeAsync(); + Assert.IsNotNull(loggedUser); + + var authorization = await Client.GetAuthorizationsApi() + .CreateAuthorizationAsync(_org, + new List { readBucket, writeBucket }); + + _token = authorization.Token; + + Client.Dispose(); + + _options = new InfluxDBClientOptions(InfluxDbUrl) + { + Token = _token, + Org = _org.Id, + Bucket = _bucket.Id + }; + + Client = new InfluxDBClient(_options); + } + + + [Test] + public void HandleEvents() + { + using (var writeApi = Client.GetWriteApi()) + { + writeApi.EventHandler += (sender, eventArgs) => + { + switch (eventArgs) + { + case WriteSuccessEvent successEvent: + Assert.Fail("Call should not succeed"); + break; + case WriteErrorEvent errorEvent: + Assert.AreEqual("unable to parse 'velocity,unit=C3PO mps=': missing field value", + errorEvent.Exception.Message); + var eventHeaders = errorEvent.GetHeaders(); + if (eventHeaders == null) + { + Assert.Fail("WriteErrorEvent must return headers."); + } + + var headers = new Dictionary { }; + foreach (var hp in eventHeaders) + { + Console.WriteLine("DEBUG {0}: {1}", hp.Name, hp.Value); + headers.Add(hp.Name, hp.Value); + } + + Assert.AreEqual(4, headers.Count); + Assert.AreEqual("OSS", headers["X-Influxdb-Build"]); + Assert.True(headers["X-Influxdb-Version"].StartsWith('v')); + Assert.AreEqual("invalid", headers["X-Platform-Error-Code"]); + Assert.AreNotEqual("missing", headers.GetValueOrDefault("Date", "missing")); + break; + case WriteRetriableErrorEvent retriableErrorEvent: + Assert.Fail("Call should not be retriable."); + break; + case WriteRuntimeExceptionEvent runtimeExceptionEvent: + Assert.Fail("Call should not result in runtime exception. {0}", runtimeExceptionEvent); + break; + } + }; + + writeApi.WriteRecord("velocity,unit=C3PO mps=", WritePrecision.S, _bucket.Name, _org.Name); + } + } + } +} \ No newline at end of file diff --git a/Client.Test/ItWriteApiAsyncTest.cs b/Client.Test/ItWriteApiAsyncTest.cs index 6cfb6323e..a9aba7798 100644 --- a/Client.Test/ItWriteApiAsyncTest.cs +++ b/Client.Test/ItWriteApiAsyncTest.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using InfluxDB.Client.Api.Domain; using InfluxDB.Client.Core; +using InfluxDB.Client.Core.Exceptions; using InfluxDB.Client.Core.Flux.Domain; using InfluxDB.Client.Core.Test; using InfluxDB.Client.Writes; @@ -185,5 +187,28 @@ public async Task WriteULongValues() Assert.AreEqual(ulong.MaxValue, query[0].Records[0].GetValue()); } + + [Test] + public async Task WriteWithError() + { + try + { + await _writeApi.WriteRecordAsync("h2o,location=fox_hollow water_level="); + Assert.Fail("Call should fail"); + } + catch (HttpException exception) + { + Assert.AreEqual("unable to parse 'h2o,location=fox_hollow water_level=': missing field value", + exception.Message); + Assert.AreEqual(400, exception.Status); + Assert.GreaterOrEqual(4, exception.Headers.Count()); + var headers = new Dictionary(); + foreach (var header in exception?.Headers) headers.Add(header.Name, header.Value); + Assert.AreEqual("OSS", headers["X-Influxdb-Build"]); + Assert.AreEqual("invalid", headers["X-Platform-Error-Code"]); + Assert.IsTrue(headers["X-Influxdb-Version"].StartsWith('v')); + Assert.NotNull(headers["Date"]); + } + } } } \ No newline at end of file diff --git a/Client.Test/ItWriteQueryApiTest.cs b/Client.Test/ItWriteQueryApiTest.cs index 56ec9e237..4f8d3bf67 100644 --- a/Client.Test/ItWriteQueryApiTest.cs +++ b/Client.Test/ItWriteQueryApiTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using InfluxDB.Client.Api.Domain; using InfluxDB.Client.Core; using InfluxDB.Client.Writes; @@ -944,5 +945,37 @@ public async Task GzipWithLargeAmountOfData() Assert.AreEqual(1000, tables.Count); Assert.AreEqual(1, tables[0].Records.Count); } + + [Test] + public async Task QueryAsyncEnumerableGzip() + { + Client.EnableGzip(); + Client.SetLogLevel(LogLevel.Body); + + await Client.GetWriteApiAsync().WriteMeasurementsAsync(new[] + { + new H20Measurement + { + Location = "angel_bay", Level = 2.927, Time = DateTime.UtcNow.Add(-TimeSpan.FromSeconds(10)) + }, + new H20Measurement + { + Location = "angel_bay", Level = 1.927, Time = DateTime.UtcNow.Add(-TimeSpan.FromSeconds(20)) + } + }); + + var query = $@"from(bucket: ""{_bucket.Name}"") + |> range(start: 0) + |> filter(fn: (r) => r[""location""] == ""angel_bay"") + |> pivot(rowKey:[""_time""], columnKey: [""_field""], valueColumn: ""_value"")"; + + var list = new List(); + await foreach (var item in _queryApi.QueryAsyncEnumerable(query).ConfigureAwait(false)) + list.Add(item); + + Assert.AreEqual(2, list.Count); + Assert.AreEqual(1.927, list[0].Level); + Assert.AreEqual(2.927, list[1].Level); + } } } \ No newline at end of file diff --git a/Client.Test/WriteApiTest.cs b/Client.Test/WriteApiTest.cs index ec2b3fc3a..83fe59a14 100644 --- a/Client.Test/WriteApiTest.cs +++ b/Client.Test/WriteApiTest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; +using Castle.Core.Smtp; using InfluxDB.Client.Api.Domain; using InfluxDB.Client.Core; using InfluxDB.Client.Core.Exceptions; @@ -380,6 +381,27 @@ public void RetryContainsMessage() StringAssert.Contains(message, writer.ToString()); } + [Test] + public void Created() + { + var listener = new EventListener(_writeApi); + + MockServer.Reset(); + MockServer + .Given(Request.Create().UsingPost()) + .RespondWith(CreateResponse( + "OK", + 201)); + + _writeApi.WriteRecord("h2o_feet,location=coyote_creek level\\ description=\"feet 1\",water_level=1.0 1", + WritePrecision.Ns, "b1", "org1"); + _writeApi.Flush(); + + var writeSuccessEvent = listener.Get(); + Assert.AreEqual("h2o_feet,location=coyote_creek level\\ description=\"feet 1\",water_level=1.0 1", + writeSuccessEvent.LineProtocol); + } + [Test] public void TwiceDispose() { @@ -506,6 +528,62 @@ public void WriteRuntimeException() Assert.AreEqual(0, MockServer.LogEntries.Count()); } + [Test] + public void WriteExceptionWithHeaders() + { + var localWriteApi = _client.GetWriteApi(new WriteOptions { RetryInterval = 1_000 }); + + var traceId = Guid.NewGuid().ToString(); + const string buildName = "TestBuild"; + const string version = "v99.9.9"; + + localWriteApi.EventHandler += (sender, eventArgs) => + { + switch (eventArgs) + { + case WriteErrorEvent errorEvent: + Assert.AreEqual("just a test", errorEvent.Exception.Message); + var errHeaders = errorEvent.GetHeaders(); + var headers = new Dictionary(); + foreach (var h in errHeaders) + headers.Add(h.Name, h.Value); + Assert.AreEqual(6, headers.Count); + Assert.AreEqual(traceId, headers["Trace-Id"]); + Assert.AreEqual(buildName, headers["X-Influxdb-Build"]); + Assert.AreEqual(version, headers["X-Influxdb-Version"]); + break; + default: + Assert.Fail("Expect only WriteErrorEvents but got {0}", eventArgs.GetType()); + break; + } + }; + MockServer + .Given(Request.Create().WithPath("/api/v2/write").UsingPost()) + .RespondWith( + CreateResponse("{ \"message\": \"just a test\", \"status-code\": \"Bad Request\"}") + .WithStatusCode(400) + .WithHeaders(new Dictionary() + { + { "Content-Type", "application/json" }, + { "Trace-Id", traceId }, + { "X-Influxdb-Build", buildName }, + { "X-Influxdb-Version", version } + }) + ); + + + var measurement = new SimpleModel + { + Time = new DateTime(2024, 09, 01, 6, 15, 00), + Device = "R2D2", + Value = 1976 + }; + + localWriteApi.WriteMeasurement(measurement, WritePrecision.S, "b1", "org1"); + + localWriteApi.Dispose(); + } + [Test] public void RequiredOrgBucketWriteApi() { diff --git a/Client/Client.csproj b/Client/Client.csproj index 14fab4a38..bda849b60 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -7,8 +7,8 @@ The reference client that allows query, write and management (bucket, organization, users) for the InfluxDB 2.x. influxdb-client-csharp Contributors InfluxDB.Client - 4.13.0 - + 4.19.0 + dev InfluxDB.Client influxdata;timeseries;flux;influxdb @@ -28,6 +28,10 @@ true + + true + + @@ -36,11 +40,11 @@ - - - - - + + + + + diff --git a/Client/InfluxDB.Client.Api/Client/ApiClient.cs b/Client/InfluxDB.Client.Api/Client/ApiClient.cs index 851093212..0ab8b2a8e 100644 --- a/Client/InfluxDB.Client.Api/Client/ApiClient.cs +++ b/Client/InfluxDB.Client.Api/Client/ApiClient.cs @@ -15,6 +15,7 @@ using System.Text.RegularExpressions; using System.IO; using System.Linq; +using System.Net.Http; using System.Runtime.Serialization; using System.Text; using System.Threading; @@ -115,9 +116,13 @@ internal RestRequest PrepareRequest( string path, Method method, List> queryParams, object postBody, Dictionary headerParams, Dictionary formParams, Dictionary fileParams, Dictionary pathParams, - string contentType) + string contentType, + HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) { - var request = new RestRequest(path, method); + var request = new RestRequest(path, method) + { + CompletionOption = httpCompletionOption + }; // add path parameter, if any foreach (var param in pathParams) diff --git a/Client/InfluxDB.Client.Api/Client/ApiResponse.cs b/Client/InfluxDB.Client.Api/Client/ApiResponse.cs index 0849b4b39..d2942306b 100644 --- a/Client/InfluxDB.Client.Api/Client/ApiResponse.cs +++ b/Client/InfluxDB.Client.Api/Client/ApiResponse.cs @@ -43,7 +43,7 @@ public class ApiResponse /// HTTP status code. /// HTTP headers. /// Data (parsed HTTP body) - public ApiResponse(int statusCode, IEnumerable<(string Name, object Value)> headers, T data) + public ApiResponse(int statusCode, IEnumerable<(string Name, string Value)> headers, T data) { StatusCode = statusCode; Headers = headers diff --git a/Client/InfluxDB.Client.Api/Service/QueryService.cs b/Client/InfluxDB.Client.Api/Service/QueryService.cs index 4bcbded02..0bd9401a4 100644 --- a/Client/InfluxDB.Client.Api/Service/QueryService.cs +++ b/Client/InfluxDB.Client.Api/Service/QueryService.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Net.Http; using System.Threading; using RestSharp; using InfluxDB.Client.Api.Client; @@ -1544,9 +1545,11 @@ public RestResponse PostQueryWithIRestResponse(string zapTraceSpan = null, strin /// Specifies the name of the organization executing the query. Takes either the ID or Name. If both `orgID` and `org` are specified, `org` takes precedence. (optional) /// Specifies the ID of the organization executing the query. If both `orgID` and `org` are specified, `org` takes precedence. (optional) /// Flux query or specification to execute (optional) + /// Specify http completion to enable fully stream queries for async. /// ApiResponse of string public RestRequest PostQueryWithRestRequest(string zapTraceSpan = null, string acceptEncoding = null, - string contentType = null, string org = null, string orgID = null, Query query = null) + string contentType = null, string org = null, string orgID = null, Query query = null, + HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) { var localVarPath = "/api/v2/query"; var localVarPathParams = new Dictionary(); @@ -1621,7 +1624,7 @@ public RestRequest PostQueryWithRestRequest(string zapTraceSpan = null, string a return Configuration.ApiClient.PrepareRequest(localVarPath, Method.Post, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarFormParams, localVarFileParams, - localVarPathParams, localVarHttpContentType); + localVarPathParams, localVarHttpContentType, httpCompletionOption); } /// diff --git a/Client/InfluxDBClient.cs b/Client/InfluxDBClient.cs index d990b15bc..6ab932297 100644 --- a/Client/InfluxDBClient.cs +++ b/Client/InfluxDBClient.cs @@ -359,8 +359,8 @@ public void Dispose() } catch (Exception e) { - Trace.WriteLine("The signout exception"); - Trace.WriteLine(e); + Trace.WriteLine("The signout exception", InfluxDBTraceFilter.CategoryInfluxError); + Trace.WriteLine(e, InfluxDBTraceFilter.CategoryInfluxError); } // diff --git a/Client/Internal/ApiClient.cs b/Client/Internal/ApiClient.cs index 5243d0107..a072a2e33 100644 --- a/Client/Internal/ApiClient.cs +++ b/Client/Internal/ApiClient.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using InfluxDB.Client.Core; using InfluxDB.Client.Core.Internal; using RestSharp; using RestSharp.Authenticators; @@ -133,8 +134,8 @@ private void InitToken() } catch (IOException e) { - Trace.WriteLine("Cannot retrieve the Session token!"); - Trace.WriteLine(e); + Trace.WriteLine("Cannot retrieve the Session token!", InfluxDBTraceFilter.CategoryInfluxError); + Trace.WriteLine(e, InfluxDBTraceFilter.CategoryInfluxError); return; } diff --git a/Client/Internal/MeasurementMapper.cs b/Client/Internal/MeasurementMapper.cs index 236ccc33b..4dbca6164 100644 --- a/Client/Internal/MeasurementMapper.cs +++ b/Client/Internal/MeasurementMapper.cs @@ -97,7 +97,8 @@ internal PointData ToPoint(TM measurement, WritePrecision precision) } else { - Trace.WriteLine($"{value} is not supported as Timestamp"); + Trace.WriteLine($"{value} is not supported as Timestamp", + InfluxDBTraceFilter.CategoryInfluxError); } } else diff --git a/Client/Internal/RetryAttempt.cs b/Client/Internal/RetryAttempt.cs index d274ee9da..0d7eb2415 100644 --- a/Client/Internal/RetryAttempt.cs +++ b/Client/Internal/RetryAttempt.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; +using InfluxDB.Client.Core; using InfluxDB.Client.Core.Exceptions; namespace InfluxDB.Client.Internal @@ -141,7 +142,8 @@ internal long GetRetryInterval() var retryInterval = (long)(rangeStart + (rangeStop - rangeStart) * _random.NextDouble()); Trace.WriteLine("The InfluxDB does not specify \"Retry-After\". " + - $"Use the default retryInterval: {retryInterval}"); + $"Use the default retryInterval: {retryInterval}" + , InfluxDBTraceFilter.CategoryInflux); return retryInterval; } diff --git a/Client/InvokableScriptsApi.cs b/Client/InvokableScriptsApi.cs index c26d1d373..9e603d29d 100644 --- a/Client/InvokableScriptsApi.cs +++ b/Client/InvokableScriptsApi.cs @@ -300,14 +300,13 @@ private Task InvokeScript(string scriptId, FluxCsvParser.IFluxResponseConsumer c cancellationToken); } - private Func, RestRequest> CreateRequest(string scriptId, + private RestRequest CreateRequest(string scriptId, Dictionary bindParams = default) { Arguments.CheckNonEmptyString(scriptId, nameof(scriptId)); - return advancedResponseWriter => _service - .PostScriptsIDInvokeWithRestRequest(scriptId, new ScriptInvocationParams(bindParams)) - .AddAdvancedResponseHandler(advancedResponseWriter); + return _service + .PostScriptsIDInvokeWithRestRequest(scriptId, new ScriptInvocationParams(bindParams)); } } } \ No newline at end of file diff --git a/Client/QueryApi.cs b/Client/QueryApi.cs index 361d10457..2946ff26b 100644 --- a/Client/QueryApi.cs +++ b/Client/QueryApi.cs @@ -783,17 +783,18 @@ private Task QueryAsync(Query query, FluxCsvParser.IFluxResponseConsumer consume cancellationToken); } - private Func, RestRequest> CreateRequest(Query query, - string org = null) + private RestRequest CreateRequest(Query query, string org = null) { Arguments.CheckNotNull(query, nameof(query)); var optionsOrg = org ?? _options.Org; Arguments.CheckNonEmptyString(optionsOrg, OrgArgumentValidation); - return advancedResponseWriter => _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query) - .AddAdvancedResponseHandler(advancedResponseWriter); + var postQueryWithRestRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); + + return postQueryWithRestRequest; } internal static Query CreateQuery(string query, Dialect dialect = null) diff --git a/Client/QueryApiSync.cs b/Client/QueryApiSync.cs index 3344c8a66..ce0baa26e 100644 --- a/Client/QueryApiSync.cs +++ b/Client/QueryApiSync.cs @@ -75,6 +75,10 @@ public interface IQueryApiSync /// /// The synchronous version of QueryApi. + /// + /// The client uses to send the request and parse responses to InfluxDB 2.0. + /// The `HttpClient` is supposed to use maximus size of the body to int.MaxValue. + /// If you want to query large data, use method. /// public class QueryApiSync : AbstractQueryClient, IQueryApiSync { @@ -136,14 +140,11 @@ public List QuerySync(Query query, string org = null, CancellationToken ca var consumer = new FluxResponseConsumerPoco(poco => { measurements.Add(poco); }, Mapper); - RestRequest QueryFn(Func advancedResponseWriter) - { - return _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query) - .AddAdvancedResponseHandler(advancedResponseWriter); - } + var restRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); - QuerySync(QueryFn, consumer, ErrorConsumer, EmptyAction, cancellationToken); + QuerySync(restRequest, consumer, ErrorConsumer, EmptyAction, cancellationToken); return measurements; } @@ -188,14 +189,11 @@ public List QuerySync(Query query, string org = null, CancellationTok var optionsOrg = org ?? _options.Org; Arguments.CheckNonEmptyString(optionsOrg, OrgArgumentValidation); - RestRequest QueryFn(Func advancedResponseWriter) - { - return _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query) - .AddAdvancedResponseHandler(advancedResponseWriter); - } + var restRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); - QuerySync(QueryFn, consumer, ErrorConsumer, EmptyAction, cancellationToken); + QuerySync(restRequest, consumer, ErrorConsumer, EmptyAction, cancellationToken); return consumer.Tables; } diff --git a/Client/README.md b/Client/README.md index 83e23b9b4..50ee698b7 100644 --- a/Client/README.md +++ b/Client/README.md @@ -769,6 +769,33 @@ namespace Examples } ``` +## Filter trace verbose + +You can filter out verbose messages from `InfluxDB.Client` by using TraceListener. + +```cs +using System; +using System.Diagnostics; +using InfluxDB.Client.Core; + +namespace Examples +{ + public static class MyProgram + { + public static void Main() + { + TraceListener ConsoleOutListener = new TextWriterTraceListener(Console.Out) + { + Filter = InfluxDBTraceFilter.SuppressInfluxVerbose(), + }; + Trace.Listeners.Add(ConsoleOutListener); + + // My code ... + } + } +} +```` + ## Management API The client has following management API: diff --git a/Client/UsersApi.cs b/Client/UsersApi.cs index bb4899a24..2b9aa2226 100644 --- a/Client/UsersApi.cs +++ b/Client/UsersApi.cs @@ -307,7 +307,7 @@ public async Task MeUpdatePasswordAsync(string oldPassword, string newPassword, var me = await MeAsync(cancellationToken).ConfigureAwait(false); if (me == null) { - Trace.WriteLine("User is not authenticated."); + Trace.WriteLine("User is not authenticated.", InfluxDBTraceFilter.CategoryInfluxError); return; } diff --git a/Client/WriteApi.cs b/Client/WriteApi.cs index 944e2cf81..bff60a8e1 100644 --- a/Client/WriteApi.cs +++ b/Client/WriteApi.cs @@ -300,14 +300,17 @@ protected internal WriteApi( switch (notification.Kind) { case NotificationKind.OnNext: - Trace.WriteLine($"The batch item: {notification} was processed successfully."); + Trace.WriteLine($"The batch item: {notification} was processed successfully." + , InfluxDBTraceFilter.CategoryInfluxWrite); break; case NotificationKind.OnError: Trace.WriteLine( - $"The batch item wasn't processed successfully because: {notification.Exception}"); + $"The batch item wasn't processed successfully because: {notification.Exception}" + , InfluxDBTraceFilter.CategoryInfluxWriteError); break; default: - Trace.WriteLine($"The batch item: {notification} was processed"); + Trace.WriteLine($"The batch item: {notification} was processed" + , InfluxDBTraceFilter.CategoryInfluxWrite); break; } }, @@ -315,12 +318,14 @@ protected internal WriteApi( { Publish(new WriteRuntimeExceptionEvent(exception)); _disposed = true; - Trace.WriteLine($"The unhandled exception occurs: {exception}"); + Trace.WriteLine($"The unhandled exception occurs: {exception}" + , InfluxDBTraceFilter.CategoryInfluxWriteError); }, () => { _disposed = true; - Trace.WriteLine("The WriteApi was disposed."); + Trace.WriteLine("The WriteApi was disposed." + , InfluxDBTraceFilter.CategoryInfluxWrite); }); } @@ -337,7 +342,7 @@ internal void ReleaseAndClose(int millis = 30000) { _unsubscribeDisposeCommand.Dispose(); // avoid duplicate call to dispose - Trace.WriteLine("Flushing batches before shutdown."); + Trace.WriteLine("Flushing batches before shutdown.", InfluxDBTraceFilter.CategoryInfluxWrite); if (!_subject.IsDisposed) { @@ -572,7 +577,8 @@ internal override string ToLineProtocol() { if (!_point.HasFields()) { - Trace.WriteLine($"The point: ${_point} doesn't contains any fields, skipping"); + Trace.WriteLine($"The point: ${_point} doesn't contains any fields, skipping", + InfluxDBTraceFilter.CategoryInfluxWrite); return null; } @@ -603,7 +609,8 @@ internal override string ToLineProtocol() var point = _converter.ConvertToPointData(_measurement, Options.Precision); if (!point.HasFields()) { - Trace.WriteLine($"The point: ${point} doesn't contains any fields, skipping"); + Trace.WriteLine($"The point: ${point} doesn't contains any fields, skipping", + InfluxDBTraceFilter.CategoryInfluxWrite); return null; } diff --git a/Client/WriteApiAsync.cs b/Client/WriteApiAsync.cs index b64249c0f..bd926637b 100644 --- a/Client/WriteApiAsync.cs +++ b/Client/WriteApiAsync.cs @@ -411,7 +411,8 @@ private Task WriteData(string org, string bucket, WritePrecision precision, IEnu var sb = ToLineProtocolBody(data); if (sb.Length == 0) { - Trace.WriteLine($"The writes: {data} doesn't contains any Line Protocol, skipping"); + Trace.WriteLine($"The writes: {data} doesn't contains any Line Protocol, skipping", + InfluxDBTraceFilter.CategoryInfluxWrite); return Task.CompletedTask; } diff --git a/Client/Writes/Events.cs b/Client/Writes/Events.cs index 3124a2b12..dd8b32227 100644 --- a/Client/Writes/Events.cs +++ b/Client/Writes/Events.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Core; +using InfluxDB.Client.Core.Exceptions; +using RestSharp; namespace InfluxDB.Client.Writes { @@ -18,7 +22,8 @@ public WriteSuccessEvent(string organization, string bucket, WritePrecision prec internal override void LogEvent() { - Trace.WriteLine("The data was successfully written to InfluxDB 2."); + Trace.WriteLine("The data was successfully written to InfluxDB 2.", + InfluxDBTraceFilter.CategoryInfluxWrite); } } @@ -40,6 +45,14 @@ internal override void LogEvent() { Trace.TraceError($"The error occurred during writing of data: {Exception.Message}"); } + + /// + /// Get headers from the nested exception. + /// + public IEnumerable GetHeaders() + { + return ((HttpException)Exception)?.Headers; + } } /// diff --git a/Examples/ExampleBlazor/ExampleBlazor.csproj b/Examples/ExampleBlazor/ExampleBlazor.csproj index f8359afea..53dbaa35f 100644 --- a/Examples/ExampleBlazor/ExampleBlazor.csproj +++ b/Examples/ExampleBlazor/ExampleBlazor.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable @@ -9,7 +9,7 @@ - + diff --git a/Examples/Examples.csproj b/Examples/Examples.csproj index 84bcc3c48..32dfbbd68 100644 --- a/Examples/Examples.csproj +++ b/Examples/Examples.csproj @@ -2,10 +2,10 @@ Exe - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 - 4.13.0 - + 4.19.0 + dev false ../Keys/Key.snk diff --git a/Examples/HttpErrorHandling.cs b/Examples/HttpErrorHandling.cs new file mode 100644 index 000000000..3c1f3fc80 --- /dev/null +++ b/Examples/HttpErrorHandling.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using InfluxDB.Client; +using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Core.Exceptions; +using InfluxDB.Client.Writes; +using RestSharp; + +namespace Examples +{ + public class HttpErrorHandling + { + private static InfluxDBClient _client; + private static List _lpRecords; + + private static void Setup() + { + _client = new InfluxDBClient("http://localhost:9999", + "my-user", "my-password"); + var nowMillis = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + _lpRecords = new List() + { + $"temperature,location=north value=42 {nowMillis}", + $"temperature,location=north value=17 {nowMillis - 10000}", + $"temperature,location=north value= {nowMillis - 20000}", // one flaky record + $"temperature,location=north value=5 {nowMillis - 30000}" + }; + } + + private static void TearDown() + { + _client.Dispose(); + } + + private static Dictionary Headers2Dictionary(IEnumerable headers) + { + var result = new Dictionary(); + foreach (var hp in headers) + result.Add(hp.Name, hp.Value); + return result; + } + + private static async Task WriteRecordsAsync() + { + Console.WriteLine("Write records async with one invalid record."); + + // + // Write Data + // + var writeApiAsync = _client.GetWriteApiAsync(); + + try + { + await writeApiAsync.WriteRecordsAsync(_lpRecords, WritePrecision.Ms, + "my-bucket", "my-org"); + } + catch (HttpException he) + { + Console.WriteLine(" WARNING write failed"); + var headersDix = Headers2Dictionary(he.Headers); + Console.WriteLine(" Caught Exception({0}) \"{1}\"", + he.GetType(), + he.Message); + Console.WriteLine(" Response Status: {0}", he.Status); + Console.WriteLine(" Headers:"); + foreach (var key in headersDix.Keys) + Console.WriteLine($" {key}: {headersDix[key]}"); + } + finally + { + Console.WriteLine(" ===================="); + } + } + + private static void WriteRecordsWithErrorEvent() + { + Console.WriteLine("Write records with error event."); + + var caughtError = false; + using (var writeApi = _client.GetWriteApi()) + { + writeApi.EventHandler += (sender, eventArgs) => + { + switch (eventArgs) + { + case WriteErrorEvent wee: + Console.WriteLine(" WARNING write failed"); + Console.WriteLine(" Received event WriteErrorEvent with:"); + Console.WriteLine(" Status: {0}", ((HttpException)wee.Exception).Status); + Console.WriteLine(" Exception: {0}", wee.Exception.GetType()); + Console.WriteLine(" Message: {0}", wee.Exception.Message); + Console.WriteLine(" Headers:"); + var headersDix = Headers2Dictionary(wee.GetHeaders()); + foreach (var key in headersDix.Keys) + Console.WriteLine($" {key}: {headersDix[key]}"); + caughtError = true; + break; + default: + throw new Exception("Should only receive WriteErrorEvent"); + } + }; + Console.WriteLine("Trying the records list"); + writeApi.WriteRecords(_lpRecords, WritePrecision.Ms, "my-bucket", "my-org"); + var slept = 0; + while (!caughtError && slept < 3001) + { + Thread.Sleep(1000); + slept += 1000; + } + + if (!caughtError) + { + Console.WriteLine("WARN, did not encounter expected error"); + } + + + // manually retry the bad record + Console.WriteLine("Manually retrying the bad record."); + writeApi.WriteRecord(_lpRecords[2], WritePrecision.Ms, "my-bucket", "my-org"); + caughtError = false; + slept = 0; + while (!caughtError && slept < 3001) + { + Thread.Sleep(1000); + slept += 1000; + } + + if (!caughtError) + { + Console.WriteLine("WARN, did not encounter expected error"); + } + } + } + + public static async Task Main() + { + Console.WriteLine("Main()"); + Setup(); + await WriteRecordsAsync(); + WriteRecordsWithErrorEvent(); + TearDown(); + } + } +} \ No newline at end of file diff --git a/Examples/README.md b/Examples/README.md index 6eca76b7a..e8681b3f4 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -2,6 +2,7 @@ ## Writes - [WriteEventHandlerExample.cs](WriteEventHandlerExample.cs) - How to use WriteAPI with batch options and how to handle events +- [HttpErrorHandling.cs](HttpErrorHandling.cs) - How to handle errors on write and retrieve response headers. ## Others - [InvokableScripts.cs](InvokableScripts.cs) - How to use Invokable scripts Cloud API to create custom endpoints that query data diff --git a/Examples/RunExamples.cs b/Examples/RunExamples.cs index 91d5e59f8..a40688819 100644 --- a/Examples/RunExamples.cs +++ b/Examples/RunExamples.cs @@ -72,6 +72,9 @@ public static async Task Main(string[] args) case "RecordRowExample": await RecordRowExample.Main(); break; + case "HttpErrorHandling": + await HttpErrorHandling.Main(); + break; } } else diff --git a/Scripts/ci-test.sh b/Scripts/ci-test.sh index ffc662d02..b6d258413 100755 --- a/Scripts/ci-test.sh +++ b/Scripts/ci-test.sh @@ -20,11 +20,11 @@ echo "$NET_TEST_VERSION" DEFAULT_NET_TARGET_VERSION="netstandard2.1" NET_TARGET_VERSION="${NET_TARGET_VERSION:-$DEFAULT_NET_TARGET_VERSION}" -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Test/Client.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Examples/Examples.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Test/Client.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Examples/Examples.csproj sed -i '/netstandard2.0;netstandard2.1<\/TargetFrameworks>/c\'"${NET_TARGET_VERSION}"'<\/TargetFramework>' Client.Core/Client.Core.csproj sed -i '/netstandard2.0;netstandard2.1<\/TargetFrameworks>/c\'"${NET_TARGET_VERSION}"'<\/TargetFramework>' Client/Client.csproj @@ -42,10 +42,13 @@ else TRX2JUNIT_VERSION="1.3.2" fi -if [[ "$NET_TEST_VERSION" = "netcoreapp6.0" || "$NET_TEST_VERSION" = "netcoreapp7.0" ]] +if [[ "$NET_TEST_VERSION" = "netcoreapp6.0" || "$NET_TEST_VERSION" = "netcoreapp7.0" || "$NET_TEST_VERSION" = "netcoreapp8.0" ]] +then + TRX2JUNIT_VERSION="2.1.0" +fi + +if [[ "$NET_TEST_VERSION" != "netcoreapp8.0" ]] then - TRX2JUNIT_VERSION="2.0.4" -else dotnet sln remove Examples/ExampleBlazor/ExampleBlazor.csproj fi diff --git a/Scripts/publish-site.sh b/Scripts/publish-site.sh index f2ddb6927..74231325e 100755 --- a/Scripts/publish-site.sh +++ b/Scripts/publish-site.sh @@ -20,13 +20,7 @@ set -ev SCRIPT_PATH="$( cd "$(dirname "$0")" || exit ; pwd -P )" cd "$SCRIPT_PATH"/.. -echo "# Install Git" -apt-get update \ - && apt-get install git --yes \ - echo "# Clone client and switch to branch for GH-Pages" -chown root:$USER ~/.ssh/config -chmod 644 ~/.ssh/config git clone git@github.com:influxdata/influxdb-client-csharp.git \ && cd influxdb-client-csharp \ && git switch -C gh-pages @@ -43,5 +37,5 @@ cp -R "${SCRIPT_PATH}"/../.circleci/ "$SCRIPT_PATH"/../influxdb-client-csharp/ echo "# Deploy site" cd "$SCRIPT_PATH"/../influxdb-client-csharp git add -f . -git -c commit.gpgsign=false commit -m "Pushed the latest Docs to GitHub pages [skip CI]" +git commit -m "Pushed the latest Docs to GitHub pages [skip CI]" git push -fq origin gh-pages