After you've turned on code-level metrics using the steps in Go agent code-level metrics configuration, you can fine-tune how your metrics are collected by using additional instrumentation.
Instrumenting transactions
With code-level metrics enabled, the Go agent will add source code context information to any transaction started by a call to the StartTransaction
method of the application. Without changing any of your existing instrumentation code, this means that the source code location will be added based on the function which called StartTransaction
. This is true even if StartTransaction
is called for you by an instrumentation package.
However, if you wish to exert some manual control over how code-level metrics are collected for a particular transaction, you may add a number of newrelic.TraceOption
functions as described below:
Suppressing code-level metrics for a single transaction
If you don't want code-level metrics for a particular transaction (say, to avoid the overhead of collecting the information when you know you won't need that data), add a WithoutCodeLevelMetrics
function to the end of the StartTransaction
call:
txn := app.StartTransaction("nothing-here-to-see", newrelic.WithoutCodeLevelMetrics())defer txn.End()
Adjusting the file path prefix
By default, the Go agent will report full (absolute) file pathnames for the source files referenced by code-level metrics. The configuration guide has information about how you can truncate file pathnames globally to start at your choice of directory name via the ConfigCodeLevelMetricsPathPrefixes
setting, but you may need to specify a different file pathname prefix for individual transactions. If so, just add a WithPathPrefixes
option to the transaction:
txn := app.StartTransaction("mytransaction", newrelic.WithPathPrefixes("otherproject/lib/src"))defer txn.End()
This means that, for this transaction only, a source file pathname of, for example, "/usr/src/otherproject/lib/src/main.go"
is shortened to "otherproject/lib/src/main.go"
.
If you have multiple path prefixes you wish to use, simply pass them as additional parameters to WithPathPrefixes
:
txn := app.StartTransaction("mytransaction", newrelic.WithPathPrefixes("otherproject/lib/src", "myproject/src"))defer txn.End()
Adjusting the function recognition heuristic's ignore prefix
By default, the Go agent contains logic to skip over functions in the call stack which are internal to the agent itself, in order to arrive at the one you are instrumenting. The configuration guide
has information about how you can globally change this heuristic via the ConfigCodeLevelMetricsIgnoredPrefixes
setting, but you may wish to provide a custom namespace ignore prefix for a single transaction. You can do so by adding a WithIgnoredPrefixes
option to the transaction:
txn := app.StartTransaction("mytransaction", newrelic.WithIgnoredPrefixes("github.com/ignore/this/"))defer txn.End()
You can specify multiple string arguments to WithIgnoredPrefixes
if there are multiple packages whose functions should be ignored:
txn := app.StartTransaction("mytransaction", newrelic.WithIgnoredPrefixes("github.com/ignore/this/", "github.com/ignore/that/"))defer txn.End()
With this change, any functions from a package whose name begins with github.com/ignore/this/
(or github.com/ignore/that/
in the second example) will be skipped over to find the function being instrumented.
Explicitly mark the code location
To make it easier to identify the point of interest to be reported, add WithThisCodeLocation
to the end of the StartTransaction
call (for example, if the transaction is actually started inside another framework). This will force the code-level metrics to report the location in the source code where WithThisCodeLocation
was invoked.
txn := app.StartTransaction("mytransaction", newrelic.WithThisCodeLocation())defer txn.End()
Explicitly mark any code location
You can also fully control the location in your source code to be associated with a transaction. In essence you can mark any location by calling ThisCodeLocation
to get a "marker" for that location. Then, use that saved marker with the WithCodeLocation
option to a StartTransaction
call:
here := newrelic.ThisCodeLocation()...txn := app.StartTransaction("mytransaction", newrelic.WithCodeLocation(here))defer txn.End()
If needed, you can tell ThisCodeLocation
to skip a number of calling functions so that the reported location is farther up the call stack. For example, to skip 1 caller, so that here
refers to the caller of the function that calls ThisCodeLocation
, change the above example to:
here := newrelic.ThisCodeLocation(1)...txn := app.StartTransaction("mytransaction", newrelic.WithCodeLocation(here))defer txn.End()
Explicitly mark the code location based on a function value
If the code you wish to instrument is available to you as a func
type value, such as the name of a function or a function literal value, you can specify that as the location for code-level metrics reporting by adding a WithFunctionLocation
option to StartTransaction
, passing the func
value as a parameter:
func myfunction() { ... }...txn := app.StartTransaction("myfunction", newrelic.WithFunctionLocation(myfunction))
or
someFunction := func() {...}...txn := app.StartTransaction("myfunction", newrelic.WithFunctionLocation(someFunction))
You may also obtain a CodeLocation
value from a func
value for later use with the WithCodeLocation
option. Compare this to the usage shown above to save a code location with ThisCodeLocation
for later reference with WithCodeLocation
. This time we do the same thing but with a func
value instead:
func myFunc() {...}...locationOfMyFunc, err := newrelic.FunctionLocation(myFunc)if err != nil { // handle the case that the location could not be determined // from the value passed to FunctionLocation.}...txn := app.StartTransaction("mytransaction", newrelic.WithCodeLocation(locationOfMyFunc))
Note that FunctionLocation
returns an error if the value passed to it was not a valid function, or the source code location could not be obtained from it. By contrast, the WithFunctionLocation
option sets the code-level metrics based on the value passed if possible, but silently does nothing if an error occurs.
Adding custom options to wrapped handlers
The same options described above which may be added to the end of StartTransaction
may also be added to WrapHandle
and WrapHandleFunc
to adjust the code-level metrics collection associated with transactions started by them, if desired (although in most cases the WrapHandle
and WrapHandleFunc
functions will correctly identify the location of the code being instrumented). For example:
http.HandleFunc(newrelic.WrapHandleFunc(app, "/endpoint", endpointFunction, newrelic.WithThisCodeLocation()))
Changing trace options after a transaction has started
Sometimes you don't have the luxury of knowing the code location until after the transaction has already been started (perhaps by a framework or integration function on your behalf). You may change the transaction options on an existing transaction by calling its SetOption
method. The parameters are a set of TraceOption
functions just as described above. For example:
txn := newrelic.FromContext(r.context)txn.SetOption(newrelic.WithThisCodeLocation())
Improving performance with cached code-level metrics functions
Often the most convenient way to instrument a transaction with code-level metrics is to call newrelic.WithThisCodeLocation()
(or similar function as described above) inside the instrumented function. However, if that function is going to be called many times throughout the runtime of your application, it would be preferable to avoid the overhead of repeatedly computing the source code location. This is especially true if the transaction code will be run concurrently in many goroutines.
To mitigate this, the Go agent provides caching versions of several of these functions. They operate the same as their uncached counterparts do, except that they only do the work of figuring out the source code location the first time they are called, and then simply reuse that value every subsequent time.
In order to make use of this feature, you need to create a cache storage variable by calling NewCachedCodeLocation()
and put it where it will persist between executions of the instrumented transaction.
This variable will hold the cached value for that code location. Then simply use the methods FunctionLocation(functionValue)
or
ThisCodeLocation()
just as you would use the stand-alone functions of the same names, but in this case they are methods of your CachedCodeLocation
variable.
These methods are thread-safe, so you may use them in concurrent goroutines without adding any additional concurrency controls to them.
For example, in this code we set up a cache variable which is used in the closure assigned to the myFunc
variable.
cache := newrelic.NewCachedCodeLocation()
myFunc := func() { txn := app.StartTransaction("mytransaction", cache.WithThisCodeLocation()) defer txn.End() // go on to perform the transaction}
(This example assumes that app
is the agent's Application
value created when the agent was started, and is visible to this code snippet.)
Now we can call the myFunc
function many times. Each (possibly concurrent) invocation of myFunc
has a reference to the cache
variable. The first invocation
to execute cache.WithThisCodeLocation()
will compute the source code location at that point, and store it in the cache
variable. Subsequent executions of myFunc
will reuse the value previously stored in cache
.
Note that you must use a different cache variable for each code location you want to use, since the point is to only evaluate that location once and then use the cached value from there. The cache variable is not intended to be copied or used other than as documented here and in the module package documentation.
Please refer to the full Go module package documentation for more details about all of the cached code-level metrics functions and methods you may employ.