Querying collection properties in Seq 1.5
Seq implements a filtering syntax that’s loosely based on C#. The following expression matches the events you’d expect it to, if you’re used to working with C# or a similar language:
Tag == "seq"
Which is to say: “Match events where the Tag
property has the value "seq"
. Sub-properties of objects can be accessed using the familiar dotted syntax (Property.Subproperty.Subproperty) and so-on.
Seq 1.5, currently a preview, steps into some new territory.
What about a collection of tags?
It’s not uncommon for Serilog events to carry properties that are collections. For example we might log events on a Q&A; site like:
Log.Information("User {Username} posted a question tagged {Tags}", username, tags);
Resulting in events like:
Instead of being marked with a single tag, each event carries a collection of zero-or-more.
Matt Hamilton asks:
https://twitter.com/mabster/status/507368509060313088
This is where Seq 1.4 draws a blank. Translating this to the Q&A; example, about the best we can manage is a text search for "seq"
and requiring the existence of the Tags
property using Has(Tags)
.
This is a bit of a shame - the promise of Seq and structured logging in general is to make querying log events precise - so why aren’t collections covered?
The C# way
Seq isn’t only about precise queries; the whole foundation of the app is the idea that it should also be easy and natural to query log data.
I’d thought about collection queries quite a lot when putting the original filtering syntax together. C#-style the query would look something like:
Any(Tags, tag => tag == "seq")
Seq doesn’t use extension method-style calls, but this is pretty close to the Enumerable.Any()
call you’d write using Linq.
I don’t like it!
In a full-featured programming language this syntax makes sense, but it’s too noisy, complex, and easy to mistype to make a good filtering syntax for Seq. And so, that’s where things have sat for the last twelve months.
Pattern matching
With a bit more thought, Matt and I both ended up thinking the problem might be better suited to a pattern-matching syntax like:
Tags[?] == "seq"
So, this is what Seq 1.5 provides. The question mark wildcard ?
matches any element in the collection, while an asterisk *
wildcard only matches if all elements satisfy the condition.
Wildcards work in any comparison where one side is a property path (including indexers and dotted sub-properties). Multiple wildcards along the path are supported.
You can write the following expression to find questions where all answers are "Yes!"
:
Answers[*].Content == "Yes!"
(Under the hood, Seq rewrites these expressions into the equivalent lambda-style syntax - much the same way as the C# compiler transforms Linq query comprehensions. Working through implementing this reminded me just how fantastic the C# compiler really is!)
Try it out!
You can download a preview build of Seq 1.5 here. It’s not ready for production use yet, but should be just fine for dev/non-mission-critical servers. We’d love to hear what you think!