Are you building modern apps in Golang? This multi-part blog series will help you instrument Golang faster by providing tips, guidance, and best practices with New Relic.

Typically, the content in this blog would be presented in a workshop, but we've made it a blog series so anyone can follow along "hands on," using sample code, Instruqt, this blog, and videos. For the best experience:

The following two videos will give an overview of Instruqt and some instructions about how to use it if you haven't done that before.

Overview of Instruqt

Instruqt UI navigation

Now that you're set up, let's learn a little more about the instrumentation of Golang.

Tip 4: Transactions and segments

The New Relic Go instrumentation revolves around two fundamental concepts: Transactions and segments. These concepts are essential to understanding Go integrations and examples.

Transactions are a crucial aspect of Go instrumentation. It refers to a logical unit of work in a software application comprising function calls and method calls executed during a specific time frame. A transaction usually represents a key transaction encompassing the activity from when the application receives a request to when it sends the response.

Transactions are crucial because they help developers identify the slowest parts of the code. By tracking these transactions, developers can identify bottlenecks and optimize the performance of the application. This information can then be used to optimize the user experience and improve customer satisfaction.

Here's an example of monitoring a web transaction as seen in our docs:

// Begin a new transaction with a specified name and defer its ending.
txn := app.StartTransaction("transaction_name")
defer txn.End()

// req variable of type *http.Request to mark the transaction as a web transaction.
txn.SetWebRequestHTTP(req)

// writer is a http.ResponseWriter, use the returned writer in place of the original.
writer = txn.SetWebResponse(writer)
writer.WriteHeader(500)

In software applications, a transaction may consist of multiple segments representing a specific part of the process. For instance, a transaction in an ecommerce platform may involve adding items to a shopping cart, processing payment, and updating inventory levels.

To gain insights into these transactions' performance, measuring the time taken by each segment is essential. By instrumenting segments, you can track the time taken by functions and code blocks that make up the segments, such as external calls, datastore calls, adding messages to queues, and background tasks. This information is powerful for gaining insights into the time taken by specific functions and code blocks.

Here's an example of a segment as seen in our docs:

segment := newrelic.Segment{}
segment.Name = "yourSegmentName"
segment.StartTime = txn.StartSegmentNow()
// insert your code logic here
segment.End()

To effectively instrument Go, it is important to understand the three types of segments. Choosing the appropriate segment is essential to ensure that they appear correctly in the UI. Properly ending each transaction or segment is mandatory, as failure to do so will result in the instrumented function not appearing in New Relic.

Function segments: Instrumenting a function as a segment is equivalent to instrumenting any other block of code as a segment. This means that the process of measuring the execution time of a function or any arbitrary code block is essentially the same.

Here's an example of a function segment as seen in our docs:

segment := txn.StartSegment("yourSegmentName")
// insert your code logic here
segment.End()

Datastore Segments: If you want to improve the performance of your datastore calls, you can use a datastore segment to instrument them. By doing so, you'll be able to gather more detailed insights into how your datastore is performing and identify any areas that may be causing issues. A datastore segment allows you to see the resulting datastore segments in the transactions breakdown table and Databases tab of the transactions page within New Relic. This gives you access to more granular data about the performance of your datastore, which can help you make more informed decisions about optimizing it for your specific needs.

Here's an example of a datastore segment as seen in our docs:

s := newrelic.DatastoreSegment{
    Product: newrelic.DatastoreMySQL,
    Collection: "users",
    Operation: "INSERT",
    ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)",
    QueryParameters: map[string]interface{}{
        "name": "Dracula",
        "age": 439,
    },
    Host: "mysql-server-1",
    PortPathOrID: "3306",
    DatabaseName: "my_database",
}
s.StartTime = txn.StartSegmentNow()
// insert your code logic here
s.End()

External segments: For your Go application's external service calls, such as web services, cloud resources, and other network requests, you can instrument them with external segments. This allows the resulting external segments to be displayed in the transactions breakdown table and external services page within New Relic.

Here's an example of a external segment as seen in our docs:

func external(txn *newrelic.Transaction, req *http.Request) (*http.Response, error) {
    s := newrelic.StartExternalSegment(txn, req)
    response, err := http.DefaultClient.Do(req)
    s.Response = response
    s.End()
    return response, err
}

In summary, here's a sample code of how everything fits together:

func randomFormat() string {

	//Monitor a transaction
	nrTxnTracer := nrApp.StartTransaction("randomFormat")
	defer nrTxnTracer.End()

	// Random sleep to simulate delays
	randomDelayOuter := rand.Intn(40)
	time.Sleep(time.Duration(randomDelayOuter) * time.Microsecond)

	// Create a segment
	nrSegment := nrTxnTracer.StartSegment("Formats")

	// Random sleep to simulate delays
	randomDelayInner := rand.Intn(80)
	time.Sleep(time.Duration(randomDelayInner) * time.Microsecond)

	// A slice of message formats.
	formats := []string{
		"Hi, %v. Welcome!",
		"Great to see you, %v!",
		"Good day, %v! Well met!",
		"%v! Hi there!",
		"Greetings %v!",
		"Hello there, %v!",
	}

	// End a segment
	nrSegment.End()

	// Return a randomly selected message format by specifying
	// a random index for the slice of formats.
	return formats[rand.Intn(len(formats))]
}

Datastore Transactions in New Relic

Datastore request with Distributed Tracing in New Relic

Datastore Segments in New Relic

Tip 5: Custom attributes

Utilizing custom attributes can be highly beneficial when using a New Relic such as APM, browser, mobile, infrastructure, and synthetics. This enhancement allows for the addition of personalized metadata to existing events. Custom attributes provide essential business and operational context to existing events, consisting of key-value pairs that offer metadata related to the associated events. This provides the ability to improve custom charts and queries to analyze the data, as discussed in Tip 1: Learn why you should instrument.

Various types of business context can be added as custom attributes, including customer ID, customer market segment, customer value classification, or transaction identifiers. Operational context can also be added as custom attributes, including feature flags that were utilized, datastore used, infrastructure accessed, or errors detected and ignored.

Here's an example of AddAttribute() to add metadata to your transactions:

txn.AddAttribute("product", "shoes")
txn.AddAttribute("price", 69.90)
txn.AddAttribute("onSale", true)

See additional details using the Go agent API, including AddAttribute().

Here's an example of capturing a custom attribute:

func Hello(name string) (string, error) {

	// Monitor a transaction
	nrTxnTracer := nrApp.StartTransaction("Hello")
	defer nrTxnTracer.End()

	// If no name was given, return an error with a message.
	if name == "" {
		return name, errors.New("empty name")
	}

	// Create a message using a random format.
	message := fmt.Sprintf(randomFormat(), name)

	// Custom attributes by using this method in a transaction
	nrTxnTracer.AddAttribute("message", message)
	return message, nil
}

Instrumented Goroutines in New Relic

Tip 6: Modularize your instrumentation

At some point, you will need to attach instrumentation at scale throughout your environment. This may seem daunting, but it' a critical step in ensuring your application performs optimally. Consider discussing with your team or engineering lead to determine the best strategic location for instrumenting all of your request calls.

This could include examining the architecture of your application to identify potential chokepoints and considering the types of metrics you want to collect. You may also want to consider any potential tradeoffs between the depth and breadth of instrumentation. Ultimately, the key is to approach this process thoughtfully and systematically, ensuring that your instrumentation strategy aligns with your broader goals and objectives.

We recommend instrumenting the middleware layer with a wrapper, as demonstrated in this example using the Gin framework. This package, nrgin, instruments https://github.com/gin-gonic/gin applications.

router := gin.Default()
// add the nrgin middleware as the first middleware or route in your application.
router.Use(nrgin.Middleware(app))

The middleware creates a Gin middleware that instrument all requests. Use this package to instrument inbound requests handled by a gin.Engine. Call nrgin.Middleware to get a gin.HandlerFunc, which can be added to your application as a middleware.

router := gin.Default()

// Package nrgin instruments https://github.com/gin-gonic/gin applications.
router.Use(nrgin.Middleware(nrApp))

router.GET("/games", getgames)
router.GET("/games/:id", getgameByID)
router.POST("/games", postgames)

Gin Transactions in New Relic

For more information, don't forget to visit the official page for the New Relic Go agent.