diff --git a/README.md b/README.md
index 9836820..8e53d6d 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
# Seq HTTP API Client [](https://ci.appveyor.com/project/datalust/seq-api) [](https://nuget.org/packages/seq.api) [](https://gitter.im/datalust/seq)
-
This library includes:
* C# representations of the entities exposed by the Seq HTTP API
@@ -32,7 +31,19 @@ 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)`.
-For a more complete example, see the [seq-tail app included in the source](https://github.com/continuousit/seq-api/blob/master/example/SeqTail/Program.cs);
+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).
+
+#### Creating entities
+
+The Seq API provides a `/template` resource for each resource group that provides a new instance of the resource with defaults populated. The API client uses this pattern when creating new entities:
+
+```csharp
+var signal = await connection.Signals.TemplateAsync();
+signal.Title = "Signal 123";
+await connection.Signals.AddAsync(signal);
+```
+
+See the [signal-copy app](https://github.com/datalust/seq-api/blob/master/example/SignalCopy/Program.cs) for an example of this pattern in action.
### Reading events
@@ -46,7 +57,7 @@ string lastReadEventId = null;
while(true)
{
var resultSet = await connection.Events.InSignalAsync(
- filter: "Environment == \"Test\"",
+ filter: "Environment = 'Test'",
render: true,
afterId: lastReadEventId);
@@ -64,7 +75,7 @@ If the result set is expected to be small, `ListAsync()` will buffer results and
```csharp
var resultSet = await connection.Events.ListAsync(
- filter: "Environment == \"Test\"",
+ filter: "Environment = 'Test'",
render: true,
count: 1000);
diff --git a/example/SeqQuery/Properties/AssemblyInfo.cs b/example/SeqQuery/Properties/AssemblyInfo.cs
deleted file mode 100644
index 50640c6..0000000
--- a/example/SeqQuery/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("SeqTail")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("SeqTail")]
-[assembly: AssemblyCopyright("Copyright © 2014")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("c0bc57c8-ec16-4353-959b-09bb552e13d1")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/example/SeqTail/Properties/AssemblyInfo.cs b/example/SeqTail/Properties/AssemblyInfo.cs
deleted file mode 100644
index 50640c6..0000000
--- a/example/SeqTail/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("SeqTail")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("SeqTail")]
-[assembly: AssemblyCopyright("Copyright © 2014")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("c0bc57c8-ec16-4353-959b-09bb552e13d1")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/example/SignalCopy/App.config b/example/SignalCopy/App.config
new file mode 100644
index 0000000..8277a73
--- /dev/null
+++ b/example/SignalCopy/App.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/SignalCopy/Program.cs b/example/SignalCopy/Program.cs
new file mode 100644
index 0000000..f77c169
--- /dev/null
+++ b/example/SignalCopy/Program.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Threading.Tasks;
+using DocoptNet;
+using Seq.Api;
+using System.Linq;
+using Seq.Api.Model.Signals;
+using System.Collections.Generic;
+
+namespace SeqQuery
+{
+ class Program
+ {
+ const string Usage = @"signal-copy: copy Seq signals from one server to another.
+
+Usage:
+ signal-copy.exe [--srckey=] [--dstkey=]
+ signal-copy.exe (-h | --help)
+
+Options:
+ -h --help Show this screen.
+ --srckey= Source server API key.
+ --dstkey= Destination server API key.
+
+ ";
+
+ static void Main(string[] args)
+ {
+ Task.Run(async () =>
+ {
+ try
+ {
+ var arguments = new Docopt().Apply(Usage, args, version: "Seq Signal Copy 0.1", exit: true);
+
+ var src = arguments[""].ToString();
+ var dst = arguments[""].ToString();
+ var srcKey = Normalize(arguments["--srckey"]);
+ var dstKey = Normalize(arguments["--dstkey"]);
+
+ await Run(src, srcKey, dst, dstKey);
+ }
+ catch (Exception ex)
+ {
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.BackgroundColor = ConsoleColor.Red;
+ Console.WriteLine("signal-copy: {0}", ex);
+ Console.ResetColor();
+ Environment.Exit(-1);
+ }
+ }).Wait();
+ }
+
+ static string Normalize(ValueObject v)
+ {
+ if (v == null) return null;
+ var s = v.ToString();
+ return string.IsNullOrWhiteSpace(s) ? null : s;
+ }
+
+ static async Task Run(string src, string srcKey, string dst, string dstKey)
+ {
+ var srcConnection = new SeqConnection(src, srcKey);
+ var dstConnection = new SeqConnection(dst, dstKey);
+
+ var dstSignals = new Dictionary();
+ foreach (var dstSignal in await dstConnection.Signals.ListAsync())
+ {
+ if (dstSignals.ContainsKey(dstSignal.Title))
+ {
+ Console.WriteLine($"The destination server has more than one signal named '{dstSignal.Title}'; only one copy of the signal will be updated.");
+ continue;
+ }
+
+ dstSignals.Add(dstSignal.Title, dstSignal);
+ }
+
+ var count = 0;
+
+ foreach (var signal in await srcConnection.Signals.ListAsync())
+ {
+ SignalEntity target;
+ if (dstSignals.TryGetValue(signal.Title, out target))
+ {
+ if (target.IsRestricted)
+ {
+ Console.WriteLine($"Skipping restricted signal '{signal.Title}' ({target.Id})");
+ continue;
+ }
+
+ Console.WriteLine($"Updating existing signal '{signal.Title}' ({target.Id})");
+ }
+ else
+ {
+ Console.WriteLine($"Creating new signal '{signal.Title}'");
+ target = await dstConnection.Signals.TemplateAsync();
+ }
+
+ target.Title = signal.Title;
+ target.Filters = signal.Filters;
+ target.TaggedProperties = signal.TaggedProperties;
+ target.Description = signal.Description + " (copy)";
+
+ await (target.Id != null ? dstConnection.Signals.UpdateAsync(target) : dstConnection.Signals.AddAsync(target));
+ ++count;
+ }
+
+ Console.WriteLine($"Done, {count} signals updated.");
+ }
+ }
+}
diff --git a/example/SignalCopy/Properties/launchSettings.json b/example/SignalCopy/Properties/launchSettings.json
new file mode 100644
index 0000000..f57dda9
--- /dev/null
+++ b/example/SignalCopy/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "SignalCopy": {
+ "commandName": "Project",
+ "commandLineArgs": "http://localhost:5341 http://localhost:5342 --srckey=fSCcBOyOfttZ0kiZFgq"
+ }
+ }
+}
\ No newline at end of file
diff --git a/example/SignalCopy/getseq_net.ico b/example/SignalCopy/getseq_net.ico
new file mode 100644
index 0000000..34b3b9f
Binary files /dev/null and b/example/SignalCopy/getseq_net.ico differ
diff --git a/example/SignalCopy/project.json b/example/SignalCopy/project.json
new file mode 100644
index 0000000..56ca860
--- /dev/null
+++ b/example/SignalCopy/project.json
@@ -0,0 +1,28 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "emitEntryPoint": true,
+ "outputName": "signal-copy"
+ },
+
+ "dependencies": {
+ "Seq.Api": { "target": "project" },
+ "docopt.net": "0.6.1.9"
+ },
+
+ "frameworks": {
+ "net4.6": {},
+ "netcoreapp1.0": {
+ "dependencies": {
+ "Microsoft.NETCore.App": {
+ "type": "platform",
+ "version": "1.0.0"
+ }
+ },
+ "imports": [
+ "dnxcore50",
+ "portable-net45+win8"
+ ]
+ }
+ }
+}
diff --git a/example/SignalCopy/signal-copy.xproj b/example/SignalCopy/signal-copy.xproj
new file mode 100644
index 0000000..88b89fc
--- /dev/null
+++ b/example/SignalCopy/signal-copy.xproj
@@ -0,0 +1,19 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ e8cdde17-8e29-4eb4-a4bb-38bce346a752
+ SignalCopy
+ .\obj
+ .\bin\
+ v4.5.2
+
+
+ 2.0
+
+
+
\ No newline at end of file
diff --git a/seq-api.sln b/seq-api.sln
index 81a43d5..2fc6982 100644
--- a/seq-api.sln
+++ b/seq-api.sln
@@ -31,6 +31,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "seq-query", "example\SeqQue
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "seq-tail", "example\SeqTail\seq-tail.xproj", "{42CEBFBA-208F-40F1-AC95-13F05F6D5412}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "signal-copy", "example\SignalCopy\signal-copy.xproj", "{E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -53,6 +55,10 @@ Global
{42CEBFBA-208F-40F1-AC95-13F05F6D5412}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42CEBFBA-208F-40F1-AC95-13F05F6D5412}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42CEBFBA-208F-40F1-AC95-13F05F6D5412}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -62,5 +68,6 @@ Global
{CD473266-4AED-4207-89FD-0B185239F1C7} = {5E7A565B-A8EE-43E7-A225-5B640A5D29A0}
{34BBD428-8297-484E-B771-0B72C172C264} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487}
{42CEBFBA-208F-40F1-AC95-13F05F6D5412} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487}
+ {E8CDDE17-8E29-4EB4-A4BB-38BCE346A752} = {1C66E116-DC21-4C8F-833E-A4C2DDF58487}
EndGlobalSection
EndGlobal
diff --git a/src/Seq.Api/Api/Client/SeqApiClient.cs b/src/Seq.Api/Api/Client/SeqApiClient.cs
index 73ddc24..30df9b7 100644
--- a/src/Seq.Api/Api/Client/SeqApiClient.cs
+++ b/src/Seq.Api/Api/Client/SeqApiClient.cs
@@ -170,17 +170,19 @@ async Task HttpSendAsync(HttpRequestMessage request)
if (response.IsSuccessStatusCode)
return stream;
+ Dictionary payload = null;
try
{
- var payload = _serializer.Deserialize>(new JsonTextReader(new StreamReader(stream)));
- object error;
- if (payload.TryGetValue("Error", out error) && error != null)
- throw new SeqApiException(error.ToString());
+ payload = _serializer.Deserialize>(new JsonTextReader(new StreamReader(stream)));
}
// ReSharper disable once EmptyGeneralCatchClause
catch { }
- throw new SeqApiException("The Seq request failed (" + response.StatusCode + ").");
+ object error;
+ if (payload != null && payload.TryGetValue("Error", out error) && error != null)
+ throw new SeqApiException($"{(int)response.StatusCode} - {error}");
+
+ throw new SeqApiException($"The Seq request failed ({(int)response.StatusCode}).");
}
HttpContent MakeJsonContent(object content)