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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Stream log events matching a filter.

Print the current executable version.

## Extraction Patterns
## Extraction patterns

The `seqcli ingest` command can be used for parsing plain text logs into structured log events.

Expand Down Expand Up @@ -201,7 +201,7 @@ Different matchers are needed so that a piece of text like `200OK` can be separa

There are three kinds of matchers:

* Matchers like `alpha` and `nat` are built-in _named_ matchers. These are built-in.
* Matchers like `alpha` and `nat` are built-in _named_ matchers.
* The special matchers `*`, `**` and so-on, are _non-greedy content_ matchers; these will match any text up until the next pattern element matches (`*`), the next two elements match, and so-on. We saw this in action with the `{@m:*}{:n}` elements in the example - the message is all of the text up until the next newline.
* More complex _compound_ matchers are described using a sub-expression. These are prefixed with an equals sign `=`, like `{Phone:={:nat}-{:nat}-{:nat}}`. This will extract chunks of text like `123-456-7890` into the `Phone` property.

Expand Down
9 changes: 6 additions & 3 deletions src/SeqCli/Cli/CommandLineHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ public async Task<int> Run(string[] args)
if (args.Length > 0)
{
var norm = args[0].ToLowerInvariant();
var subCommandNorm = args.Length > 1 && !args[1].Contains("-") ? args[1].ToLowerInvariant() : default;
var cmd = _availableCommands.SingleOrDefault(c => c.Metadata.Name == norm && c.Metadata.SubCommand == subCommandNorm);
var subCommandNorm = args.Length > 1 && !args[1].Contains("-") ? args[1].ToLowerInvariant() : null;

var cmd = _availableCommands.SingleOrDefault(c =>
c.Metadata.Name == norm && (c.Metadata.SubCommand == subCommandNorm || c.Metadata.SubCommand == null));

if (cmd != null)
{
var amountToSkip = subCommandNorm == default ? 1 : 2;
var amountToSkip = cmd.Metadata.SubCommand == null ? 1 : 2;
return await cmd.Value.Value.Invoke(args.Skip(amountToSkip).ToArray());
}
}
Expand Down
38 changes: 14 additions & 24 deletions src/SeqCli/Cli/Commands/ApiKey/ListCommand.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SeqCli.Cli.Features;
using SeqCli.Config;
using SeqCli.Connection;
using Serilog;

namespace SeqCli.Cli.Commands.ApiKey
{
[Command("apikey", "list", "List of API Keys", Example =
"seqcli apikey list")]
[Command("apikey", "list", "List API keys on the server", Example="seqcli apikey list")]
class ListCommand : Command
{
private readonly SeqConnectionFactory _connectionFactory;
private readonly ConnectionFeature _connection;
readonly SeqConnectionFactory _connectionFactory;

readonly ConnectionFeature _connection;
readonly OutputFormatFeature _output;

public ListCommand(SeqConnectionFactory connectionFactory)
public ListCommand(SeqConnectionFactory connectionFactory, SeqCliConfig config)
{
_connectionFactory = connectionFactory;
if (config == null) throw new ArgumentNullException(nameof(config));
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));

_output = Enable(new OutputFormatFeature(config.Output));
_connection = Enable<ConnectionFeature>();
}

Expand All @@ -27,25 +30,12 @@ protected override async Task<int> Run()

var apiKeys = await connection.ApiKeys.ListAsync();
Log.Debug("Retrieved ApiKeys {@ApiKeys}", apiKeys);
var data = apiKeys.Select(a => new
{
a.Title,
a.Id,
a.Token,
a.MinimumLevel,
a.AppliedProperties,
a.CanActAsPrincipal,
a.InputFilter,
a.UseServerTimestamps,
a.IsDefault
});

foreach (var apiKey in data)
foreach (var apiKey in apiKeys)
{
var apiKeyString = JsonConvert.SerializeObject(apiKey);

Console.WriteLine(apiKeyString);
_output.WriteEntity(apiKey);
}

return 0;
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/SeqCli/Cli/Commands/ApiKey/RemoveCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

namespace SeqCli.Cli.Commands.ApiKey
{
[Command("apikey", "remove", "Remove API Key from the server", Example =
"seqcli apikey remove -t TestApiKey")]
[Command("apikey", "remove", "Remove an API key from the server",
Example="seqcli apikey remove -t TestApiKey")]
class RemoveCommand : Command
{
private readonly SeqConnectionFactory _connectionFactory;
private readonly ConnectionFeature _connection;
private string _title;
private string _id;
readonly SeqConnectionFactory _connectionFactory;
readonly ConnectionFeature _connection;
string _title;
string _id;

public RemoveCommand(SeqConnectionFactory connectionFactory)
{
Expand Down
71 changes: 52 additions & 19 deletions src/SeqCli/Cli/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,29 @@ protected override Task<int> Run(string[] unrecognised)
{
var ea = Assembly.GetEntryAssembly();
var name = ea.GetName().Name;


if (_markdown)
{
if (unrecognised.Length != 0)
return base.Run(unrecognised);

PrintMarkdownHelp(name);
return Task.FromResult(0);
}

string topLevelCommand = null;
if (unrecognised.Length > 0)
{
var target = unrecognised[0].ToLowerInvariant();
var cmd = _availableCommands.SingleOrDefault(c => c.Metadata.Name == target);
if (cmd != null)
topLevelCommand = unrecognised[0].ToLowerInvariant();
var subCommand = unrecognised.Length > 1 && !unrecognised[1].Contains("-") ? unrecognised[1] : null;
var cmds = _availableCommands.Where(c => c.Metadata.Name == topLevelCommand &&
(subCommand == null || subCommand == c.Metadata.SubCommand)).ToArray();
if (cmds.Length == 0)
return base.Run(unrecognised);

if (cmds.Length == 1)
{
var cmd = cmds.Single();
var argHelp = cmd.Value.Value.HasArgs ? " [<args>]" : "";
Console.WriteLine(name + " " + cmd.Metadata.Name + argHelp);
Console.WriteLine();
Expand All @@ -52,19 +68,13 @@ protected override Task<int> Run(string[] unrecognised)
cmd.Value.Value.PrintUsage();
return Task.FromResult(0);
}

return base.Run(unrecognised);
}

if (_markdown)
{
PrintMarkdownHelp(name);
}
if (topLevelCommand != null)
PrintHelp(name, topLevelCommand);
else
{
PrintHelp(name);
}


return Task.FromResult(0);
}

Expand Down Expand Up @@ -126,17 +136,40 @@ void PrintHelp(string executableName)
Console.WriteLine();
Console.WriteLine("Available commands are:");

foreach (var availableCommand in _availableCommands)
var printedGroups = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var avail in _availableCommands)
{
Printing.Define(
" " + availableCommand.Metadata.Name,
availableCommand.Metadata.HelpText,
13,
Console.Out);
if (avail.Metadata.SubCommand != null)
{
if (!printedGroups.Contains(avail.Metadata.Name))
{
Printing.Define($" {avail.Metadata.Name}", "<sub-command>", 13, Console.Out);
printedGroups.Add(avail.Metadata.Name);
}
}
else
{
Printing.Define($" {avail.Metadata.Name}", avail.Metadata.HelpText, 13, Console.Out);
}
}

Console.WriteLine();
Console.WriteLine($"Type `{executableName} help <command>` for detailed help");
}

void PrintHelp(string executableName, string topLevelCommand)
{
Console.WriteLine($"Usage: {executableName} {topLevelCommand} <sub-command> [<args>]");
Console.WriteLine();
Console.WriteLine("Available sub-commands are:");

foreach (var avail in _availableCommands.Where(c => c.Metadata.Name == topLevelCommand))
{
Printing.Define($" {avail.Metadata.SubCommand}", avail.Metadata.HelpText, 13, Console.Out);
}

Console.WriteLine();
Console.WriteLine($"Type `{executableName} help {topLevelCommand} <sub-command>` for detailed help");
}
}
}
32 changes: 32 additions & 0 deletions src/SeqCli/Cli/Features/OutputFormatFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
// limitations under the License.

using System;
using Destructurama;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Seq.Api.Model;
using SeqCli.Config;
using SeqCli.Csv;
using SeqCli.Output;
Expand Down Expand Up @@ -75,5 +79,33 @@ public void WriteCsv(string csv)
CsvWriter.WriteCsv(tokens, Theme, Console.Out, true);
}
}

public void WriteEntity(Entity entity)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));

var jo = JObject.FromObject(entity);

if (_json)
{
jo.Remove("Links");
// Proof-of-concept; this is a very inefficient
// way to write colorized JSON ;)

var writer = new LoggerConfiguration()
.Destructure.JsonNetTypes()
.Enrich.With<StripStructureTypeEnricher>()
.WriteTo.Console(
outputTemplate: "{@Message:j}{NewLine}",
theme: Theme)
.CreateLogger();
writer.Information("{@Entity}", jo);
}
else
{
var dyn = (dynamic) jo;
Console.WriteLine($"{entity.Id} {dyn.Title ?? dyn.Name}");
}
}
}
}
25 changes: 25 additions & 0 deletions src/SeqCli/Output/StripStructureTypeEnricher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Linq;
using Serilog.Core;
using Serilog.Data;
using Serilog.Events;

namespace SeqCli.Output
{
public class StripStructureTypeEnricher : LogEventPropertyValueRewriter<object>, ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
foreach (var property in logEvent.Properties)
{
var updated = new LogEventProperty(property.Key, Visit(null, property.Value));
logEvent.AddOrUpdateProperty(updated);
}
}

protected override LogEventPropertyValue VisitStructureValue(object state, StructureValue structure)
{
return new StructureValue(structure.Properties.Select(p =>
new LogEventProperty(p.Name, Visit(null, p.Value))));
}
}
}
1 change: 1 addition & 0 deletions src/SeqCli/SeqCli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Destructurama.JsonNet" Version="1.2.0" />
<PackageReference Include="newtonsoft.json" Version="10.0.3" />
<PackageReference Include="Serilog" Version="2.6.0" />
<PackageReference Include="serilog.filters.expressions" Version="1.1.0" />
Expand Down
51 changes: 4 additions & 47 deletions test/SeqCli.Tests/Cli/CommandLineHostTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autofac.Features.Metadata;
using SeqCli.Cli;
using SeqCli.Cli.Commands;
using Xunit;

namespace SeqCli.Tests.Cli
Expand All @@ -21,57 +18,17 @@ public async Task CheckCommandLineHostPicksCorrectCommand()
{
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test"))),
new CommandMetadata() {Name = "test"}),
new CommandMetadata {Name = "test"}),
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test2"))),
new CommandMetadata() {Name = "test2"})
new CommandMetadata {Name = "test2"})
};
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(new []{ "test"});

Assert.Equal(commandsRan.First(), "test");
}

[Fact]
public async Task WhenCommandAndSubcommandAndTheUserRunsWithoutSubcommandEnsurePickedCorrect()
{
var commandsRan = new List<string>();
var availableCommands =
new List<Meta<Lazy<Command>, CommandMetadata>>
{
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test"))),
new CommandMetadata() {Name = "test"}),
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand"))),
new CommandMetadata() {Name = "test", SubCommand = "subcommand"})
};
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(new[] { "test" });

Assert.Equal(commandsRan.First(), "test");
}

[Fact]
public async Task WhenCommandAndSubcommandAndTheUserRunsWithSubcommandEnsurePickedCorrect()
{
var commandsRan = new List<string>();
var availableCommands =
new List<Meta<Lazy<Command>, CommandMetadata>>
{
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test"))),
new CommandMetadata() {Name = "test"}),
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand"))),
new CommandMetadata() {Name = "test", SubCommand = "subcommand"})
};
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(new[] { "test", "subcommand" });

Assert.Equal(commandsRan.First(), "test-subcommand");
}

[Fact]
public async Task WhenMoreThanOneSubcommandAndTheUserRunsWithSubcommandEnsurePickedCorrect()
{
Expand All @@ -81,10 +38,10 @@ public async Task WhenMoreThanOneSubcommandAndTheUserRunsWithSubcommandEnsurePic
{
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand1"))),
new CommandMetadata() {Name = "test", SubCommand = "subcommand1"}),
new CommandMetadata {Name = "test", SubCommand = "subcommand1"}),
new Meta<Lazy<Command>, CommandMetadata>(
new Lazy<Command>(() => new ActionCommand(() => commandsRan.Add("test-subcommand2"))),
new CommandMetadata() {Name = "test", SubCommand = "subcommand2"})
new CommandMetadata {Name = "test", SubCommand = "subcommand2"})
};
var commandLineHost = new CommandLineHost(availableCommands);
await commandLineHost.Run(new[] { "test", "subcommand2" });
Expand Down