Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/Seq.Api/Client/SeqApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ public async Task<TResponse> PostAsync<TEntity, TResponse>(ILinked entity, strin
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}

// Callers are expected to derive 400 error information from the response stream. All other result status codes throw.
internal async Task<TResponse> TryPostAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping these internal until it's clear there's a general pattern worth exposing.

{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpTrySendAsync(request, cancellationToken).ConfigureAwait(false);
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}
/// <summary>
/// Issue a <c>POST</c> request accepting a serialized <typeparamref name="TEntity"/> and returning a string by following <paramref name="link"/> from <paramref name="entity"/>.
/// </summary>
Expand Down Expand Up @@ -452,8 +460,19 @@ async Task<string> HttpGetStringAsync(string url, CancellationToken cancellation
#endif
).ConfigureAwait(false);
}


// Throws on 5xx errors; callers are expected to derive 400 error information from the response stream.
async Task<Stream> HttpTrySendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return await HttpSendAsyncCore(request, throwOn400: false, cancellationToken);
}

async Task<Stream> HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return await HttpSendAsyncCore(request, throwOn400: true, cancellationToken);
}

async Task<Stream> HttpSendAsyncCore(HttpRequestMessage request, bool throwOn400, CancellationToken cancellationToken)
{
var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync(
Expand All @@ -462,7 +481,7 @@ async Task<Stream> HttpSendAsync(HttpRequestMessage request, CancellationToken c
#endif
).ConfigureAwait(false);

if (response.IsSuccessStatusCode)
if (response.IsSuccessStatusCode || (!throwOn400 && response.StatusCode == HttpStatusCode.BadRequest))
{
return stream;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Seq.Api/Model/Alerting/AlertActivityPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ public class AlertActivityPart
/// <summary>
/// The most recent occurrences of the alert that triggered notifications.
/// </summary>
public List<AlertOccurrencePart> RecentOccurrences { get; set; } = new List<AlertOccurrencePart>();
public List<AlertOccurrencePart> RecentOccurrences { get; set; } = new();

/// <summary>
/// Minimal metrics for the most recent occurrences of the alert that triggered notifications.
/// The metrics in this list are a superset of <see cref="RecentOccurrences"/>.
/// </summary>
public List<AlertOccurrenceRangePart> RecentOccurrenceRanges { get; set; } =
new List<AlertOccurrenceRangePart>();
new();

/// <summary>
/// The number of times this alert has been triggered since its creation.
Expand Down
14 changes: 12 additions & 2 deletions src/Seq.Api/Model/Alerting/AlertEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,21 @@ public class AlertEntity : Entity
/// </summary>
public bool IsDisabled { get; set; }

/// <summary>
/// The source of the data for the query.
/// </summary>
public DataSource DataSource { get; set; } = DataSource.Stream;

/// <summary>
/// Lateral joins applied to the data source.
/// </summary>
public List<JoinPart> Joins { get; set; } = [];

/// <summary>
/// An optional <see cref="SignalExpressionPart"/> limiting the data source that triggers the alert.
/// </summary>
public SignalExpressionPart SignalExpression { get; set; }

/// <summary>
/// An optional <c>where</c> clause limiting the data source that triggers the alert.
/// </summary>
Expand All @@ -75,7 +85,7 @@ public class AlertEntity : Entity
/// <summary>
/// The individual measurements that will be tested by the alert condition.
/// </summary>
public List<ColumnPart> Select { get; set; } = new List<ColumnPart>();
public List<ColumnPart> Select { get; set; } = [];

/// <summary>
/// The alert condition. This is a <c>having</c> clause over the grouped results
Expand Down
2 changes: 1 addition & 1 deletion src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ public class AlertOccurrencePart
/// <summary>
/// The <see cref="NotificationChannelPart">NotificationChannelParts</see> that were alerted.
/// </summary>
public List<AlertNotificationPart> Notifications { get; set; } = new List<AlertNotificationPart>();
public List<AlertNotificationPart> Notifications { get; set; } = new();
}
}
2 changes: 1 addition & 1 deletion src/Seq.Api/Model/Alerting/NotificationChannelPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ public class NotificationChannelPart
/// by the alert.
/// </summary>
public Dictionary<string, string> NotificationAppSettingOverrides { get; set; } =
new Dictionary<string, string>();
new();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ public class AppInstanceOutputMetricsPart
/// it being processed by the app.
/// </summary>
public int DispatchedEventsPerMinute { get; set; }

/// <summary>
/// The number of events per minute that failed to reach the app from Seq. Includes streamed events (if enabled),
/// manual invocations, and alert notifications. This value doesn't track events the app may have internally
/// failed to process.
/// </summary>
public int FailedEventsPerMinute { get; set; }
}
}
2 changes: 2 additions & 0 deletions src/Seq.Api/Model/Data/QueryResultPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class QueryResultPart
/// <summary>
/// The columns within the result set (at various levels of the hierarchy).
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string[] Columns { get; set; }

/// <summary>
Expand All @@ -43,6 +44,7 @@ public class QueryResultPart
/// <summary>
/// Metadata for the time grouping column.
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public ColumnMetadataPart TimeColumnMetadata { get; set; }

/// <summary>
Expand Down
26 changes: 18 additions & 8 deletions src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,37 @@ namespace Seq.Api.Model.Metrics;
public enum MetricAggregationPreference
{
/// <summary>
/// The <c>count()</c> aggregate function.
/// The total count of observed values.
/// </summary>
Count,

Total,
/// <summary>
/// The <c>sum()</c> aggregate function.
/// The sum of all observed values.
/// </summary>
Sum,

/// <summary>
/// The <c>min()</c> aggregate function.
/// The counts of values falling in each histogram bucket.
/// </summary>
BucketSum,

/// <summary>
/// The smallest observed value.
/// </summary>
Min,

/// <summary>
/// The <c>mean()</c> aggregate function.
/// The center observed value.
/// </summary>
Mean,

/// <summary>
/// The <c>max()</c> aggregate function.
/// The largest observed value.
/// </summary>
Max,

/// <summary>
/// The set of values greater than a percentage of all other observed values.
/// </summary>
Max
Percentiles
}
31 changes: 31 additions & 0 deletions src/Seq.Api/Model/Shared/JoinKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright © Datalust and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Seq.Api.Model.Shared;

/// <summary>
/// A type of relational join.
/// </summary>
public enum JoinKind
{
/// <summary>
/// The unknown join kind.
/// </summary>
Unknown,

/// <summary>
/// A join type that joins each row to the output of a table function evaluated in the context of the row.
/// </summary>
Lateral
}
38 changes: 38 additions & 0 deletions src/Seq.Api/Model/Shared/JoinPart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright © Datalust and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Seq.Api.Model.Shared;

#nullable enable

/// <summary>
/// The lateral cross join part of a from clause.
/// </summary>
public class JoinPart
{
/// <summary>
/// The type of relational join.
/// </summary>
public JoinKind Kind { get; set; }

/// <summary>
/// The set function call used in the lateral join.
/// </summary>
public string? SetFunctionCall { get; set; }

/// <summary>
/// The alias of the set function call.
/// </summary>
public string? Alias { get; set; }
}
7 changes: 7 additions & 0 deletions src/Seq.Api/ResourceGroups/ApiResourceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ protected async Task<TResponse> GroupPostAsync<TEntity, TResponse>(string link,
var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
return await Client.PostAsync<TEntity, TResponse>(group, link, content, parameters, cancellationToken).ConfigureAwait(false);
}

// Callers are expected to derive 400 error information from the response stream. All other result status codes throw.
internal async Task<TResponse> GroupTryPostAsync<TEntity, TResponse>(string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
return await Client.TryPostAsync<TEntity, TResponse>(group, link, content, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Update an entity.
Expand Down
34 changes: 33 additions & 1 deletion src/Seq.Api/ResourceGroups/DataResourceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ internal DataResourceGroup(ILoadResourceGroup connection)
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>.
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>. For non-throwing
/// syntax error reporting, see <see cref="TryQueryAsync"/>.
/// </summary>
/// <param name="query">The query to execute.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
Expand Down Expand Up @@ -61,6 +62,37 @@ public async Task<QueryResultPart> QueryAsync(
return await GroupPostAsync<EvaluationContextPart, QueryResultPart>("Query", body, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>. This method
/// differs from <see cref="QueryAsync"/> by returning a result object with error information instead of throwing
/// when the query syntax is invalid.
/// </summary>
/// <param name="query">The query to execute.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which events are included in the query result. The default is the current time.</param>
/// <param name="signal">A signal expression over which the query will be executed.</param>
/// <param name="unsavedSignal">A constructed signal that may not appear on the server, for example, a <see cref="SignalEntity"/> that has been
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.</param>
/// <param name="timeout">The query timeout; if not specified, the query will run until completion.</param>
/// <param name="variables">Values for any free variables that appear in <paramref name="query"/>.</param>
/// <param name="trace">Enable detailed (server-side) query tracing.</param>
/// <param name="cancellationToken">Token through which the operation can be cancelled.</param>
/// <returns>A structured result set or syntax/execution error information.</returns>
public async Task<QueryResultPart> TryQueryAsync(
string query,
DateTime? rangeStartUtc = null,
DateTime? rangeEndUtc = null,
SignalExpressionPart signal = null,
SignalEntity unsavedSignal = null,
TimeSpan? timeout = null,
Dictionary<string, object> variables = null,
bool trace = false,
CancellationToken cancellationToken = default)
{
MakeParameters(query, rangeStartUtc, rangeEndUtc, signal, unsavedSignal, timeout, variables, trace, out var body, out var parameters);
return await GroupTryPostAsync<EvaluationContextPart, QueryResultPart>("Query", body, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Execute an SQL query and retrieve the result set as a structured <see cref="QueryResultPart"/>.
/// </summary>
Expand Down
39 changes: 33 additions & 6 deletions src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ internal MetricsResourceGroup(ILoadResourceGroup connection)
/// <param name="filter">A strict Seq filter expression to match (text expressions must be in double quotes). To
/// convert a "fuzzy" filter into a strict one the way the Seq UI does, use <see cref="ExpressionsResourceGroup.ToStrictAsync"/>.</param>
/// <param name="count">The number of definitions to retrieve. If not specified will default to 30.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which events are included in the query result. The default is the current time.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include definitions in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which definitions are included in the query result. The default is the current time.</param>
/// <param name="timeout">The query timeout; if not specified, the query will run until completion.</param>
/// <param name="variables">Values for any free variables that appear in <paramref name="filter"/>.</param>
/// <param name="trace">Enable detailed (server-side) query tracing.</param>
Expand All @@ -61,13 +61,13 @@ public async Task<MetricSearchResultPart> SearchAsync(
}

/// <summary>
/// Retrieve information about the labels available for filtering samples matching a set of search criteria.
/// Retrieve information about the dimensions available for filtering samples matching a set of search criteria.
/// </summary>
/// <param name="count">The number of definitions to retrieve. If not specified will default to 30.</param>
/// <param name="count">The number of dimensions to retrieve. If not specified will default to 30.</param>
/// <param name="metric">Optionally, the name of a metric to limit dimension search to. By default, dimensions
/// for all metrics are returned.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include events in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which events are included in the query result. The default is the current time.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include dimensions in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which dimensions are included in the query result. The default is the current time.</param>
/// <param name="timeout">The query timeout; if not specified, the query will run until completion.</param>
/// <param name="trace">Enable detailed (server-side) query tracing.</param>
/// <param name="cancellationToken">Token through which the operation can be cancelled.</param>
Expand All @@ -88,6 +88,33 @@ public async Task<List<MetricDimensionPart>> ListDimensionsAsync(
return await GroupPostAsync<EvaluationContextPart, List<MetricDimensionPart>>("Dimensions", body, parameters, cancellationToken).ConfigureAwait(false);
}

/// <summary>
/// Retrieve the top <paramref name="count"/> values associated with the metric dimension <paramref name="accessor"/>.
/// </summary>
/// <param name="accessor">The dimension's accessor. This is generally a simple property path (<c>cluster.node.name</c>) but can
/// use explict root namespaces and indexer notation (<c>@Resource.cluster['node name']</c>) if necessary.</param>
/// <param name="count">The number of values to retrieve. If not specified will default to 30.</param>
/// <param name="rangeStartUtc">The earliest timestamp from which to include values in the query result.</param>
/// <param name="rangeEndUtc">The exclusive latest timestamp to which values are included in the query result. The default is the current time.</param>
/// <param name="timeout">The query timeout; if not specified, the query will run until completion.</param>
/// <param name="trace">Enable detailed (server-side) query tracing.</param>
/// <param name="cancellationToken">Token through which the operation can be cancelled.</param>
/// <returns>A structured result set.</returns>
public async Task<List<object>> ListDimensionValuesAsync(
string accessor,
int count = 30,
DateTime? rangeStartUtc = null,
DateTime? rangeEndUtc = null,
TimeSpan? timeout = null,
bool trace = false,
CancellationToken cancellationToken = default)
{
var parameters = MakeParameters(null, null, count, rangeStartUtc, rangeEndUtc, timeout, trace);
parameters.Add(nameof(accessor), accessor);
var body = new EvaluationContextPart();
return await GroupPostAsync<EvaluationContextPart, List<object>>("DimensionValues", body, parameters, cancellationToken).ConfigureAwait(false);
}

static Dictionary<string, object> MakeParameters(List<string> groups, string filter, int count, DateTime? rangeStartUtc, DateTime? rangeEndUtc, TimeSpan? timeout, bool trace)
{
var parameters = new Dictionary<string, object>
Expand Down
Loading