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 [![Build status](https://ci.appveyor.com/api/projects/status/bhtx25hyqmmdqhvt?svg=true)](https://ci.appveyor.com/project/datalust/seq-api) [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Seq.Api.svg)](https://nuget.org/packages/seq.api) [![Join the chat at https://gitter.im/datalust/seq](https://img.shields.io/gitter/room/datalust/seq.svg)](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)