APM Go Client

Hi!

I'm testing Elastic Cloud to see if this is something for us, and I'm first implementing it in our Go application using the Go client (https://github.com/elastic/apm-agent-go).

Right now I'm finding out how I'm going to implement how to start transactions and spans, our application is spread across different modules doing different stuff, so if I want to start a transaction in our top level function for handling http, and want to start spans around in our different modules to track operations I have to pass the transaction around to the functions in other modules to be able to create spans.

This changes just about every function signature and therefore screws up our testing and would require a fair amount of refactoring. is there a way to access the transaction without having to pass it around in function parameters?

And another question, if I implement passing transaction as function parameters, is there a correct way to fake a transaction in testing that don't send anything to the server?

I see that there is a private function fakeTransaction() in https://github.com/elastic/apm-agent-go/blob/693514a5b5fe4364915ab12e14c3a1c108a81eee/model/marshal_test.go

Hey @nicolaigj, welcome to the forum!

This changes just about every function signature and therefore screws up our testing and would require a fair amount of refactoring. is there a way to access the transaction without having to pass it around in function parameters?

Manual propagation is just the way things need to be done in Go, since it lacks anything like thread-local storage.

Rather than passing transactions or spans explicitly, I would recommend you use context. These docs need a bit of love, but demonstrate how you can pass context between functions and start spans using it: https://www.elastic.co/guide/en/apm/agent/go/current/instrumenting-source.html#custom-instrumentation-propagation

So rather than

func doSomething(tx *apm.Transaction) {
    span := tx.StartSpan("name", "type")
    defer span.End()
}

do this instead:

func doSomething(ctx context.Context) {
    span, ctx := apm.StartSpan(ctx, "name", "type")
    defer span.End()
}

Passing context around may have additional benefits, such as being able to propagate other request-scoped data, and request cancellation.

If you specifically want to extract the current transaction from a context object, you can use apm.TransactionFromContext.

And another question, if I implement passing transaction as function parameters, is there a correct way to fake a transaction in testing that don't send anything to the server?

This might be moot if you pass context around instead, since apm.StartSpan will return a no-op span if the context doesn't contain a transaction. Thus if you pass context.Background() into your functions, no spans will be generated.

If you want to test your instrumentation, you could initialise a Tracer a fake transport. The agent has a testing package, go.elastic.co/apm/apmtest, with various things that you could use, such as apmtest.NewRecordingTracer, which can be used for recording events in memory.

1 Like

Thanks for a thorough answer, I frankly didn't scroll past the modules in Instrumenting Go Source Code, so the documentation probably needs to be split up or indexed better.

1 Like

Thanks for a thorough answer, I frankly didn't scroll past the modules in Instrumenting Go Source Code, so the documentation probably needs to be split up or indexed better.

Absolutely - it's a known issue, but thank you for your feedback.