Skip to content

Commit

Permalink
Merge pull request #58 from datalust/dev
Browse files Browse the repository at this point in the history
5.0.0 release
  • Loading branch information
nblumhardt authored Nov 5, 2018
2 parents 74274cf + b8b08c4 commit d4a4d78
Show file tree
Hide file tree
Showing 56 changed files with 708 additions and 437 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,4 @@ paket-files/
*.sln.iml

.vscode/
*.orig
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Navigate the "resource groups" exposed as properties of the `connnection`:
var installedApps = await connection.Apps.ListAsync();
```

**To authenticate**, the `SeqConnection` constructor accepts an `apiKey` parameter (make sure the API key permits _user-level access_) or, if you want to log in with personal credentials you can `await connection.Users.Login(username, password)`.
**To authenticate**, the `SeqConnection` constructor accepts an `apiKey` parameter (make sure the API key permits _user-level access_) or, if you want to log in with personal credentials you can `await connection.Users.LoginAsync(username, password)`.

For a more complete example, see the [seq-tail app included in the source](https://github.com/datalust/seq-api/blob/master/example/SeqTail/Program.cs).

Expand Down Expand Up @@ -128,13 +128,13 @@ var events = await client.GetAsync<ResourceGroup>(root, "EventsResources");
Use the client to navigate links from entity to entity:

```csharp
var matched = await client.List<EventEntity>(
var matched = await client.ListAsync<EventEntity>(
events,
"Items",
new Dictionary<string, object>{{"count", 10}, {"render", true}});

foreach (var match in matched)
Console.WriteLine(matched.RenderedMessage);
Console.WriteLine(match.RenderedMessage);
```

### Package versioning
Expand Down
2 changes: 0 additions & 2 deletions Seq.Api.sln.DotSettings

This file was deleted.

1 change: 0 additions & 1 deletion example/SeqTail/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using DocoptNet;
using Seq.Api;
Expand Down
1 change: 0 additions & 1 deletion example/SeqTail/SeqTail.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="1.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="System.Reactive" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
Expand Down
7 changes: 3 additions & 4 deletions example/SignalCopy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Seq.Api.Model.Signals;
using System.Collections.Generic;

namespace SeqQuery
namespace SignalCopy
{
class Program
{
Expand Down Expand Up @@ -76,10 +76,9 @@ static async Task Run(string src, string srcKey, string dst, string dstKey)

foreach (var signal in await srcConnection.Signals.ListAsync())
{
SignalEntity target;
if (dstSignals.TryGetValue(signal.Title, out target))
if (dstSignals.TryGetValue(signal.Title, out var target))
{
if (target.IsRestricted)
if (target.IsProtected)
{
Console.WriteLine($"Skipping restricted signal '{signal.Title}' ({target.Id})");
continue;
Expand Down
92 changes: 51 additions & 41 deletions src/Seq.Api/Client/SeqApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class SeqApiClient : IDisposable
// Future versions of Seq may not completely support v1 features, however
// providing this as an Accept header will ensure what compatibility is available
// can be utilised.
const string SeqApiV5MediaType = "application/vnd.datalust.seq.v5+json";
const string SeqApiV6MediaType = "application/vnd.datalust.seq.v6+json";

readonly HttpClient _httpClient;
readonly CookieContainer _cookies = new CookieContainer();
Expand All @@ -36,14 +36,18 @@ public class SeqApiClient : IDisposable
Converters = { new StringEnumConverter(), new LinkCollectionConverter() }
});

public SeqApiClient(string serverUrl, string apiKey = null)
public SeqApiClient(string serverUrl, string apiKey = null, bool useDefaultCredentials = true)
{
ServerUrl = serverUrl ?? throw new ArgumentNullException(nameof(serverUrl));

if (!string.IsNullOrEmpty(apiKey))
_apiKey = apiKey;

var handler = new HttpClientHandler { CookieContainer = _cookies, UseDefaultCredentials = true };
var handler = new HttpClientHandler
{
CookieContainer = _cookies,
UseDefaultCredentials = useDefaultCredentials
};

var baseAddress = serverUrl;
if (!baseAddress.EndsWith("/"))
Expand All @@ -56,88 +60,95 @@ public SeqApiClient(string serverUrl, string apiKey = null)

public HttpClient HttpClient => _httpClient;

public Task<RootEntity> GetRootAsync()
public Task<RootEntity> GetRootAsync(CancellationToken cancellationToken = default)
{
return HttpGetAsync<RootEntity>("api");
return HttpGetAsync<RootEntity>("api", cancellationToken);
}

public Task<TEntity> GetAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null)
public Task<TEntity> GetAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetAsync<TEntity>(linkUri);
return HttpGetAsync<TEntity>(linkUri, cancellationToken);
}

public Task<string> GetStringAsync(ILinked entity, string link, IDictionary<string, object> parameters = null)
public Task<string> GetStringAsync(ILinked entity, string link, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetStringAsync(linkUri);
return HttpGetStringAsync(linkUri, cancellationToken);
}

public Task<List<TEntity>> ListAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null)
public Task<List<TEntity>> ListAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
return HttpGetAsync<List<TEntity>>(linkUri);
return HttpGetAsync<List<TEntity>>(linkUri, cancellationToken);
}

public async Task PostAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task PostAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
new StreamReader(stream).ReadToEnd();
}

public async Task<TResponse> PostAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task<TResponse> PostAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}

public async Task<string> PostReadStringAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task<string> PostReadStringAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
return await new StreamReader(stream).ReadToEndAsync();
}

public async Task PutAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task<Stream> PostReadStreamAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Post, linkUri) { Content = MakeJsonContent(content) };
return await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
}

public async Task PutAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Put, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
new StreamReader(stream).ReadToEnd();
}

public async Task DeleteAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task DeleteAsync<TEntity>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
new StreamReader(stream).ReadToEnd();
}

public async Task<TResponse> DeleteAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null)
public async Task<TResponse> DeleteAsync<TEntity, TResponse>(ILinked entity, string link, TEntity content, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);
var request = new HttpRequestMessage(HttpMethod.Delete, linkUri) { Content = MakeJsonContent(content) };
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
return _serializer.Deserialize<TResponse>(new JsonTextReader(new StreamReader(stream)));
}

public async Task<ObservableStream<TEntity>> StreamAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null)
public async Task<ObservableStream<TEntity>> StreamAsync<TEntity>(ILinked entity, string link, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
return await WebSocketStreamAsync(entity, link, parameters, reader => _serializer.Deserialize<TEntity>(new JsonTextReader(reader)));
return await WebSocketStreamAsync(entity, link, parameters, reader => _serializer.Deserialize<TEntity>(new JsonTextReader(reader)), cancellationToken);
}

public async Task<ObservableStream<string>> StreamTextAsync(ILinked entity, string link, IDictionary<string, object> parameters = null)
public async Task<ObservableStream<string>> StreamTextAsync(ILinked entity, string link, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
return await WebSocketStreamAsync(entity, link, parameters, reader => reader.ReadToEnd());
return await WebSocketStreamAsync(entity, link, parameters, reader => reader.ReadToEnd(), cancellationToken);
}

async Task<ObservableStream<T>> WebSocketStreamAsync<T>(ILinked entity, string link, IDictionary<string, object> parameters, Func<TextReader, T> deserialize)
async Task<ObservableStream<T>> WebSocketStreamAsync<T>(ILinked entity, string link, IDictionary<string, object> parameters, Func<TextReader, T> deserialize, CancellationToken cancellationToken = default)
{
var linkUri = ResolveLink(entity, link, parameters);

Expand All @@ -146,35 +157,35 @@ async Task<ObservableStream<T>> WebSocketStreamAsync<T>(ILinked entity, string l
if (_apiKey != null)
socket.Options.SetRequestHeader("X-Seq-ApiKey", _apiKey);

await socket.ConnectAsync(new Uri(linkUri), CancellationToken.None);
await socket.ConnectAsync(new Uri(linkUri), cancellationToken);

return new ObservableStream<T>(socket, deserialize);
}

async Task<T> HttpGetAsync<T>(string url)
async Task<T> HttpGetAsync<T>(string url, CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
return _serializer.Deserialize<T>(new JsonTextReader(new StreamReader(stream)));
}

async Task<string> HttpGetStringAsync(string url)
async Task<string> HttpGetStringAsync(string url, CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var stream = await HttpSendAsync(request).ConfigureAwait(false);
var stream = await HttpSendAsync(request, cancellationToken).ConfigureAwait(false);
return await new StreamReader(stream).ReadToEndAsync();
}

async Task<Stream> HttpSendAsync(HttpRequestMessage request)
async Task<Stream> HttpSendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
if (_apiKey != null)
request.Headers.Add("X-Seq-ApiKey", _apiKey);

request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV5MediaType));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(SeqApiV6MediaType));

var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);

if (response.IsSuccessStatusCode)
return stream;

Expand All @@ -186,11 +197,10 @@ async Task<Stream> HttpSendAsync(HttpRequestMessage request)
// ReSharper disable once EmptyGeneralCatchClause
catch { }

object error;
if (payload != null && payload.TryGetValue("Error", out error) && error != null)
throw new SeqApiException($"{(int)response.StatusCode} - {error}");
if (payload != null && payload.TryGetValue("Error", out var error) && error != null)
throw new SeqApiException($"{(int)response.StatusCode} - {error}", response.StatusCode);

throw new SeqApiException($"The Seq request failed ({(int)response.StatusCode}).");
throw new SeqApiException($"The Seq request failed ({(int)response.StatusCode}).", response.StatusCode);
}

HttpContent MakeJsonContent(object content)
Expand Down
17 changes: 16 additions & 1 deletion src/Seq.Api/Client/SeqApiException.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
using System;
using System.Net;

namespace Seq.Api.Client
{
/// <summary>
/// Thrown when an action cannot be performed.
/// </summary>
public class SeqApiException : Exception
{
public SeqApiException(string message)
/// <summary>
/// Construct a <see cref="SeqApiException"/> with the given message and status code.
/// </summary>
/// <param name="message">A message describing the error.</param>
/// <param name="statusCode">The corresponding status code returned from Seq, if available.</param>
public SeqApiException(string message, HttpStatusCode? statusCode)
: base(message)
{
StatusCode = statusCode;
}

/// <summary>
/// The status code returned from Seq, if available.
/// </summary>
public HttpStatusCode? StatusCode { get; }
}
}
12 changes: 5 additions & 7 deletions src/Seq.Api/Model/AppInstances/AppInstanceEntity.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Seq.Api.Model.Apps;
using Seq.Api.Model.Signals;

Expand All @@ -13,9 +14,7 @@ public AppInstanceEntity()
InvocationOverridableSettings = new List<string>();
InvocationOverridableSettingDefinitions = new List<AppSettingPart>();
EventsPerSuppressionWindow = 1;
#pragma warning disable 618
SignalIds = new List<string>();
#pragma warning restore 618
Metrics = new AppInstanceMetricsPart();
}

public string Title { get; set; }
Expand All @@ -29,11 +28,10 @@ public AppInstanceEntity()
public int ChannelCapacity { get; set; }
public TimeSpan SuppressionTime { get; set; }
public int EventsPerSuppressionWindow { get; set; }
public int? ProcessedEventsPerMinute { get; set; }

[Obsolete("Replaced by InputSignalExpression.")]
public List<string> SignalIds { get; set; }

public List<AppSettingPart> InvocationOverridableSettingDefinitions { get; set; }

[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public AppInstanceMetricsPart Metrics { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/Seq.Api/Model/AppInstances/AppInstanceMetricsPart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Seq.Api.Model.AppInstances
{
public class AppInstanceMetricsPart
{
public int ReceivedEventsPerMinute { get; set; }
public int EmittedEventsPerMinute { get; set; }
public long ProcessWorkingSetBytes { get; set; }
public bool IsRunning { get; set; }
}
}
4 changes: 1 addition & 3 deletions src/Seq.Api/Model/Backups/BackupEntity.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;

namespace Seq.Api.Model.Backups
namespace Seq.Api.Model.Backups
{
public class BackupEntity : Entity
{
Expand Down
1 change: 0 additions & 1 deletion src/Seq.Api/Model/Events/DeleteResultPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@
{
public class DeleteResultPart
{
public long DeletedEventCount { get; set; }
}
}
Loading

0 comments on commit d4a4d78

Please sign in to comment.