Using this example you can sent application logs, transactions and transaction spans to NewRelic. It uses HTTP router from Golang's standard library. You can improve it as you wish because some packages (e.g. logger) are coupled to NewRelic which is not great but this is meant to help you start from some point anyway.


Files


new_relic.go


package xmiddleware

import (
"net/http"

"github.com/newrelic/go-agent/v3/newrelic"
)

type NewRelic struct {
Application *newrelic.Application
}

func (n NewRelic) Handle(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
txn := n.Application.StartTransaction(r.Pattern)
defer txn.End()

txn.SetWebRequestHTTP(r)

w = txn.SetWebResponse(w)
r = newrelic.RequestWithTransactionContext(r, txn)

next.ServeHTTP(w, r)
})
}

router.go


package xhttp

import (
"net/http"
)

type Router struct {
handler *http.ServeMux
}

func NewRouter() *Router {
return &Router{
handler: http.NewServeMux(),
}
}

func (r *Router) Handler() http.Handler {
return r.handler
}

func (r *Router) Add(path string, handler http.HandlerFunc, middlewares ...func(http.Handler) http.Handler) {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler).(http.HandlerFunc)
}

r.handler.HandleFunc(path, handler)
}

logger.go


package logger

import (
"log/slog"
"os"
"time"

"github.com/newrelic/go-agent/v3/integrations/logcontext-v2/logWriter"
"github.com/newrelic/go-agent/v3/newrelic"
)

func Configure(trace *newrelic.Application, severity string) {
var level slog.Level
if err := level.UnmarshalText([]byte(severity)); err != nil {
level = slog.LevelError
}

writer := logWriter.New(os.Stderr, trace)

slog.SetDefault(slog.New(slog.NewJSONHandler(writer, &slog.HandlerOptions{
Level: level,
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
if attr.Key == slog.TimeKey {
attr.Value = slog.StringValue(attr.Value.Time().Format(time.RFC3339))
}

if attr.Key == slog.MessageKey {
attr.Key = "message"
}

return attr
},
})))
}

user.go


package api

import (
"context"
"log/slog"
"net/http"

"github.com/newrelic/go-agent/v3/newrelic"
)

type User struct{}

// HANDLERS --------------------------------------------------------------------

func (u User) Create(w http.ResponseWriter, r *http.Request) {
// This is how it is done!
txn := newrelic.FromContext(r.Context())
defer txn.End()

req := CreateRequest{}
req.Parse(txn)
req.Validate(txn)

slog.InfoContext(r.Context(), "User created")
}

func (u User) Delete(w http.ResponseWriter, r *http.Request) {
// This is not how you use it!
// When `Parse` closes `txn`, `Validate` span will be lost!
req := DeleteRequest{}
req.Parse(r.Context())
req.Validate(r.Context())

slog.Info("User deleted", "data.user_id", r.PathValue("id"))
}

// MODELS ----------------------------------------------------------------------

type CreateRequest struct{}

func (c CreateRequest) Parse(txn *newrelic.Transaction) {
defer txn.StartSegment("CreateRequest.Parse").End()

slog.Debug("parsed request")
}

func (c CreateRequest) Validate(txn *newrelic.Transaction) {
defer txn.StartSegment("CreateRequest.Validate").End()

slog.Debug("validated request")
}

type DeleteRequest struct{}

func (d DeleteRequest) Parse(ctx context.Context) {
txn := newrelic.FromContext(ctx)
defer txn.End()
defer txn.StartSegment("DeleteRequest.Parse").End()

slog.Debug("parsed request")
}

func (d DeleteRequest) Validate(ctx context.Context) {
txn := newrelic.FromContext(ctx)
defer txn.End()
defer txn.StartSegment("DeleteRequest.Validate").End()

slog.Debug("validated request")
}

main.go


package main

import (
"log"
"log/slog"
"net/http"

"rest/logger"
"rest/pkg/xhttp"
"rest/pkg/xmiddleware"
"rest/src/api"

"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
app, err := newrelic.NewApplication(
newrelic.ConfigAppName("Rest-1"),
newrelic.ConfigLicense("licence-goes-here"),
newrelic.ConfigAppLogForwardingEnabled(true),
)
if err != nil {
log.Fatalln(err)
}

logger.Configure(app, "DEBUG")

// HTTP middleware
newRelicMiddleware := (xmiddleware.NewRelic{Application: app}).Handle

// HTTP api
user := api.User{}

// HTTP router
rtr := xhttp.NewRouter()
rtr.Add("POST /api/v1/users", user.Create, newRelicMiddleware)
rtr.Add("DELETE /api/v1/users/{id}", user.Delete, newRelicMiddleware)

// HTTP server
slog.Debug("Server starting")
if err := http.ListenAndServe(":1234", rtr.Handler()); err != nil && err != http.ErrServerClosed {
slog.Error("Server start", "error", err)
} else {
slog.Debug("Server shutdown")
}
}

Test


$ curl --location --request POST 'http://localhost:1234/api/v1/users'

$ curl --location --request DELETE 'http://localhost:1234/api/v1/users/user-1'