Skip to content

Instantly share code, notes, and snippets.

@killnine
Created March 26, 2020 20:39
Show Gist options
  • Save killnine/af024fed8ba1488ede881d333dd5a346 to your computer and use it in GitHub Desktop.
Save killnine/af024fed8ba1488ede881d333dd5a346 to your computer and use it in GitHub Desktop.
Creating multiple spans within a transaction for Elastic APM
[HttpGet]
public async Task<IActionResult> TraceSingleBlockCall()
{
await Agent.Tracer.CaptureTransaction("TraceSingleBlockCall", "Request", async (transaction) =>
{
transaction.Labels["Foo"] = "Bar";
//Unit of work 1
await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 1", "TraceSingleBlockCall", async (span) =>
{
await Task.Delay(50);
});
//Unit of work 2
await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 2", "TraceSingleBlockCall", async (span) =>
{
await Task.Delay(250);
});
//Unit of work 3
await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 3", "TraceSingleBlockCall", async (span) =>
{
await Task.Delay(5);
});
});
return Ok();
}
@killnine
Copy link
Author

This doesn't do what I'd expect: create 3 spans within the single transaction
image

@killnine
Copy link
Author

Also, no Metadata gets written out...
image

Adding insult to injury, the link for Metadata/Labels is broke:
image

@gregkalapos
Copy link

gregkalapos commented Mar 26, 2020

Hey @killnine,

the reason for this is that you start a new transaction within the controller - so what you created in the code will probably show up as a separate new transaction.

Probably something like this would do what you want:

[HttpGet]
public async Task<IActionResult> TraceSingleBlockCall()
{
	await Agent.Tracer.CurrentTransaction.CaptureSpan("TraceSingleBlockCall", "Request", async (span) =>
	{
		//Add label to the current transaction:
		Agent.Tracer.CurrentTransaction.Labels["Foo"] = "Bar";
		//Unit of work 1
		await span.CaptureSpan("Unit of Work 1", "TraceSingleBlockCall",
			async (innerSpan) => { await Task.Delay(50); });
		//Unit of work 2
		await span.CaptureSpan("Unit of Work 2", "TraceSingleBlockCall",
			async (innerSpan) => { await Task.Delay(250); });
		//Unit of work 3
		await span.CaptureSpan("Unit of Work 3", "TraceSingleBlockCall", async (span) => { await Task.Delay(5); });
	});
	return Ok();
}

I changed Agent.Tracer.CaptureTransaction to Agent.Tracer.CurrentTransaction.CaptureSpan (1. line in the method) - the difference is that Agent.Tracer.CurrentTransaction will get you the currently active transaction - and since ASP.NET Core auto-instrumentation seems to be active here, Agent.Tracer.CurrentTransaction will simply give you the transaction that the agent automatically created for the incoming HTTP request.

Agent.Tracer.CaptureTransaction will always create a new transaction - and ignore the existing one.

Also - what really happens in the code above is that we have span on the transaction called TraceSingleBlockCall and that span has 3 other sub-spans. So overall this creates 4 spans - not 3.

I pasted the code into another controller - so ignore the URL and the error - other than those this shows you the result:

image

If you just want to have 3 spans on the existing transaction, do this:

[HttpGet]
public async Task<IActionResult> TraceSingleBlockCall()
{
	await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 1", "TraceSingleBlockCall",
		async (span) => { await Task.Delay(50); });
	await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 2", "TraceSingleBlockCall",
		async (span) => { await Task.Delay(250); });
	await Agent.Tracer.CurrentTransaction.CaptureSpan("Unit of Work 2", "TraceSingleBlockCall",
		async (span) => { await Task.Delay(5); });
	return Ok();
}

Since the ASP.NET Core instrumentation already created the transaction in this case, we just grab it with Agent.Tracer.CurrentTransaction and start the 3 spans.

Also one important note: here we assume that Agent.Tracer.CurrentTransaction is never null. The CaptureSpan is a convenient method, if you wanna prepare for everything use the StartSpan method:

[HttpGet]
public async Task<IActionResult> TraceSingleBlockCall()
{
	var span = Agent.Tracer?.CurrentTransaction.StartSpan("Unit of Work", "TraceSingleBlockCall");
	try
	{
		await Task.Delay(50);
	}
	catch (Exception e)
	{
		span?.CaptureException(e);
		throw;
	}
	span?.End();
	return Ok();
}

Hope this helps.

Also, sorry about the broken link - we already know about it and we work on it. Here is the correct link.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment