Observability Engineering second edition out now! 27 net-new chapters written for today's observability challenges.Get your copy

Relational Query Superpowers

See how Honeycomb's relational query keywords—root, parent, child, any, any2, any3, and none—let you pull attributes from anywhere in a single trace into one query, walked through with a real checkout-error investigation.

Relational Query Superpowers

I'm investigating repeated errors in my e-commerce application, and I need to get enough context in a single Honeycomb query to piece the entire picture together. Each query returns events based on the event's WHERE clauses, but I want to know several things from outside of the event that recorded an error. Things like:

  • the URL involved
  • the exception details
  • the user's email address to contact them for support
  • the shipping costs
  • the amount of money the user spent

Those attributes are all over the trace. That's going to make a single query tough, right?

Wrong!

But, once I start applying relational queries to this challenge, I can grab information all over the trace and make it visible to the team—and my manager, who wanted to be informed on business-impacting errors.

See relational queries in action in the below video.

What is a relational query?

Honeycomb relational queries let you add criteria and groupings not only to target specific trace spans, but to their parent, children, and any other trace-involved spans as well. This can be incredibly useful in groupings, filterings, and queries in boards, triggers, and SLOs. Relational queries are implemented by attributes prefixed by the following keywords: root, parent, child, any, any2, and any3.

For additional information on relational fields in queries, head over to the relational fields page.

Let's start with the error spans in the checkout service

My manager asked me to focus on errors related to checkout, so I'll start there. The errors are the events we care about. I'll make my query dataset the checkout service, and add error = true to the WHERE conditions. I'll GROUP BY name to see what spans are tripping the error status, since all spans have a name property.

Query builder showing SELECT COUNT, WHERE error = true and name exists, GROUP BY name, error, exception.message for the checkout service

This shows several span names with errors originating from the checkout service in the past 24 hours:

Overview results table showing span names with error counts for the checkout service

Overview is the "show me the values" view: any visualization or grouping in the query shows up in the table of values below the graph in the Overview tab.

So far, we've located a single event type in our query, one with an error = true condition:

A sample erroring span

To make an impact that helps both business and our IT team, I want to include the actual exception.message causing the error in the query results.

Where is the exception?

The exception details exist within the error event, recorded against the span. Here's a chunk of the trace view in Honeycomb. That right-most red circle (highlighted in blue) represents the erroring span's exception event:

Trace view highlighting the erroring span's exception event as a red circle

Our first challenge: the WHERE error = true condition finds the trace span, but the span event is its child.

Let's get the actual error message

I'll use the relational child prefix to add exception.message to the GROUP BY, and, just so I don't include errors that propagated upward but may not have messages of their own, I'll only include errors that contain an exception event:

Query builder adding child.exception.message to the GROUP BY and an exists check to the WHERE clause

Right away, we've narrowed down the spans in the checkout service that contain errors with real exceptions.

A table of results from the Overview results tab, which retrieves not only the erroring span's name, the error condition, and the exception.message attribute from the span's child event. The count in the last column represents how many spans have that combination of values of the same name, error, and child.exception.message

This query now looks at two events: the primary span, and the span's child, an event with the exception details.

A depiction of the trace span, and its related exception, which shows up as a circle in the trace span's duration, at the time of the event. In this image it's separated out to illustrate the relationship between the trace span and its events.

Getting top-level information about the trace

I want to see what endpoint people hit when they get this error.

To do that, I'll add another relational query keyword, root, using, root.http.url to the GROUP BY to see the values, and I'll narrow the query to return errors where the root spans that actually contain a root.http.url using the WHERE clause.

A query that adds a check for whether the root.http.url attribute exists, and then groups by it. Now the query Overview tab will contain data from the erroring span, the child exception event, and the root span's HTTP URL.

I'm starting to get more useful data; I see the endpoint used for each call. In the past 14 days, we've had a few failures around the payment process:

An updated Overview results table, now including the root's HTTP.URL attribute

Adding root to the GROUP BY fetches additional trace span attributes from the span that began this trace, which Honeycomb refers to as the root span, even though the primary search fetches spans with error = true.

A depiction of the connection between the root span, the erroring span, and its exception child event.

I don't just have to limit myself to the http.url in root, I can add other attributes as well.

More details from the root span

I wonder what the http status code is for these errors. I'll add it to the GROUP BY:

Now the query adds a root.http.status_code attribute. This can be added to any query as root spans are always available (unless the root span hasn't arrived yet).

Each of our failures cause http 500 errors:

The Overview table now contains root.http.url

Oh, and the management team wants to focus on operations that leave money on the table. Next, we'll focus on the checkout purchase details.

Pulling the user id from another span

The manager wanted the user's information, so we can quickly contact them and resolve their problems with the site. Elsewhere in the trace, we record an app.user.id attribute. We can find it quickly, regardless of which span it lives in, using Honeycomb's any relational prefix.

Let's try it. We should be able to add any.app.user.id to the GROUP BY, right?

This image details how a user might add an any attribute to a GROUP BY. Note that the user hasn't, in this case, added the any condition for the same attribute to the WHERE clause.

Ok, it looks good. Unfortunately, when I run the query, the any.app.user.id clause gets removed, and a tip appears:

Removed 'any.app.user.id' from the Group By clause. Add a corresponding any filter to the Where clause to group by any fields.

Apparently we can't just use any in the WHERE clause. So, what do we do?

Tip: Identify a span in the WHERE clause to use its values in the GROUP BY clause

You certainly can use the any clause in the GROUP BY, but only if you add the same attribute to a WHERE clause… To fix this here, I'll just add an exists clause to the conditions:

To get the query to retrieve data from the app.user.id from any other span, it must be added to the WHERE clause, as this image shows. Once the attribute is added to the WHERE clause, it can be fetched and used in a GROUP BY clause.

Now, we get the recorded app.user.id that exists somewhere in the trace:

Depicts the relationship between the root span, the erroring span, its child exception event, and one other span with app.user.id anywhere else in the trace. The first span with the value of app.user.id is used.

Here are some results:

The table gets wider, now bringing back the app.user.id from another span using any.

I also received a warning:

Results match on the first `any` span found per trace and may exclude additional matching spans.

Because Honeycomb has to pick a single qualifying span for the any expression, it will not retrieve more than one here, so it picked the first one it found. In this case, it's fine; if you try this and found it picked the wrong span, qualify it further with other attributes on the any.

Adding other any attributes

Let's try adding another attribute using any. How about app.shipping.amount? It's in another span, surely any should work, right?

In the query for this image, another attribute is searched using any as a prefix

Nope! Now my query is broken, and it found no spans.

Because no single span contains both the app.user.id and the app.shipping.amount, no spans are retrieved.

What happened?

We didn't find one span with both attributes!

For any to retrieve a span, the attributes in referenced must all match the WHERE criteria against the any.

In my query above, I asked for spans that contained both app.user.id and app.shipping.amount, and no single span containing both of those attributes exists in our data.

Matching on additional spans: more anys

To match on additional spans, Honeycomb provides two additional attributes: any2 and any3. This lets us widen the results to include up to three spans anywhere in the trace with different, even mutually exclusive conditions.

I'll use any2 to against app.shipping.amount from that other trace span, adding it to the WHERE as an existence check, and then grouping by it:

To properly check two completely different attributes that live in different, mutually exclusive spans, the second attribute can use the any2 prefix in the WHERE and GROUP BY clauses.

This brings us the additional attribute:

Now the table contains app.shipping.amount from another span.

Now I'm looking at more of the trace:

The image depicts the relationship of spans retreived: from the root span, to the span with an error, to its exception event, to another span that contains app.user.id, and another span that contains a app.shipping.amount value.

I'm querying up to five spans in the same trace: the primary span with an error, the child exception event, the first span in the same trace containing root.http.url, and the first span in the same trace that has an app.shipping.amount.

How many spans can you reference this way?

But we still don't have a financial amount for these failed payments! As it turns out, another span has that attribute, app.payment.amount, so can we pull yet another span's attributes into our query?

Yes, we can. Honeycomb actually provides three anys: any, any2, and any3. I'll use any3 to pull in the actual payment amount from the span that starts the payment charging process.

Using any3, this query now can access app.payment.amount from yet another span.

This gives me data across six different spans!

This image depicts the relationship of the root span with the errored span, its exception event, the span with app.user.id, another span with app.shipping.amount and another span with app.payment.amount. All can be used in a GROUP BY which adds the attributes to the results in the Overview tab.

This is straining the ability to display so much data in the Overview tab, but the data can be found and displayed from one query:

The image here has many columns in it, which may or may not be practical to display in a single query but fetches data using parent, child, any, any2 and any3.

I can add this query to my board in table form, and get immediate at-a-glance views of the problems coming from my checkout service.

The none prefix

Use the none prefix to make sure a WHERE clause item doesn't exist anywhere in the traces of the spans you select. For example, to ignore any traces for a specific product, use:

none.app.product.id = 0PUK6V6EV0

Re-running your query, the results won't include traces with errors for that product.

The none keyword ensures that the condition checked does not exist on any other span in the trace, in addition to narrowing that has been selected by the other WHERE conditions

Now, the errors are only reported when one specific product isn't part of the trace with the error.

This final trace diagram shows the relationship between the root span, the span with an error, its exception event, the app.user.id from another span, the app.shipping.amount from another span, the app.payment.amount from yet another span, but only as long as none of the spans in the trace for this error is for one specific product.

An odd request, but it can be done!

Check exactly where the error happened

Finally, we can restrict our errors to the ones that exist at the API surface layer with the parent keyword.

users can also use parent to check the attributes of the span directly related to the erroring span as a parent span id. In this case, it has to have a service name of api-gateway, so we know the error is related to API calls.

This makes sure the span directly above the erroring span is in the api-gateway service. A partial extract now only shows trace spans from the API gateway.

The erroring trace spans only come from the api-gateway calls, since we're now checking the service name of the parent and making sure it originates from api-gateway.

Add trace.trace_id to the GROUP BY to make it easy for anyone to view a trace right from the query results. Our query reach has extended!

Now, we're referencing seven different spans, including the span with the error. Plus, we're excluding any traces involving one specific product with the none expression.

Additional techniques

Here are a few more ideas you can leverage.

Adding attributes to additional spans

if you could put all of these attributes on every span from your code, great! Honeycomb doesn't charge for additional attributes for this reason. Then you can use them in a visualization like HEATMAP or SUM.

Have your agent write the query

Your agent is smart enough to get information across different spans, and will use some of these techniques. If you want to have the agent do something specific, like referencing parent, root, and additional spans, you can use relational query operation language in your request.

Use the query in your favorite agent or another environment

If you've connected your agent to the Honeycomb MCP, you can grab the query URL from Honeycomb, share it in Slack or email, or tell the Honeycomb MCP (or Canvas) to run the query based on it.

Wrap-up

We started off with a challenge: grabbing information across a set of events in a single trace to use in a Honeycomb Board.

Using relational query operators, we were able to connect our results across services from the entire business. We are now able to view attributes from more than just a single service in the Honeycomb Overview tab (and in Board tables), and we can narrow down error details, reported from a specific service, at particular times, from trace spans. Magic!