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 330d4b2cc..5683da693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,142 @@ +## 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 +1. [#528](https://github.com/influxdata/influxdb-client-csharp/pull/528): Add HttpClient as a part of InfluxDBClientOptions + +### Bug Fixes +1. [#555](https://github.com/influxdata/influxdb-client-csharp/pull/555): Chaining multiple conditions in LINQ queries + +### Dependencies +Update dependencies: + +#### Build: + - [#525](https://github.com/influxdata/influxdb-client-csharp/pull/525): `System.Reactive` to `6.0.0` + - [#549](https://github.com/influxdata/influxdb-client-csharp/pull/549): `Microsoft.Extensions.ObjectPool` to `7.0.9` + +#### Examples: + - [#556](https://github.com/influxdata/influxdb-client-csharp/pull/556): `Radzen.Blazor` to `4.14.1` + +#### Test: + - [#543](https://github.com/influxdata/influxdb-client-csharp/pull/543): `Microsoft.NET.Test.Sdk` to `17.6.3` + - [#524](https://github.com/influxdata/influxdb-client-csharp/pull/524): `coverlet.collector` to `6.0.0` + - [#550](https://github.com/influxdata/influxdb-client-csharp/pull/550): `WireMock.Net` to `1.5.32` + - [#529](https://github.com/influxdata/influxdb-client-csharp/pull/529): `NUnit3TestAdapter` to `4.5.0` + ## 4.12.0 [2023-04-28] ### Bug Fixes 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 7bd55e84f..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 f0caec251..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.12.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 4967b2e78..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 7b0f49d1f..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.12.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 b4091905f..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.Test/ItInfluxDBQueryableTest.cs b/Client.Linq.Test/ItInfluxDBQueryableTest.cs index 581b8f629..6129b1426 100644 --- a/Client.Linq.Test/ItInfluxDBQueryableTest.cs +++ b/Client.Linq.Test/ItInfluxDBQueryableTest.cs @@ -71,6 +71,27 @@ orderby s.Timestamp Assert.AreEqual(1, sensors.Count); } + [Test] + public void QueryExampleNestedOr() + { + // https://github.com/influxdata/influxdb-client-csharp/issues/481 + var query = (from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _client.GetQueryApiSync()) +// ReSharper disable All + where (((((s.SensorId == "id-1") || (s.SensorId == "id-no-5")) || (s.SensorId == "id-no-8")) || + (s.SensorId == "id-no-10")) || (s.SensorId == "id-no-12")) +// ReSharper restore All + where s.Value > 12 + where s.Timestamp > new DateTime(2019, 11, 16, 8, 20, 15, DateTimeKind.Utc) + where s.Timestamp < new DateTime(2021, 01, 10, 5, 10, 0, DateTimeKind.Utc) + select s) + .Take(2) + .Skip(2); + + var sensors = query.ToList(); + + Assert.AreEqual(1, sensors.Count); + } + [Test] public void QueryExampleCount() { diff --git a/Client.Linq/Client.Linq.csproj b/Client.Linq/Client.Linq.csproj index d9d8c5cfb..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.12.0 - + 4.19.0 + dev InfluxDB.Client.Linq influxdata;timeseries;flux;influxdb;linq @@ -28,6 +28,10 @@ true + + true + + diff --git a/Client.Linq/Internal/QueryAggregator.cs b/Client.Linq/Internal/QueryAggregator.cs index fb5286c68..5066559dd 100644 --- a/Client.Linq/Internal/QueryAggregator.cs +++ b/Client.Linq/Internal/QueryAggregator.cs @@ -129,12 +129,18 @@ internal void AddLimitTailOffset(string limitOffsetAssignment) internal void AddFilterByTags(string filter) { - _filterByTags.Add(filter); + if (!string.IsNullOrEmpty(filter)) + { + _filterByTags.Add(filter); + } } internal void AddFilterByFields(string filter) { - _filterByFields.Add(filter); + if (!string.IsNullOrEmpty(filter)) + { + _filterByFields.Add(filter); + } } internal void AddSubQueries(QueryAggregator aggregator) diff --git a/Client.Linq/Internal/QueryExpressionTreeVisitor.cs b/Client.Linq/Internal/QueryExpressionTreeVisitor.cs index f3a4a65ca..618287e02 100644 --- a/Client.Linq/Internal/QueryExpressionTreeVisitor.cs +++ b/Client.Linq/Internal/QueryExpressionTreeVisitor.cs @@ -331,62 +331,70 @@ internal static void NormalizeExpressions(List parts) var indexes = Enumerable.Range(0, parts.Count) .Where(i => parts[i] is BinaryOperator) .ToList(); + var eliminators = new List>(); foreach (var index in indexes) { // "( and )" if (index >= 1 && parts[index - 1] is LeftParenthesis && parts[index + 1] is RightParenthesis) { - parts.RemoveAt(index + 1); - parts.RemoveAt(index); - parts.RemoveAt(index - 1); - - NormalizeExpressions(parts); - return; + eliminators.Add(Tuple.Create(1, () => + { + parts.RemoveAt(index + 1); + parts.RemoveAt(index); + parts.RemoveAt(index - 1); + })); + continue; } // "( timestamp > )" if (index >= 2 && parts[index - 2] is LeftParenthesis && parts[index + 1] is RightParenthesis) { - parts.RemoveAt(index + 1); - parts.RemoveAt(index); - parts.RemoveAt(index - 1); - parts.RemoveAt(index - 2); - - NormalizeExpressions(parts); - return; + eliminators.Add(Tuple.Create(1, () => + { + parts.RemoveAt(index + 1); + parts.RemoveAt(index); + parts.RemoveAt(index - 1); + parts.RemoveAt(index - 2); + })); + continue; } // "( < timestamp )" if (index >= 1 && parts[index - 1] is LeftParenthesis && parts[index + 2] is RightParenthesis) { - parts.RemoveAt(index + 2); - parts.RemoveAt(index + 1); - parts.RemoveAt(index); - parts.RemoveAt(index - 1); - - NormalizeExpressions(parts); - return; + eliminators.Add(Tuple.Create(10, () => + { + parts.RemoveAt(index + 2); + parts.RemoveAt(index + 1); + parts.RemoveAt(index); + parts.RemoveAt(index - 1); + })); + continue; } // "( or (r["sensor_id"] != p4))" if (index >= 1 && parts[index - 1] is LeftParenthesis && parts[index + 1] is LeftParenthesis) { - parts.RemoveAt(index); - - NormalizeExpressions(parts); - return; + eliminators.Add(Tuple.Create(1, () => { parts.RemoveAt(index); })); + continue; } // "(r["sensor_id"] != p4) or )" if (index >= 1 && parts[index - 1] is RightParenthesis && parts[index + 1] is RightParenthesis) { - parts.RemoveAt(index); - - NormalizeExpressions(parts); - return; + eliminators.Add(Tuple.Create(1, () => { parts.RemoveAt(index); })); + continue; } } + + if (eliminators.Count > 0) + { + eliminators.Sort((e1, e2) => e2.Item1.CompareTo(e1.Item1)); // descending order + eliminators[0].Item2(); + NormalizeExpressions(parts); + return; + } } // Parenthesis diff --git a/Client.Test/ApiClientTest.cs b/Client.Test/ApiClientTest.cs index 4b4ffbcce..6f2e22176 100644 --- a/Client.Test/ApiClientTest.cs +++ b/Client.Test/ApiClientTest.cs @@ -1,9 +1,9 @@ -using System; -using System.Net; using InfluxDB.Client.Api.Client; using InfluxDB.Client.Core; using InfluxDB.Client.Core.Internal; using NUnit.Framework; +using System; +using System.Net; namespace InfluxDB.Client.Test { diff --git a/Client.Test/Client.Test.csproj b/Client.Test/Client.Test.csproj index dbb4c3e26..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/InfluxDbClientFactoryTest.cs b/Client.Test/InfluxDbClientFactoryTest.cs index 21fdbc5d5..3bb3dfcef 100644 --- a/Client.Test/InfluxDbClientFactoryTest.cs +++ b/Client.Test/InfluxDbClientFactoryTest.cs @@ -2,11 +2,14 @@ using System.Collections.Generic; using System.Configuration; using System.IO; +using System.Net; +using System.Net.Http; using System.Reflection; using System.Security.Cryptography.X509Certificates; using InfluxDB.Client.Api.Client; using InfluxDB.Client.Core; using InfluxDB.Client.Core.Exceptions; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; namespace InfluxDB.Client.Test @@ -546,6 +549,34 @@ public void CertificatesFactory() Assert.AreEqual(certificateCollection, apiClient.RestClientOptions.ClientCertificates); } + [Test] + public void InjectHttpClient() + { + var options = new InfluxDBClientOptions("http://localhost:8086") + { + Token = "my-token" + }; + + var services = new ServiceCollection(); + + services.AddHttpClient(); + services.AddTransient(p => + { + var httpClientFactory = p.GetService(); + options.HttpClient = httpClientFactory.CreateClient(); + return new InfluxDBClient(options); + }); + + var builder = services.BuildServiceProvider(); + + _client = builder.GetRequiredService(); + + var restClient = GetDeclaredField(_client.GetType(), _client, "_apiClient").RestClient; + + Assert.AreEqual(options.HttpClient, + GetDeclaredField(restClient.GetType(), restClient, "k__BackingField")); + } + private static T GetDeclaredField(IReflect type, object instance, string fieldName) { const BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic 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 9e4789e91..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.12.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/InfluxDBClientOptions.cs b/Client/InfluxDBClientOptions.cs index 539e2d986..2e090dd43 100644 --- a/Client/InfluxDBClientOptions.cs +++ b/Client/InfluxDBClientOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Configuration; using System.Net; +using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; @@ -34,6 +35,7 @@ public class InfluxDBClientOptions private bool _allowHttpRedirects; private bool _verifySsl; private X509CertificateCollection _clientCertificates; + private HttpClient _httpClient; /// /// Set the url to connect the InfluxDB. @@ -251,6 +253,15 @@ public void AddDefaultTags(Dictionary tags) } } + /// + /// Add a HttpClient as a part of InfluxDBClientOptions + /// + public HttpClient HttpClient + { + get => _httpClient; + set => _httpClient = value; + } + /// /// Create an instance of InfluxDBClientOptions. The url could be a connection string with various configurations. /// @@ -428,6 +439,11 @@ private InfluxDBClientOptions(Builder builder) { ClientCertificates = builder.CertificateCollection; } + + if (builder.HttpClient != null) + { + HttpClient = builder.HttpClient; + } } private static TimeSpan ToTimeout(string value) @@ -506,6 +522,7 @@ public sealed class Builder internal bool VerifySslCertificates = true; internal RemoteCertificateValidationCallback VerifySslCallback; internal X509CertificateCollection CertificateCollection; + internal HttpClient HttpClient; internal PointSettings PointSettings = new PointSettings(); @@ -828,6 +845,20 @@ public InfluxDBClientOptions Build() return new InfluxDBClientOptions(this); } + + /// + /// Configure HttpClient + /// + /// + /// + public Builder SetHttpClient(HttpClient httpClient) + { + Arguments.CheckNotNull(httpClient, nameof(httpClient)); + + HttpClient = httpClient; + + return this; + } } } } \ No newline at end of file diff --git a/Client/Internal/ApiClient.cs b/Client/Internal/ApiClient.cs index 8b9779a1b..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; @@ -60,7 +61,10 @@ public ApiClient(InfluxDBClientOptions options, LoggingHandler loggingHandler, G RestClientOptions.ClientCertificates.AddRange(options.ClientCertificates); } - RestClient = new RestClient(RestClientOptions); + RestClient = options.HttpClient == null + ? new RestClient(RestClientOptions) + : new RestClient(options.HttpClient, RestClientOptions); + Configuration = new Configuration { ApiClient = this, @@ -130,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 9afa5ece4..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 e09a016dd..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.12.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/README.md b/README.md index cf722a68b..2b802f567 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ [![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/influxdata/influxdb-client-csharp.svg)](https://github.com/influxdata/influxdb-client-csharp/pulls) [![Slack Status](https://img.shields.io/badge/slack-join_chat-white.svg?logo=slack&style=social)](https://www.influxdata.com/slack) -This repository contains the reference C# client for the InfluxDB 2.x. +This repository contains the C# client library for use with InfluxDB 2.x and Flux. InfluxDB 3.x users should instead use the lightweight [v3 client library](https://github.com/InfluxCommunity/influxdb3-csharp). InfluxDB 1.x users should use the [v1 client library](https://github.com/influxdata/influxdb-csharp). -#### Note: Use this client library with InfluxDB 2.x and InfluxDB 1.8+ ([see details](#influxdb-18-api-compatibility)). For connecting to InfluxDB 1.7 or earlier instances, use the [influxdb-csharp](https://github.com/influxdata/influxdb-csharp) client library. +For ease of migration and a consistent query and write experience, v2 users should consider using InfluxQL and the [v1 client library](https://github.com/influxdata/influxdb-csharp). - [Features](#features) - [Documentation](#documentation) 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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy