diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs index f0b690b..295467b 100644 --- a/src/Seq.Api/Client/SeqApiClient.cs +++ b/src/Seq.Api/Client/SeqApiClient.cs @@ -59,7 +59,7 @@ public sealed class SeqApiClient : IDisposable /// The base URL of the Seq server. /// An API key to use when making requests to the server, if required. /// Whether default credentials will be sent with HTTP requests; the default is true. - [Obsolete("Prefer `SeqApiClient(serverUrl, apiKey, handler => handler.UseDefaultCredentials = true)` instead."), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Prefer `SeqApiClient(serverUrl, apiKey, createHttpMessageHandler)` instead."), EditorBrowsable(EditorBrowsableState.Never)] public SeqApiClient(string serverUrl, string apiKey, bool useDefaultCredentials) : this(serverUrl, apiKey, handler => handler.UseDefaultCredentials = useDefaultCredentials) { @@ -72,25 +72,45 @@ public SeqApiClient(string serverUrl, string apiKey, bool useDefaultCredentials) /// An API key to use when making requests to the server, if required. /// An optional callback to configure the used when making HTTP requests /// to the Seq API. - public SeqApiClient(string serverUrl, string apiKey = null, Action configureHttpClientHandler = null) + [Obsolete("Prefer `SeqApiClient(serverUrl, apiKey, createHttpMessageHandler)` instead."), EditorBrowsable(EditorBrowsableState.Never)] + public SeqApiClient(string serverUrl, string apiKey, Action configureHttpClientHandler) + : this(serverUrl, apiKey, cookies => + { + var handler = new HttpClientHandler { CookieContainer = cookies }; + configureHttpClientHandler?.Invoke(handler); + return handler; + }) + { + } + + /// + /// Construct a . + /// + /// The base URL of the Seq server. + /// An API key to use when making requests to the server, if required. + /// An optional callback to construct the HTTP message handler used when making requests + /// to the Seq API. The callback receives a that is shared with WebSocket requests made by the client. + public SeqApiClient(string serverUrl, string apiKey = null, Func createHttpMessageHandler = null) { + // This is required for compatibility with the obsolete constructor, which we can remove sometime in 2024. + var httpMessageHandler = createHttpMessageHandler?.Invoke(_cookies) ?? +#if SOCKETS_HTTP_HANDLER + new SocketsHttpHandler { CookieContainer = _cookies }; +#else + new HttpClientHandler { CookieContainer = _cookies }; +#endif + ServerUrl = serverUrl ?? throw new ArgumentNullException(nameof(serverUrl)); if (!string.IsNullOrEmpty(apiKey)) _apiKey = apiKey; - - var handler = new HttpClientHandler - { - CookieContainer = _cookies - }; - - configureHttpClientHandler?.Invoke(handler); - + var baseAddress = serverUrl; if (!baseAddress.EndsWith("/")) baseAddress += "/"; - HttpClient = new HttpClient(handler) { BaseAddress = new Uri(baseAddress) }; + HttpClient = new HttpClient(httpMessageHandler); + HttpClient.BaseAddress = new Uri(baseAddress); HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV9MediaType)); if (_apiKey != null) @@ -251,8 +271,8 @@ public async Task PutAsync(ILinked entity, string link, TEntity content var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Put, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - reader.ReadToEnd(); + using var reader = new StreamReader(stream); + await reader.ReadToEndAsync(); } /// @@ -270,8 +290,8 @@ public async Task DeleteAsync(ILinked entity, string link, TEntity cont var linkUri = ResolveLink(entity, link, parameters); var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) }; var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - reader.ReadToEnd(); + using var reader = new StreamReader(stream); + await reader.ReadToEndAsync(); } /// @@ -345,8 +365,8 @@ async Task HttpGetStringAsync(string url, CancellationToken cancellation { var request = new HttpRequestMessage(HttpMethod.Get, url); var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false); - using (var reader = new StreamReader(stream)) - return await reader.ReadToEndAsync(); + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync().ConfigureAwait(false); } async Task HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) diff --git a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs index c5fed8b..7adfa5a 100644 --- a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs +++ b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs @@ -19,7 +19,7 @@ public class AlertOccurrencePart /// /// The time grouping that triggered the alert. /// - public DateTimeRange DetectedOverRange { get; set; } + public DateTimeRangePart DetectedOverRange { get; set; } /// /// The level of notifications sent for this instance. diff --git a/src/Seq.Api/Model/Alerting/AlertOccurrenceRangePart.cs b/src/Seq.Api/Model/Alerting/AlertOccurrenceRangePart.cs index bd2df84..7a9116c 100644 --- a/src/Seq.Api/Model/Alerting/AlertOccurrenceRangePart.cs +++ b/src/Seq.Api/Model/Alerting/AlertOccurrenceRangePart.cs @@ -11,6 +11,6 @@ public class AlertOccurrenceRangePart /// /// The time grouping that triggered the alert. /// - public DateTimeRange DetectedOverRange { get; set; } + public DateTimeRangePart DetectedOverRange { get; set; } } } diff --git a/src/Seq.Api/Model/Cluster/ClusterNodeEntity.cs b/src/Seq.Api/Model/Cluster/ClusterNodeEntity.cs index f8056e5..db17fa3 100644 --- a/src/Seq.Api/Model/Cluster/ClusterNodeEntity.cs +++ b/src/Seq.Api/Model/Cluster/ClusterNodeEntity.cs @@ -43,6 +43,21 @@ public class ClusterNodeEntity : Entity /// The time since the node's last completed sync operation. /// public double? MillisecondsSinceLastSync { get; set; } + + /// + /// The time since the follower's active sync was started. + /// + public double? MillisecondsSinceActiveSync { get; set; } + + /// + /// The total number of operations in the active sync. + /// + public int? TotalActiveOps { get; set; } + + /// + /// The remaining number of operations in the active sync. + /// + public int? RemainingActiveOps { get; set; } /// /// An informational description of the node's current state, or null if no additional diff --git a/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs b/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs index 3ca6df3..437f9dd 100644 --- a/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs +++ b/src/Seq.Api/Model/Diagnostics/Storage/StorageConsumptionPart.cs @@ -25,12 +25,12 @@ public class StorageConsumptionPart /// /// The range of timestamps covered by the result. /// - public DateTimeRange Range { get; set; } + public DateTimeRangePart Range { get; set; } /// /// The available range of timestamps. /// - public DateTimeRange FullRange { get; set; } + public DateTimeRangePart FullRange { get; set; } /// /// The duration of the timestamp interval covered by each result. diff --git a/src/Seq.Api/Model/Events/EventEntity.cs b/src/Seq.Api/Model/Events/EventEntity.cs index fb38bad..fa58cca 100644 --- a/src/Seq.Api/Model/Events/EventEntity.cs +++ b/src/Seq.Api/Model/Events/EventEntity.cs @@ -62,5 +62,24 @@ public class EventEntity : Entity /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string RenderedMessage { get; set; } + + /// + /// A trace id associated with the event, if any. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string TraceId { get; set; } + + /// + /// A span id associated with the event, if any. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string SpanId { get; set; } + + /// + /// A collection of properties describing the origin of the event, if any. These correspond to resource + /// attributes in the OpenTelemetry spec. + /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List Resource { get; set; } } } diff --git a/src/Seq.Api/Model/Shared/DateTimeRange.cs b/src/Seq.Api/Model/Shared/DateTimeRange.cs index 8790bdc..ee5a43f 100644 --- a/src/Seq.Api/Model/Shared/DateTimeRange.cs +++ b/src/Seq.Api/Model/Shared/DateTimeRange.cs @@ -5,7 +5,7 @@ namespace Seq.Api.Model.Shared /// /// A range represented by a start and end . /// - public readonly struct DateTimeRange + public readonly struct DateTimeRangePart { /// /// The (inclusive) start of the range. @@ -18,11 +18,11 @@ public readonly struct DateTimeRange public DateTime End { get; } /// - /// Construct a . + /// Construct a . /// /// The (inclusive) start of the range. /// The (exclusive) end of the range. - public DateTimeRange(DateTime start, DateTime end) + public DateTimeRangePart(DateTime start, DateTime end) { Start = start; End = end; diff --git a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs index ad98137..c6fbe1d 100644 --- a/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs +++ b/src/Seq.Api/ResourceGroups/ApiKeysResourceGroup.cs @@ -121,5 +121,17 @@ public async Task GetMeasurementTimeseriesAsync(ApiKe var parameters = new Dictionary{ ["id"] = entity.Id, ["measurement"] = measurement }; return await GroupGetAsync("Metric", parameters, cancellationToken); } + + /// + /// Retrieve a detailed metric for all API keys. + /// + /// A allowing the operation to be canceled. + /// The measurement to get. + /// + public async Task> GetAllMeasurementTimeseriesAsync(string measurement, CancellationToken cancellationToken = default) + { + var parameters = new Dictionary{ ["measurement"] = measurement }; + return await GroupGetAsync>("Metrics", parameters, cancellationToken); + } } } diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj index a43ff62..27c4c1a 100644 --- a/src/Seq.Api/Seq.Api.csproj +++ b/src/Seq.Api/Seq.Api.csproj @@ -1,7 +1,7 @@ Client library for the Seq HTTP API. - 2023.3.1 + 2023.1.2 Datalust;Contributors netstandard2.0;net6.0 true @@ -14,6 +14,10 @@ 9 + + $(DefineConstants);SOCKETS_HTTP_HANDLER + + diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs index 63703ce..35df2ce 100644 --- a/src/Seq.Api/SeqConnection.cs +++ b/src/Seq.Api/SeqConnection.cs @@ -41,7 +41,7 @@ public class SeqConnection : ILoadResourceGroup, IDisposable /// The base URL of the Seq server. /// An API key to use when making requests to the server, if required. /// Whether default credentials will be sent with HTTP requests; the default is true. - [Obsolete("Prefer `SeqConnection(serverUrl, apiKey, handler => handler.UseDefaultCredentials = true)` instead."), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Prefer `SeqConnection(serverUrl, apiKey, createHttpMessageHandler)` instead."), EditorBrowsable(EditorBrowsableState.Never)] public SeqConnection(string serverUrl, string apiKey, bool useDefaultCredentials) { if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); @@ -55,11 +55,25 @@ public SeqConnection(string serverUrl, string apiKey, bool useDefaultCredentials /// An API key to use when making requests to the server, if required. /// An optional callback to configure the used when making HTTP requests /// to the Seq API. - public SeqConnection(string serverUrl, string apiKey = null, Action configureHttpClientHandler = null) + [Obsolete("Prefer `SeqConnection(serverUrl, apiKey, createHttpMessageHandler)` instead."), EditorBrowsable(EditorBrowsableState.Never)] + public SeqConnection(string serverUrl, string apiKey, Action configureHttpClientHandler) { if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); Client = new SeqApiClient(serverUrl, apiKey, configureHttpClientHandler); } + + /// + /// Construct a . + /// + /// The base URL of the Seq server. + /// An API key to use when making requests to the server, if required. + /// An optional callback to construct the HTTP message handler used when making requests + /// to the Seq API. The callback receives a that is shared with WebSocket requests made by the client. + public SeqConnection(string serverUrl, string apiKey = null, Func createHttpMessageHandler = null) + { + if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); + Client = new SeqApiClient(serverUrl, apiKey, createHttpMessageHandler); + } /// ///