diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..6376137
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,39 @@
+# If this file is renamed, the incrementing run attempt number will be reset.
+
+name: CI
+
+on:
+ push:
+ branches: [ "dev", "main" ]
+ pull_request:
+ branches: [ "dev", "main" ]
+
+env:
+ CI_BUILD_NUMBER_BASE: ${{ github.run_number }}
+ CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+ build:
+
+ runs-on: ubuntu-24.04
+
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 10.0.x
+ - name: Compute build number
+ run: |
+ echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+200))" >> $GITHUB_ENV
+ - name: Build and Publish
+ env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: pwsh
+ run: |
+ ./Build.ps1
diff --git a/Build.ps1 b/Build.ps1
index 40b95c7..952dce0 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -1,69 +1,77 @@
-# This script originally (c) 2016 Serilog Contributors - license Apache 2.0
-
-echo "build: Build started"
+Write-Output "build: Build started"
Push-Location $PSScriptRoot
+Write-Output "build: Tool versions follow"
+
+dotnet --version
+dotnet --list-sdks
+
if(Test-Path .\artifacts) {
- echo "build: Cleaning .\artifacts"
- Remove-Item .\artifacts -Force -Recurse
+ Write-Output "build: Cleaning ./artifacts"
+ Remove-Item ./artifacts -Force -Recurse
}
& dotnet restore --no-cache
-if($LASTEXITCODE -ne 0) { exit 1 }
-$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
-$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
-$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]
+$dbp = [Xml] (Get-Content .\Directory.Build.props)
+$versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix
+
+Write-Output "build: Package version prefix is $versionPrefix"
+
+$branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH];
+$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER];
+$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"]
-echo "build: Version suffix is $suffix"
+Write-Output "build: Package version suffix is $suffix"
-foreach ($src in ls src/*) {
+foreach ($src in Get-ChildItem src/*) {
Push-Location $src
- echo "build: Packaging project in $src"
+ Write-Output "build: Packaging project in $src"
if ($suffix) {
- & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix
+ & dotnet pack -c Release -o ../../artifacts --version-suffix=$suffix
} else {
- & dotnet pack -c Release -o ..\..\artifacts
+ & dotnet pack -c Release -o ../../artifacts
}
- if($LASTEXITCODE -ne 0) { exit 1 }
+ if($LASTEXITCODE -ne 0) { throw "Packaging failed" }
Pop-Location
}
-foreach ($test in ls test/*.Tests) {
- Push-Location $test
-
- echo "build: Testing project in $test"
-
- & dotnet test -c Release
- if($LASTEXITCODE -ne 0) { exit 3 }
+Write-Output "build: Checking complete solution builds"
+& dotnet build
+if($LASTEXITCODE -ne 0) { throw "Solution build failed" }
- Pop-Location
-}
-foreach ($test in ls test/*.Benchmarks) {
+foreach ($test in Get-ChildItem test/*.Tests) {
Push-Location $test
- echo "build: Building performance test project in $test"
+ Write-Output "build: Testing project in $test"
- & dotnet build -c Release
- if($LASTEXITCODE -ne 0) { exit 2 }
+ & dotnet test -c Release
+ if($LASTEXITCODE -ne 0) { throw "Testing failed" }
Pop-Location
}
-foreach ($sample in ls example/*) {
- Push-Location $sample
+Pop-Location
- echo "build: Building sample project in $sample"
+if ($env:NUGET_API_KEY) {
+ # GitHub Actions will only supply this to branch builds and not PRs. We publish
+ # builds from any branch this action targets (i.e. main and dev).
+
+ Write-Output "build: Publishing NuGet packages"
+
+ foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) {
+ & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg"
+ if($LASTEXITCODE -ne 0) { throw "Publishing failed" }
+ }
- & dotnet build -c Release
- if($LASTEXITCODE -ne 0) { exit 2 }
+ if (!($suffix)) {
+ Write-Output "build: Creating release for version $versionPrefix"
- Pop-Location
+ iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)"
+ }
}
-
-Pop-Location
diff --git a/Directory.Build.props b/Directory.Build.props
index 2deb00d..c86a925 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,11 @@
-
-
- latest
-
-
\ No newline at end of file
+
+ latest
+ true
+ 2026.1.0
+ true
+ true
+ snupkg
+ NETSDK1138
+
+
diff --git a/README.md b/README.md
index acda6e5..60f29ae 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Seq HTTP API Client [](https://ci.appveyor.com/project/datalust/seq-api) [](https://nuget.org/packages/seq.api)
+# Seq HTTP API Client [](https://nuget.org/packages/seq.api)
This library includes:
diff --git a/seq-api.sln b/seq-api.sln
index 883a5c6..2005812 100644
--- a/seq-api.sln
+++ b/seq-api.sln
@@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{1C66
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{7DCED657-7E83-4E6F-AB14-5753E044304D}"
ProjectSection(SolutionItems) = preProject
- appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
LICENSE = LICENSE
README.md = README.md
diff --git a/src/Seq.Api/Client/SeqApiClient.cs b/src/Seq.Api/Client/SeqApiClient.cs
index fed86cb..14f2c70 100644
--- a/src/Seq.Api/Client/SeqApiClient.cs
+++ b/src/Seq.Api/Client/SeqApiClient.cs
@@ -41,7 +41,7 @@ public sealed class SeqApiClient : IDisposable
// Future versions of Seq may not completely support vN-1 features, however
// providing this as an Accept header will ensure what compatibility is available
// can be utilized.
- const string SeqApiVersionedMediaType = "application/vnd.datalust.seq.v13+json";
+ const string SeqApiVersionedMediaType = "application/vnd.datalust.seq.v14+json";
const string ApiKeyHeaderName = "X-Seq-ApiKey";
const string CsrfTokenHeaderName = "X-Seq-CsrfToken";
@@ -191,6 +191,14 @@ public async Task PostAsync(ILinked entity, strin
return _serializer.Deserialize(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 TryPostAsync(ILinked entity, string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
+ {
+ 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(new JsonTextReader(new StreamReader(stream)));
+ }
///
/// Issue a POST request accepting a serialized and returning a string by following from .
///
@@ -452,8 +460,19 @@ async Task 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 HttpTrySendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ {
+ return await HttpSendAsyncCore(request, throwOn400: false, cancellationToken);
+ }
+
async Task HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ {
+ return await HttpSendAsyncCore(request, throwOn400: true, cancellationToken);
+ }
+
+ async Task HttpSendAsyncCore(HttpRequestMessage request, bool throwOn400, CancellationToken cancellationToken)
{
var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync(
@@ -462,7 +481,7 @@ async Task HttpSendAsync(HttpRequestMessage request, CancellationToken c
#endif
).ConfigureAwait(false);
- if (response.IsSuccessStatusCode)
+ if (response.IsSuccessStatusCode || (!throwOn400 && response.StatusCode == HttpStatusCode.BadRequest))
{
return stream;
}
@@ -531,7 +550,7 @@ static string EnsureAbsoluteWebSocketUri(string target, Uri apiBaseAddress)
absoluteUnknownScheme.Scheme = absoluteUnknownScheme.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ||
absoluteUnknownScheme.Scheme.Equals("ws", StringComparison.OrdinalIgnoreCase) ?
"ws" : "wss";
-
+
return absoluteUnknownScheme.ToString();
}
diff --git a/src/Seq.Api/Model/Alerting/AlertActivityPart.cs b/src/Seq.Api/Model/Alerting/AlertActivityPart.cs
index 97f25f5..340e9f1 100644
--- a/src/Seq.Api/Model/Alerting/AlertActivityPart.cs
+++ b/src/Seq.Api/Model/Alerting/AlertActivityPart.cs
@@ -38,14 +38,14 @@ public class AlertActivityPart
///
/// The most recent occurrences of the alert that triggered notifications.
///
- public List RecentOccurrences { get; set; } = new List();
+ public List RecentOccurrences { get; set; } = new();
///
/// Minimal metrics for the most recent occurrences of the alert that triggered notifications.
/// The metrics in this list are a superset of .
///
public List RecentOccurrenceRanges { get; set; } =
- new List();
+ new();
///
/// The number of times this alert has been triggered since its creation.
diff --git a/src/Seq.Api/Model/Alerting/AlertEntity.cs b/src/Seq.Api/Model/Alerting/AlertEntity.cs
index ad8bab1..d39927d 100644
--- a/src/Seq.Api/Model/Alerting/AlertEntity.cs
+++ b/src/Seq.Api/Model/Alerting/AlertEntity.cs
@@ -51,11 +51,21 @@ public class AlertEntity : Entity
///
public bool IsDisabled { get; set; }
+ ///
+ /// The source of the data for the query.
+ ///
+ public DataSource DataSource { get; set; } = DataSource.Stream;
+
+ ///
+ /// Lateral joins applied to the data source.
+ ///
+ public List Joins { get; set; } = [];
+
///
/// An optional limiting the data source that triggers the alert.
///
public SignalExpressionPart SignalExpression { get; set; }
-
+
///
/// An optional where clause limiting the data source that triggers the alert.
///
@@ -75,7 +85,7 @@ public class AlertEntity : Entity
///
/// The individual measurements that will be tested by the alert condition.
///
- public List Select { get; set; } = new List();
+ public List Select { get; set; } = [];
///
/// The alert condition. This is a having clause over the grouped results
diff --git a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs
index 8104e3d..fd04f7a 100644
--- a/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs
+++ b/src/Seq.Api/Model/Alerting/AlertOccurrencePart.cs
@@ -28,6 +28,6 @@ public class AlertOccurrencePart
///
/// The NotificationChannelParts that were alerted.
///
- public List Notifications { get; set; } = new List();
+ public List Notifications { get; set; } = new();
}
}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs b/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs
index 37988dc..bd901a9 100644
--- a/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs
+++ b/src/Seq.Api/Model/Alerting/NotificationChannelPart.cs
@@ -58,6 +58,6 @@ public class NotificationChannelPart
/// by the alert.
///
public Dictionary NotificationAppSettingOverrides { get; set; } =
- new Dictionary();
+ new();
}
}
diff --git a/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs b/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs
index d0f36bb..4e1c114 100644
--- a/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs
+++ b/src/Seq.Api/Model/AppInstances/AppInstanceOutputMetricsPart.cs
@@ -11,5 +11,12 @@ public class AppInstanceOutputMetricsPart
/// it being processed by the app.
///
public int DispatchedEventsPerMinute { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public int FailedEventsPerMinute { get; set; }
}
}
diff --git a/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs b/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs
index 2f84f5b..08086b1 100644
--- a/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs
+++ b/src/Seq.Api/Model/Dashboarding/ChartQueryPart.cs
@@ -28,10 +28,20 @@ public class ChartQueryPart
///
public string Id { get; set; }
+ ///
+ /// The source of the data displayed by the chart.
+ ///
+ public DataSource DataSource { get; set; } = DataSource.Stream;
+
+ ///
+ /// Lateral joins applied to the data source.
+ ///
+ public List Joins { get; set; } = [];
+
///
/// Individual measurements included in the query. These are effectively projected columns.
///
- public List Measurements { get; set; } = new List();
+ public List Measurements { get; set; } = [];
///
/// An optional filtering where clause limiting the data that contributes to the chart.
@@ -46,12 +56,12 @@ public class ChartQueryPart
///
/// A series of expressions used to group data returned by the query.
///
- public List GroupBy { get; set; } = new List();
+ public List GroupBy { get; set; } = [];
///
/// How measurements included in the chart will be displayed.
///
- public MeasurementDisplayStylePart DisplayStyle { get; set; } = new MeasurementDisplayStylePart();
+ public MeasurementDisplayStylePart DisplayStyle { get; set; } = new();
///
/// A filter that limits which groups will be displayed on the chart. Not supported by all chart types.
@@ -61,7 +71,7 @@ public class ChartQueryPart
///
/// An ordering applied to the results of the query; not supported by all chart types.
///
- public List OrderBy { get; set; } = new List();
+ public List OrderBy { get; set; } = [];
///
/// The row limit used for the query. By default, a server-determined limit will be applied.
diff --git a/src/Seq.Api/Model/Data/QueryResultPart.cs b/src/Seq.Api/Model/Data/QueryResultPart.cs
index cbd14ea..0cbffa4 100644
--- a/src/Seq.Api/Model/Data/QueryResultPart.cs
+++ b/src/Seq.Api/Model/Data/QueryResultPart.cs
@@ -32,6 +32,7 @@ public class QueryResultPart
///
/// The columns within the result set (at various levels of the hierarchy).
///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string[] Columns { get; set; }
///
@@ -43,6 +44,7 @@ public class QueryResultPart
///
/// Metadata for the time grouping column.
///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public ColumnMetadataPart TimeColumnMetadata { get; set; }
///
diff --git a/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs
new file mode 100644
index 0000000..918ec4a
--- /dev/null
+++ b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStatisticsPart.cs
@@ -0,0 +1,45 @@
+// 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.Diagnostics.ColumnStore;
+
+///
+/// Describes a particular (column name, encoding) pair. Storage for each column is broken down by encoding because
+/// the summary information available for each encoding differs.
+///
+public class ColumnStatisticsPart
+{
+ ///
+ /// The name of the column. For most user-provided data, such as metrics or labels, the column name will
+ /// correspond to the metric or label name. Complex objects are decomposed into individual columns per sub-property.
+ /// Note that these names approximate the Seq query language syntax for referring to the column, but this is
+ /// inexact.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// A textual description of the native encoding scheme used for the column data.
+ ///
+ public string Encoding { get; set; }
+
+ ///
+ /// The number of rows in which the column has a defined value.
+ ///
+ public long Count { get; set; }
+
+ ///
+ /// The full on-disk size of the encoded column data, excluding framing details such as CRC blocks, padding, and
+ /// file layout overhead (these are usually trivial).
+ ///
+ public long SizeBytes { get; set; }
+}
diff --git a/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs
new file mode 100644
index 0000000..c40240a
--- /dev/null
+++ b/src/Seq.Api/Model/Diagnostics/ColumnStore/ColumnStoreStatisticsPart.cs
@@ -0,0 +1,28 @@
+// 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.
+
+using System.Collections.Generic;
+
+namespace Seq.Api.Model.Diagnostics.ColumnStore;
+
+///
+/// Provides information about the internal column store that drives Seq's metrics features.
+///
+public class ColumnStoreStatisticsPart
+{
+ ///
+ /// The columns stored in the column store.
+ ///
+ public List Columns { get; set; } = new();
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Events/EventEntity.cs b/src/Seq.Api/Model/Events/EventEntity.cs
index 5d8a7ab..fbfa96b 100644
--- a/src/Seq.Api/Model/Events/EventEntity.cs
+++ b/src/Seq.Api/Model/Events/EventEntity.cs
@@ -116,5 +116,11 @@ public class EventEntity : Entity
///
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TimeSpan? Elapsed { get; set; }
+
+ ///
+ /// If the event is a metric, the definitions of its metric samples.
+ ///
+ [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
+ public List Definitions { get; set; }
}
}
diff --git a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs
index bb0c4a6..bb9b284 100644
--- a/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs
+++ b/src/Seq.Api/Model/Inputs/ApiKeyEntity.cs
@@ -47,7 +47,7 @@ public class ApiKeyEntity : Entity
///
/// Settings that control how events are ingested through the API key.
///
- public InputSettingsPart InputSettings { get; set; } = new InputSettingsPart();
+ public InputSettingsPart InputSettings { get; set; } = new();
///
/// If true, the key is the built-in (tokenless) API key representing unauthenticated HTTP ingestion.
@@ -63,12 +63,12 @@ public class ApiKeyEntity : Entity
/// The s assigned to the API key. Note that, if the API key is owned by an individual user, permissions
/// not held by the user will be ignored by the server.
///
- public HashSet AssignedPermissions { get; set; } = new HashSet();
+ public HashSet AssignedPermissions { get; set; } = [];
///
/// Information about the ingestion activity using this API key.
///
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
- public InputMetricsPart InputMetrics { get; set; } = new InputMetricsPart();
+ public InputMetricsPart InputMetrics { get; set; } = new();
}
}
diff --git a/src/Seq.Api/Model/Inputs/InputSettingsPart.cs b/src/Seq.Api/Model/Inputs/InputSettingsPart.cs
index 3eae56d..c0159f3 100644
--- a/src/Seq.Api/Model/Inputs/InputSettingsPart.cs
+++ b/src/Seq.Api/Model/Inputs/InputSettingsPart.cs
@@ -27,12 +27,12 @@ public class InputSettingsPart
/// Properties that will be automatically added to all events ingested using the key. These will override any properties with
/// the same names already present on the event.
///
- public List AppliedProperties { get; set; } = new List();
+ public List AppliedProperties { get; set; } = [];
///
/// A filter that selects events to ingest. If null, all events received using the key will be ingested.
///
- public DescriptiveFilterPart Filter { get; set; } = new DescriptiveFilterPart();
+ public DescriptiveFilterPart Filter { get; set; } = new();
///
/// A minimum level at which events received using the key will be ingested. The level hierarchy understood by Seq is fuzzy
diff --git a/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs b/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs
new file mode 100644
index 0000000..2f5720c
--- /dev/null
+++ b/src/Seq.Api/Model/Metrics/DimensionFilterOperator.cs
@@ -0,0 +1,23 @@
+#nullable enable
+namespace Seq.Api.Model.Metrics;
+
+///
+/// A simplified representation of the expression encoded in a .
+///
+public enum DimensionFilterOperator
+{
+ ///
+ /// The filter includes samples with for the dimension.
+ ///
+ Include,
+
+ ///
+ /// The filter excludes samples with for the dimension.
+ ///
+ Exclude,
+
+ ///
+ /// The filter includes samples with any value for the dimension.
+ ///
+ Has,
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs b/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs
new file mode 100644
index 0000000..37e248f
--- /dev/null
+++ b/src/Seq.Api/Model/Metrics/DimensionFilterPart.cs
@@ -0,0 +1,24 @@
+#nullable enable
+namespace Seq.Api.Model.Metrics;
+
+///
+/// A filter (requirement) applied to a metric dimension.
+///
+public class DimensionFilterPart
+{
+ ///
+ /// The dimension. See .
+ ///
+ public required string Accessor { get; set; }
+
+ ///
+ /// How the filter applies to the dimension.
+ ///
+ public DimensionFilterOperator Operator { get; set; }
+
+ ///
+ /// When is or
+ /// , the value included or excluded by the filter. Ignored otherwise.
+ ///
+ public object? Value { get; set; }
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs b/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs
new file mode 100644
index 0000000..e077c02
--- /dev/null
+++ b/src/Seq.Api/Model/Metrics/MetricAggregationPreference.cs
@@ -0,0 +1,42 @@
+namespace Seq.Api.Model.Metrics;
+
+///
+/// The aggregate function(s) used when aggregating a metric by time interval for display.
+///
+public enum MetricAggregationPreference
+{
+ ///
+ /// The total count of observed values.
+ ///
+ Total,
+
+ ///
+ /// The sum of all observed values.
+ ///
+ Sum,
+
+ ///
+ /// The counts of values falling in each histogram bucket.
+ ///
+ BucketSum,
+
+ ///
+ /// The smallest observed value.
+ ///
+ Min,
+
+ ///
+ /// The center observed value.
+ ///
+ Mean,
+
+ ///
+ /// The largest observed value.
+ ///
+ Max,
+
+ ///
+ /// The set of values greater than a percentage of all other observed values.
+ ///
+ Percentiles
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs b/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs
new file mode 100644
index 0000000..ed31e44
--- /dev/null
+++ b/src/Seq.Api/Model/Metrics/MetricDimensionPart.cs
@@ -0,0 +1,43 @@
+// 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.
+
+using Newtonsoft.Json;
+
+namespace Seq.Api.Model.Metrics;
+
+///
+/// A description of a dimension (property/label, scope, or resource attribute) applied to metric samples.
+///
+public class MetricDimensionPart
+{
+ ///
+ /// The dimension's accessor path, including the namespace, and using indexer syntax to refer to path elements that
+ /// are not valid identifiers in the Seq query language. This member is guaranteed to contain a syntactically-valid
+ /// expression that can be used directly in queries without manipulation.
+ ///
+ public string Accessor { get; set; }
+
+ ///
+ /// The dimension's human-readable name, without namespace qualifiers such as @Resource, and potentially
+ /// including syntactically-invalid elements. Omitted if identical to the value of .
+ ///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string Name { get; set; }
+
+ ///
+ /// The dimension's namespace. Omitted if the namespace is the default @Properties.
+ ///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string Namespace { get; set; }
+}
diff --git a/src/Seq.Api/Model/Metrics/MetricPart.cs b/src/Seq.Api/Model/Metrics/MetricPart.cs
new file mode 100644
index 0000000..dda104a
--- /dev/null
+++ b/src/Seq.Api/Model/Metrics/MetricPart.cs
@@ -0,0 +1,75 @@
+// 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.
+
+#nullable enable
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace Seq.Api.Model.Metrics;
+
+///
+/// The definition of a metric present in the server's series metrics store.
+///
+///
+/// Because Seq's internal metric store uses a columnar, rather than timeseries, architecture, it does not impose a
+/// fixed notion of metric identity. Instead, metric identity can be flexibly defined in the context of a metrics search.
+/// A is the result of such a search, and carries the implied metric identity in its combination
+/// of , , , and fields.
+///
+public class MetricPart
+{
+ ///
+ /// The name of the metric, as a property accessor expression, using indexer syntax to refer to path elements that
+ /// are not valid identifiers in the Seq query language. This member is guaranteed to contain a syntactically-valid
+ /// expression that can be used directly in queries without manipulation.
+ ///
+ public required string Accessor { get; set; }
+
+ ///
+ /// The metric's human-readable name, without namespace qualifiers such as @Resource, and potentially
+ /// including syntactically-invalid elements. Omitted if identical to the value of .
+ ///
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
+ public string? Name { get; set; }
+
+ ///
+ /// The group within which this metric appears. The indexes into the array will correspond to the indexes of
+ /// the list of group key expressions provided when retrieving the definition. For example, if the definitions
+ /// are retrieved with ?group=@Resource.service.name,@Scope.name, then the contents of a returned group
+ /// key might resemble ["users_api", "AspNetCore.Server.Kestrel"].
+ ///
+ public List
NewUserShowDashboardIds,
+ ///
+ /// A comma-separated list of (shared) metrics view ids that will be included in any new user's
+ /// personal default workspace.
+ ///
+ NewUserShowViewIds,
+
///
/// If using OpenID Connect authentication, the URL of the authorization endpoint. For example,
/// https://example.com.
diff --git a/src/Seq.Api/Model/Shared/DataSource.cs b/src/Seq.Api/Model/Shared/DataSource.cs
new file mode 100644
index 0000000..e21dd66
--- /dev/null
+++ b/src/Seq.Api/Model/Shared/DataSource.cs
@@ -0,0 +1,33 @@
+// 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;
+
+///
+/// Identifies the storage objects present in Seq's underlying telemetry database.
+///
+public enum DataSource
+{
+ ///
+ /// The stream object, which stores log events and trace spans. The stream object is implemented
+ /// over row-oriented storage, optimized for general search and row-by-row retrieval.
+ ///
+ Stream,
+
+ ///
+ /// The series object, which stores metrics. The series object is implemented over columnar storage,
+ /// optimized for aggregate queries that each touch a small number of columns.
+ ///
+ Series,
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs b/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs
index 05761de..c4fd012 100644
--- a/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs
+++ b/src/Seq.Api/Model/Shared/DescriptiveFilterPart.cs
@@ -36,7 +36,7 @@ public class DescriptiveFilterPart
public string Filter { get; set; }
///
- /// The original ("fuzzy") text entered by the user into the filter bar when
+ /// Optionally, the original ("fuzzy") text entered by the user into the filter bar when
/// creating the filter. This may not be syntactically valid, e.g. it may be
/// interpreted as free text - hence while it's displayed in the UI and forms the
/// basis of user editing of the filter, the value is executed.
diff --git a/src/Seq.Api/Model/Shared/JoinKind.cs b/src/Seq.Api/Model/Shared/JoinKind.cs
new file mode 100644
index 0000000..59328d5
--- /dev/null
+++ b/src/Seq.Api/Model/Shared/JoinKind.cs
@@ -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;
+
+///
+/// A type of relational join.
+///
+public enum JoinKind
+{
+ ///
+ /// The unknown join kind.
+ ///
+ Unknown,
+
+ ///
+ /// A join type that joins each row to the output of a table function evaluated in the context of the row.
+ ///
+ Lateral
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Shared/JoinPart.cs b/src/Seq.Api/Model/Shared/JoinPart.cs
new file mode 100644
index 0000000..51a439c
--- /dev/null
+++ b/src/Seq.Api/Model/Shared/JoinPart.cs
@@ -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
+
+///
+/// The lateral cross join part of a from clause.
+///
+public class JoinPart
+{
+ ///
+ /// The type of relational join.
+ ///
+ public JoinKind Kind { get; set; }
+
+ ///
+ /// The set function call used in the lateral join.
+ ///
+ public string? SetFunctionCall { get; set; }
+
+ ///
+ /// The alias of the set function call.
+ ///
+ public string? Alias { get; set; }
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Model/Signals/SignalEntity.cs b/src/Seq.Api/Model/Signals/SignalEntity.cs
index 82fd86d..1b882a8 100644
--- a/src/Seq.Api/Model/Signals/SignalEntity.cs
+++ b/src/Seq.Api/Model/Signals/SignalEntity.cs
@@ -31,12 +31,12 @@ public class SignalEntity : Entity
public SignalEntity()
{
Title = "New Signal";
- Filters = new List();
- Columns = new List();
+ Filters = [];
+ Columns = [];
}
///
- /// The friendly, human readable title of the signal.
+ /// The friendly, human-readable title of the signal.
///
public string Title { get; set; }
@@ -46,7 +46,7 @@ public SignalEntity()
public string Description { get; set; }
///
- /// Filters that are combined (using the and operator) to identify events matching the filter.
+ /// Filters that are combined (using the and operator) to identify events present in the signal.
///
public List Filters { get; set; }
diff --git a/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs
index e3c635f..ab4940a 100644
--- a/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs
+++ b/src/Seq.Api/Model/Workspaces/WorkspaceContentPart.cs
@@ -14,8 +14,9 @@
using System.Collections.Generic;
using Seq.Api.Model.Dashboarding;
+using Seq.Api.Model.Metrics;
using Seq.Api.Model.Signals;
-using Seq.Api.Model.SqlQueries;
+using Seq.Api.Model.Queries;
namespace Seq.Api.Model.Workspaces
{
@@ -27,16 +28,21 @@ public class WorkspaceContentPart
///
/// A list of ids to include in the workspace.
///
- public List SignalIds { get; set; } = new List();
+ public List SignalIds { get; set; } = [];
///
- /// A list of ids to include in the workspace.
+ /// A list of ids to include in the workspace.
///
- public List QueryIds { get; set; } = new List();
+ public List QueryIds { get; set; } = [];
///
/// A list of ids to include in the workspace.
///
- public List DashboardIds { get; set; } = new List();
+ public List DashboardIds { get; set; } = [];
+
+ ///
+ /// A list of ids to include in the workspace.
+ ///
+ public List ViewIds { get; set; } = [];
}
}
\ No newline at end of file
diff --git a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs
index 4347f64..48da13d 100644
--- a/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/ApiResourceGroup.cs
@@ -159,6 +159,13 @@ protected async Task GroupPostAsync(string link,
var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
return await Client.PostAsync(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 GroupTryPostAsync(string link, TEntity content, IDictionary parameters = null, CancellationToken cancellationToken = default)
+ {
+ var group = await LoadGroupAsync(cancellationToken).ConfigureAwait(false);
+ return await Client.TryPostAsync(group, link, content, parameters, cancellationToken).ConfigureAwait(false);
+ }
///
/// Update an entity.
diff --git a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs
index 18d71be..ed9c6ce 100644
--- a/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/DashboardsResourceGroup.cs
@@ -36,12 +36,13 @@ internal DashboardsResourceGroup(ILoadResourceGroup connection)
/// Retrieve the dashboard with the given id; throws if the entity does not exist.
///
/// The id of the dashboard.
+ /// If true, include only partial details in the result.
/// A allowing the operation to be canceled.
/// The dashboard.
- public async Task FindAsync(string id, CancellationToken cancellationToken = default)
+ public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default)
{
if (id == null) throw new ArgumentNullException(nameof(id));
- return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false);
+ return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false);
}
///
@@ -50,11 +51,12 @@ public async Task FindAsync(string id, CancellationToken cancel
/// If the id of a is provided, only dashboards owned by them will be included in the result; if
/// not specified, personal dashboards for all owners will be listed.
/// If true, shared dashboards will be included in the result.
+ /// If true, include only partial details in the result.
/// allowing the operation to be canceled.
/// A list containing matching dashboards.
- public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default)
+ public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default)
{
- var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } };
+ var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } };
return await GroupListAsync("Items", parameters, cancellationToken).ConfigureAwait(false);
}
diff --git a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs
index df10b64..5a9d0e3 100644
--- a/src/Seq.Api/ResourceGroups/DataResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/DataResourceGroup.cs
@@ -33,7 +33,8 @@ internal DataResourceGroup(ILoadResourceGroup connection)
}
///
- /// Execute an SQL query and retrieve the result set as a structured .
+ /// Execute an SQL query and retrieve the result set as a structured . For non-throwing
+ /// syntax error reporting, see .
///
/// The query to execute.
/// The earliest timestamp from which to include events in the query result.
@@ -61,6 +62,37 @@ public async Task QueryAsync(
return await GroupPostAsync("Query", body, parameters, cancellationToken).ConfigureAwait(false);
}
+ ///
+ /// Execute an SQL query and retrieve the result set as a structured . This method
+ /// differs from by returning a result object with error information instead of throwing
+ /// when the query syntax is invalid.
+ ///
+ /// The query to execute.
+ /// The earliest timestamp from which to include events in the query result.
+ /// The exclusive latest timestamp to which events are included in the query result. The default is the current time.
+ /// A signal expression over which the query will be executed.
+ /// A constructed signal that may not appear on the server, for example, a that has been
+ /// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
+ /// The query timeout; if not specified, the query will run until completion.
+ /// Values for any free variables that appear in .
+ /// Enable detailed (server-side) query tracing.
+ /// Token through which the operation can be cancelled.
+ /// A structured result set or syntax/execution error information.
+ public async Task TryQueryAsync(
+ string query,
+ DateTime? rangeStartUtc = null,
+ DateTime? rangeEndUtc = null,
+ SignalExpressionPart signal = null,
+ SignalEntity unsavedSignal = null,
+ TimeSpan? timeout = null,
+ Dictionary 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("Query", body, parameters, cancellationToken).ConfigureAwait(false);
+ }
+
///
/// Execute an SQL query and retrieve the result set as a structured .
///
diff --git a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs
index 514e552..31ee28e 100644
--- a/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/EventsResourceGroup.cs
@@ -70,7 +70,7 @@ public async Task FindAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// The number of events to retrieve. If not specified will default to 30.
/// An event id from which to start searching (inclusively).
/// An event id to search after (exclusively).
@@ -141,7 +141,7 @@ public async IAsyncEnumerable EnumerateAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// The number of events to retrieve. If not specified will default to 30.
/// An event id from which to start searching (inclusively).
/// An event id to search after (exclusively).
@@ -220,7 +220,7 @@ public async IAsyncEnumerable PagedEnumerateAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// The number of events to retrieve. If not specified will default to 30.
/// An event id from which to start searching (inclusively).
/// An event id to search after (exclusively).
@@ -267,7 +267,7 @@ public async Task> ListAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// The number of events to retrieve. If not specified will default to 30.
/// An event id from which to start searching (inclusively).
/// An event id to search after (exclusively).
@@ -327,7 +327,7 @@ public async Task PageAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// Earliest (inclusive) date/time from which to delete.
/// Latest (exclusive) date/time from which to delete.
/// Values for any free variables that appear in .
@@ -359,7 +359,7 @@ public async Task DeleteAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// If specified, the event's message template and properties will be rendered into its property.
/// Values for any free variables that appear in .
/// Token through which the operation can be cancelled.
@@ -405,7 +405,7 @@ public async IAsyncEnumerable StreamAsync(
/// created but not saved, a signal from another server, or the modified representation of an entity already persisted.
/// If provided, a signal expression describing the set of events that will be filtered for the result.
/// 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 connection.Expressions.ToStrictAsync().
+ /// convert a "fuzzy" filter into a strict one the way the Seq UI does, use .
/// If specified, the event's message template and properties will be rendered into its property.
/// If specified, compact JSON format will be requested instead of the default format.
/// Values for any free variables that appear in .
diff --git a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs
index 7474a74..16ad9f2 100644
--- a/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/ExpressionsResourceGroup.cs
@@ -17,46 +17,50 @@
using System.Threading.Tasks;
using Seq.Api.Model.Expressions;
-namespace Seq.Api.ResourceGroups
+namespace Seq.Api.ResourceGroups;
+
+///
+/// Perform operations on queries and filter expressions.
+///
+public class ExpressionsResourceGroup : ApiResourceGroup
{
+ internal ExpressionsResourceGroup(ILoadResourceGroup connection)
+ : base("Expressions", connection)
+ {
+ }
+
///
- /// Perform operations on queries and filter expressions.
+ /// Convert an expression in the relaxed syntax supported by the filter bar, to the strictly-valid
+ /// syntax required by API interactions.
///
- public class ExpressionsResourceGroup : ApiResourceGroup
+ /// The (potentially) relaxed-syntax expression.
+ /// A allowing the operation to be canceled.
+ /// The expression in a strictly-valid form.
+ public Task ToStrictAsync(string fuzzy, CancellationToken cancellationToken = default)
{
- internal ExpressionsResourceGroup(ILoadResourceGroup connection)
- : base("Expressions", connection)
- {
- }
-
- ///
- /// Convert an expression in the relaxed syntax supported by the filter bar, to the strictly-valid
- /// syntax required by API interactions.
- ///
- /// The (potentially) relaxed-syntax expression.
- /// A allowing the operation to be canceled.
- /// The expression in a strictly-valid form.
- public Task ToStrictAsync(string fuzzy, CancellationToken cancellationToken = default)
+ var parameters = new Dictionary
{
- return GroupGetAsync("ToStrict", new Dictionary
- {
- {"fuzzy", fuzzy}
- }, cancellationToken);
- }
+ [nameof(fuzzy)] = fuzzy
+ };
+ return GroupGetAsync("ToStrict", parameters, cancellationToken);
+ }
- ///
- /// Convert an expression in the relaxed syntax supported by the filter bar, to the strict and limited
- /// syntax required within SQL queries.
- ///
- /// The (potentially) relaxed-syntax expression.
- /// A allowing the operation to be canceled.
- /// The expression in a form that can be used within SQL queries.
- public Task ToSqlAsync(string fuzzy, CancellationToken cancellationToken = default)
+ ///
+ /// Convert an expression in the relaxed syntax supported by the filter bar, to the strict and limited
+ /// syntax required within SQL queries.
+ ///
+ /// The (potentially) relaxed-syntax expression.
+ /// The name of the storage object that the expression will be evaluated against; accepted
+ /// values are stream and series. This influences the processing of "free text" expressions.
+ /// A allowing the operation to be canceled.
+ /// The expression in a form that can be used within SQL queries.
+ public Task ToSqlAsync(string fuzzy, string schema = "stream", CancellationToken cancellationToken = default)
+ {
+ var parameters = new Dictionary
{
- return GroupGetAsync("ToSql", new Dictionary
- {
- {"fuzzy", fuzzy}
- }, cancellationToken);
- }
+ [nameof(fuzzy)] = fuzzy,
+ [nameof(schema)] = schema
+ };
+ return GroupGetAsync("ToSql", parameters, cancellationToken);
}
-}
\ No newline at end of file
+}
diff --git a/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs b/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs
new file mode 100644
index 0000000..29c5df5
--- /dev/null
+++ b/src/Seq.Api/ResourceGroups/MetricsResourceGroup.cs
@@ -0,0 +1,144 @@
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Seq.Api.Model.Metrics;
+using Seq.Api.Model.Shared;
+namespace Seq.Api.ResourceGroups;
+
+///
+/// Retrieve information about the metrics stored in this Seq instance.
+///
+public class MetricsResourceGroup : ApiResourceGroup
+{
+ internal MetricsResourceGroup(ILoadResourceGroup connection)
+ : base("Metrics", connection)
+ {
+ }
+
+ ///
+ /// Retrieve metric definitions for samples matching a set of search criteria.
+ ///
+ /// Expressions naming any properties that determine the uniqueness/identity of metric definitions.
+ /// 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 .
+ /// The number of definitions to retrieve. If not specified will default to 30.
+ /// The earliest timestamp from which to include definitions in the query result.
+ /// The exclusive latest timestamp to which definitions are included in the query result. The default is the current time.
+ /// The query timeout; if not specified, the query will run until completion.
+ /// Values for any free variables that appear in .
+ /// Enable detailed (server-side) query tracing.
+ /// Token through which the operation can be cancelled.
+ /// A structured result set.
+ public async Task SearchAsync(
+ List groups = null,
+ string filter = null,
+ int count = 30,
+ DateTime? rangeStartUtc = null,
+ DateTime? rangeEndUtc = null,
+ TimeSpan? timeout = null,
+ Dictionary variables = null,
+ bool trace = false,
+ CancellationToken cancellationToken = default)
+ {
+ var parameters = MakeParameters(groups, filter, count, rangeStartUtc, rangeEndUtc, timeout, trace);
+ var body = new EvaluationContextPart { Variables = variables };
+ return await GroupPostAsync("Search", body, parameters, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Retrieve information about the dimensions available for filtering samples matching a set of search criteria.
+ ///
+ /// The number of dimensions to retrieve. If not specified will default to 30.
+ /// Optionally, the name of a metric to limit dimension search to. By default, dimensions
+ /// for all metrics are returned.
+ /// The earliest timestamp from which to include dimensions in the query result.
+ /// The exclusive latest timestamp to which dimensions are included in the query result. The default is the current time.
+ /// The query timeout; if not specified, the query will run until completion.
+ /// Enable detailed (server-side) query tracing.
+ /// Token through which the operation can be cancelled.
+ /// A structured result set.
+ public async Task> ListDimensionsAsync(
+ int count = 30,
+ string metric = null,
+ 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);
+ if (metric != null)
+ parameters.Add(nameof(metric), metric);
+ var body = new EvaluationContextPart();
+ return await GroupPostAsync>("Dimensions", body, parameters, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Retrieve the top values associated with the metric dimension .
+ ///
+ /// The dimension's accessor. This is generally a simple property path (cluster.node.name) but can
+ /// use explict root namespaces and indexer notation (@Resource.cluster['node name']) if necessary.
+ /// The number of values to retrieve. If not specified will default to 30.
+ /// The earliest timestamp from which to include values in the query result.
+ /// The exclusive latest timestamp to which values are included in the query result. The default is the current time.
+ /// The query timeout; if not specified, the query will run until completion.
+ /// Enable detailed (server-side) query tracing.
+ /// Token through which the operation can be cancelled.
+ /// A structured result set.
+ public async Task> 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>("DimensionValues", body, parameters, cancellationToken).ConfigureAwait(false);
+ }
+
+ static Dictionary MakeParameters(List groups, string filter, int count, DateTime? rangeStartUtc, DateTime? rangeEndUtc, TimeSpan? timeout, bool trace)
+ {
+ var parameters = new Dictionary
+ {
+ [nameof(count)] = count
+ };
+
+ if (groups?.Count > 0)
+ parameters.Add(nameof(groups), string.Join(",", groups));
+
+ if (!string.IsNullOrWhiteSpace(filter))
+ parameters.Add(nameof(filter), filter);
+
+ if (rangeStartUtc != null)
+ parameters.Add(nameof(rangeStartUtc), rangeStartUtc);
+
+ if (rangeEndUtc != null)
+ parameters.Add(nameof(rangeEndUtc), rangeEndUtc.Value);
+
+ if (timeout != null)
+ parameters.Add("timeoutMS", timeout.Value.TotalMilliseconds.ToString("0"));
+
+ if (trace)
+ parameters.Add(nameof(trace), true);
+ return parameters;
+ }
+}
diff --git a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs b/src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs
similarity index 71%
rename from src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs
rename to src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs
index 83414ed..671fab2 100644
--- a/src/Seq.Api/ResourceGroups/SqlQueriesResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/QueriesResourceGroup.cs
@@ -17,17 +17,17 @@
using System.Threading;
using System.Threading.Tasks;
using Seq.Api.Model;
-using Seq.Api.Model.SqlQueries;
+using Seq.Api.Model.Queries;
using Seq.Api.Model.Users;
namespace Seq.Api.ResourceGroups
{
///
- /// Perform operations on saved SQL queries.
+ /// Perform operations on saved queries.
///
- public class SqlQueriesResourceGroup : ApiResourceGroup
+ public class QueriesResourceGroup : ApiResourceGroup
{
- internal SqlQueriesResourceGroup(ILoadResourceGroup connection)
+ internal QueriesResourceGroup(ILoadResourceGroup connection)
: base("SqlQueries", connection)
{
}
@@ -38,10 +38,10 @@ internal SqlQueriesResourceGroup(ILoadResourceGroup connection)
/// The id of the query.
/// A allowing the operation to be canceled.
/// The query.
- public async Task FindAsync(string id, CancellationToken cancellationToken = default)
+ public async Task FindAsync(string id, CancellationToken cancellationToken = default)
{
if (id == null) throw new ArgumentNullException(nameof(id));
- return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false);
+ return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false);
}
///
@@ -52,10 +52,10 @@ public async Task FindAsync(string id, CancellationToken cancell
/// If true, shared queries will be included in the result.
/// allowing the operation to be canceled.
/// A list containing matching queries.
- public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default)
+ public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default)
{
var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } };
- return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false);
+ return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
@@ -63,9 +63,9 @@ public async Task> ListAsync(string ownerId = null, bool sh
///
/// allowing the operation to be canceled.
/// The unsaved query.
- public async Task TemplateAsync(CancellationToken cancellationToken = default)
+ public async Task TemplateAsync(CancellationToken cancellationToken = default)
{
- return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false);
+ return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
@@ -74,9 +74,9 @@ public async Task TemplateAsync(CancellationToken cancellationTo
/// The query to add.
/// A allowing the operation to be canceled.
/// The query, with server-allocated properties such as initialized.
- public async Task AddAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default)
+ public async Task AddAsync(QueryEntity entity, CancellationToken cancellationToken = default)
{
- return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false);
+ return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
@@ -85,7 +85,7 @@ public async Task AddAsync(SqlQueryEntity entity, CancellationTo
/// The query to remove.
/// A allowing the operation to be canceled.
/// A task indicating completion.
- public async Task RemoveAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default)
+ public async Task RemoveAsync(QueryEntity entity, CancellationToken cancellationToken = default)
{
await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false);
}
@@ -96,7 +96,7 @@ public async Task RemoveAsync(SqlQueryEntity entity, CancellationToken cancellat
/// The query to update.
/// A allowing the operation to be canceled.
/// A task indicating completion.
- public async Task UpdateAsync(SqlQueryEntity entity, CancellationToken cancellationToken = default)
+ public async Task UpdateAsync(QueryEntity entity, CancellationToken cancellationToken = default)
{
await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false);
}
diff --git a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs
index 7249714..2762668 100644
--- a/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs
+++ b/src/Seq.Api/ResourceGroups/SignalsResourceGroup.cs
@@ -36,12 +36,13 @@ internal SignalsResourceGroup(ILoadResourceGroup connection)
/// Retrieve the signal with the given id; throws if the entity does not exist.
///
/// The id of the signal.
+ /// If true, include only partial details in the result.
/// A allowing the operation to be canceled.
/// The signal.
- public async Task FindAsync(string id, CancellationToken cancellationToken = default)
+ public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default)
{
if (id == null) throw new ArgumentNullException(nameof(id));
- return await GroupGetAsync("Item", new Dictionary { { "id", id } }, cancellationToken).ConfigureAwait(false);
+ return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false);
}
///
@@ -50,11 +51,12 @@ public async Task FindAsync(string id, CancellationToken cancellat
/// If the id of a is provided, only signals owned by them will be included in the result; if
/// not specified, personal signals for all owners will be listed.
/// If true, shared signals will be included in the result.
+ /// If true, include only partial details in the result.
/// allowing the operation to be canceled.
/// A list containing matching signals.
- public async Task> ListAsync(string ownerId = null, bool shared = false, CancellationToken cancellationToken = default)
+ public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default)
{
- var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared } };
+ var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } };
return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false);
}
diff --git a/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs b/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs
new file mode 100644
index 0000000..b6e0e98
--- /dev/null
+++ b/src/Seq.Api/ResourceGroups/ViewsResourceGroup.cs
@@ -0,0 +1,106 @@
+// 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.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Seq.Api.Model;
+using Seq.Api.Model.Metrics;
+using Seq.Api.Model.Users;
+
+namespace Seq.Api.ResourceGroups;
+
+///
+/// Perform operations on metrics views. A view is a collection of filters, groupings, and pinned charts used to
+/// populate the Seq Metrics screen.
+///
+public class ViewsResourceGroup : ApiResourceGroup
+{
+ internal ViewsResourceGroup(ILoadResourceGroup connection)
+ : base("Views", connection)
+ {
+ }
+
+ ///
+ /// Retrieve the view with the given id; throws if the entity does not exist.
+ ///
+ /// The id of the view.
+ /// If true, include only partial details in the result.
+ /// A allowing the operation to be canceled.
+ /// The view.
+ public async Task FindAsync(string id, bool partial = false, CancellationToken cancellationToken = default)
+ {
+ if (id == null) throw new ArgumentNullException(nameof(id));
+ return await GroupGetAsync("Item", new Dictionary { { "id", id }, { "partial", partial } }, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Retrieve views.
+ ///
+ /// If the id of a is provided, only views owned by them will be included in the result; if
+ /// not specified, personal views for all owners will be listed.
+ /// If true, shared views will be included in the result.
+ /// If true, include only partial details in the result.
+ /// allowing the operation to be canceled.
+ /// A list containing matching views.
+ public async Task> ListAsync(string ownerId = null, bool shared = false, bool partial = false, CancellationToken cancellationToken = default)
+ {
+ var parameters = new Dictionary { { "ownerId", ownerId }, { "shared", shared }, { "partial", partial } };
+ return await GroupListAsync("Items", parameters, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Construct a view with server defaults pre-initialized.
+ ///
+ /// allowing the operation to be canceled.
+ /// The unsaved view.
+ public async Task TemplateAsync(CancellationToken cancellationToken = default)
+ {
+ return await GroupGetAsync("Template", cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Add a new view.
+ ///
+ /// The view to add.
+ /// A allowing the operation to be canceled.
+ /// The view, with server-allocated properties such as initialized.
+ public async Task AddAsync(ViewEntity entity, CancellationToken cancellationToken = default)
+ {
+ return await GroupCreateAsync(entity, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Remove an existing view.
+ ///
+ /// The view to remove.
+ /// A allowing the operation to be canceled.
+ /// A task indicating completion.
+ public async Task RemoveAsync(ViewEntity entity, CancellationToken cancellationToken = default)
+ {
+ await Client.DeleteAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Update an existing view.
+ ///
+ /// The view to update.
+ /// A allowing the operation to be canceled.
+ /// A task indicating completion.
+ public async Task UpdateAsync(ViewEntity entity, CancellationToken cancellationToken = default)
+ {
+ await Client.PutAsync(entity, "Self", entity, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/Seq.Api/Seq.Api.csproj b/src/Seq.Api/Seq.Api.csproj
index 19e11c8..157f69a 100644
--- a/src/Seq.Api/Seq.Api.csproj
+++ b/src/Seq.Api/Seq.Api.csproj
@@ -1,9 +1,8 @@
Client library for the Seq HTTP API.
- 2025.2.2
Datalust;Contributors
- netstandard2.0;net6.0;net8.0
+ netstandard2.0;net6.0;net8.0;net10.0
true
true
seq
@@ -23,13 +22,21 @@
-
+
-
+
-
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Seq.Api/SeqConnection.cs b/src/Seq.Api/SeqConnection.cs
index 9fc90f7..5ac056d 100644
--- a/src/Seq.Api/SeqConnection.cs
+++ b/src/Seq.Api/SeqConnection.cs
@@ -143,6 +143,11 @@ public void Dispose()
/// Perform operations on the Seq license certificate.
///
public LicensesResourceGroup Licenses => new(this);
+
+ ///
+ /// Perform operations on metrics definitions.
+ ///
+ public MetricsResourceGroup Metrics => new(this);
///
/// Perform operations on permalinked events.
@@ -175,9 +180,9 @@ public void Dispose()
public SignalsResourceGroup Signals => new(this);
///
- /// Perform operations on saved SQL queries.
+ /// Perform operations on saved queries.
///
- public SqlQueriesResourceGroup SqlQueries => new(this);
+ public QueriesResourceGroup Queries => new(this);
///
/// Perform operations on known available Seq versions.
@@ -189,6 +194,11 @@ public void Dispose()
///
public UsersResourceGroup Users => new(this);
+ ///
+ /// Perform operations on metrics views.
+ ///
+ public ViewsResourceGroup Views => new(this);
+
///
/// Perform operations on workspaces.
///
diff --git a/test/Seq.Api.Tests/Seq.Api.Tests.csproj b/test/Seq.Api.Tests/Seq.Api.Tests.csproj
index 6c00170..603b80b 100644
--- a/test/Seq.Api.Tests/Seq.Api.Tests.csproj
+++ b/test/Seq.Api.Tests/Seq.Api.Tests.csproj
@@ -1,8 +1,10 @@
- net462;net6.0
+ net48
+ $(TargetFrameworks);net10.0
true
+ false