03/09/2024 - GO
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.
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)
})
}
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)
}
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
},
})))
}
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")
}
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")
}
}
$ curl --location --request POST 'http://localhost:1234/api/v1/users'
$ curl --location --request DELETE 'http://localhost:1234/api/v1/users/user-1'