Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Relic Integration #17

Merged
merged 10 commits into from
Aug 27, 2024
43 changes: 43 additions & 0 deletions docs/modules/newrelic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# New Relic
The New Relic module allows the Chaki application to export telemetry data to New Relic, enabling detailed monitoring and inspection. This module includes various submodules, such as `server`, `client`, `kafka`, and `postgresql`, which can be used to capture specific telemetry data.

To use the New Relic module in your application, you need to declare it as you would with any other module. However, the New Relic module itself requires the use of these submodules to function effectively.

### Usage

First, import the necessary New Relic submodules:

```go
import (
nrpsql "github.com/Trendyol/chaki/modules/newrelic/postgresql"
nrserver "github.com/Trendyol/chaki/modules/newrelic/server"
)

// ...

app := chaki.New()

app.Use(
// Other modules...

newrelic.Module(
nrserver.WithServer(),
nrpsql.WithPostgresql(),
)

// ...
```

### Config

The New Relic configuration requires the following parameters:
```yaml
newrelic:
agentenabled: true
appname: "server-with-newrelic"
licensekey: "top-secret-license-key"
```

- agentenabled: Enable or disable the New Relic agent.
- appname: The name of your application as it will appear in New Relic.
- licensekey: Your New Relic license key for authentication.
10 changes: 10 additions & 0 deletions example/server-with-newrelic/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
gorm:
host:
port:
username:
password:

newrelic:
agentenabled: true
appname: "server-with-newrelic"
licensekey: "top-secret-license-key" # optional
104 changes: 104 additions & 0 deletions example/server-with-newrelic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"context"

"github.com/Trendyol/chaki/modules/newrelic"

"github.com/Trendyol/chaki"
"github.com/Trendyol/chaki/logger"
nrorm "github.com/Trendyol/chaki/modules/newrelic/orm"
nrserver "github.com/Trendyol/chaki/modules/newrelic/server"

"github.com/Trendyol/chaki/modules/orm"
postgresdriver "github.com/Trendyol/chaki/modules/orm/driver/postgres"
"github.com/Trendyol/chaki/modules/otel"
otelserver "github.com/Trendyol/chaki/modules/otel/server"
"github.com/Trendyol/chaki/modules/server"
"github.com/Trendyol/chaki/modules/server/controller"
"github.com/Trendyol/chaki/modules/server/middlewares"
"github.com/Trendyol/chaki/modules/server/route"
"github.com/Trendyol/chaki/modules/swagger"
)

// Same example with postgres, but with newrelic.
func main() {
app := chaki.New()

app.WithOption(
chaki.WithConfigPath("config.yaml"),
)

app.Use(
otel.Module(),
orm.Module(postgresdriver.New()),
otelserver.Module(),
server.Module(),
swagger.Module(),

newrelic.Module(
nrserver.WithServer(),
nrorm.WithGorm(),
),
)

app.Provide(
middlewares.ErrHandler,

newFooRepository,
newHelloController,
)

if err := app.Start(); err != nil {
logger.Fatal(err)
}
}

type getFooReq struct {
Id int `query:"id"`
}

type HelloController struct {
*controller.Base
repo *fooRepository
}

func newHelloController(repo *fooRepository) controller.Controller {
return &HelloController{
Base: controller.New("Hello Controller").SetPrefix("/hello"),
repo: repo,
}
}

func (c *HelloController) Routes() []route.Route {
return []route.Route{
route.Get("/", c.getFoo).Name("Get Foo"),
}
}

func (c *HelloController) getFoo(ctx context.Context, req getFooReq) (*foo, error) {
logger.From(ctx).Info("trace id and spand id will be logged wiht message")
return c.repo.getFoo(ctx, req.Id)
}

type foo struct {
Id int `gorm:"primaryKey"`
Bar string `gorm:"bar"`
}

type fooRepository struct {
gp orm.GormProvider
}

func newFooRepository(gp orm.GormProvider) *fooRepository {
return &fooRepository{gp}
}

func (repo *fooRepository) getFoo(ctx context.Context, id int) (*foo, error) {
f := &foo{}
err := repo.gp.Get(ctx).Where("id = ?", id).Find(f).Error
if err != nil {
return nil, err
}
return f, nil
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ require (
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/go-playground/validator/v10 v10.22.0
github.com/go-resty/resty/v2 v2.13.1
github.com/gofiber/contrib/fibernewrelic v1.3.0
github.com/gofiber/contrib/otelfiber/v2 v2.1.1
github.com/gofiber/fiber/v2 v2.52.5
github.com/google/uuid v1.6.0
github.com/newrelic/go-agent/v3 v3.34.0
github.com/segmentio/kafka-go v0.4.47
github.com/spf13/cast v1.6.0
github.com/spf13/viper v1.18.2
Expand Down Expand Up @@ -91,6 +93,8 @@ require (
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz
github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0=
github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4=
github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc=
github.com/gofiber/contrib/fibernewrelic v1.3.0 h1:dDxwxHI82cmh4WORkzJdWWTwplXsPZhi4qCzhmAxbWM=
github.com/gofiber/contrib/fibernewrelic v1.3.0/go.mod h1:hPufiTeMdAOl1CW2DdZaBY7OC+JEDyX+7aMc/UQauq4=
github.com/gofiber/contrib/otelfiber/v2 v2.1.1 h1:viX4WuGyapgRIEINWZ6Gy8ZngmVkfhSJMJV2Zmhur0E=
github.com/gofiber/contrib/otelfiber/v2 v2.1.1/go.mod h1:52MEjuv8JSiESuedc4yUpi4HiHx2qOGyMrWL78hIHKs=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
Expand Down Expand Up @@ -99,6 +101,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/newrelic/go-agent/v3 v3.34.0 h1:jhtX+YUrAh2ddgPGIixMYq4+nCBrEN4ETGyi2h/zWJw=
github.com/newrelic/go-agent/v3 v3.34.0/go.mod h1:VNsi+XA7YsgF4fHES8l/U6OhAHhU3IdLDFkB/wpevvA=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
Expand Down Expand Up @@ -251,6 +255,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
47 changes: 11 additions & 36 deletions module/module.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
package module

import (
"strings"

"github.com/Trendyol/chaki/as"
"github.com/Trendyol/chaki/util/slc"
)

type condProvide struct {
requireds string
provides []any
}

func (cp *condProvide) match(modules ...string) bool {
rm := strings.Split(cp.requireds, ",")
for _, rmi := range rm {
if slc.Contains(modules, rmi) {
return true
}
}
return false
}

// ProvideHook represents a pre-Provide step so that constructors can be manipulated or grouped
type ProvideHook struct {
Match func(ctr any) bool
Expand All @@ -41,8 +23,7 @@ type Meta struct {
// It makes it possible to group functionalities with modules.
// These functionalities can be imported whenever needed
type Module struct {
meta Meta
condProvides []condProvide
meta Meta
}

func New(name ...string) *Module {
Expand All @@ -63,16 +44,6 @@ func (m *Module) Provide(ctr ...any) *Module {
return m
}

// CondProvide add constructors to provide pool with module import conditions
// Example:
//
// m.CondProvide("foo,bar", NewFooBarService) // provides if foo or bar module is used by application
//
// m.CondProvide("foo", NewFooController)
func (m *Module) CondProvide(requiredModules string, ctr ...any) {
m.condProvides = append(m.condProvides, condProvide{requiredModules, ctr})
}

// Name returns name of the module
func (m *Module) Name() string {
return m.meta.Name
Expand Down Expand Up @@ -102,6 +73,16 @@ func (m *Module) AddAsser(assers ...as.Asser) *Module {
return m
}

// Merge combine sub module providers into module providers
func (m *Module) Merge(sub ...*SubModule) *Module {
for _, s := range sub {
m.Provide(s.provides...)
m.AddProvideHook(s.provideHooks...)
m.AddAsser(s.assers...)
}
return m
}

// Meta returns meta data of the module
func (m *Module) Meta(usedModules ...string) Meta {
r := Meta{
Expand All @@ -116,11 +97,5 @@ func (m *Module) Meta(usedModules ...string) Meta {
return r
}

for _, v := range m.condProvides {
if v.match(usedModules...) {
r.Provides = append(r.Provides, v.provides...)
}
}

return r
}
34 changes: 34 additions & 0 deletions module/submodule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package module

import "github.com/Trendyol/chaki/as"

// SubModule represents a simple provide pool partition of a module
// can not be used as a standalone module with chaki.
// It has to be merged with another module with
type SubModule struct {
provides []any
provideHooks []ProvideHook
assers []as.Asser
}

func NewSubModule() *SubModule {
return &SubModule{
provides: make([]any, 0),
}
}

func (sm *SubModule) AddProvideHook(hooks ...ProvideHook) *SubModule {
sm.provideHooks = append(sm.provideHooks, hooks...)
return sm
}

func (sm *SubModule) AddAsser(assers ...as.Asser) *SubModule {
sm.assers = append(sm.assers, assers...)
return sm
}

// Provide add constructor to provide pool
func (sm *SubModule) Provide(provide ...any) *SubModule {
sm.provides = append(sm.provides, provide...)
return sm
}
33 changes: 33 additions & 0 deletions modules/newrelic/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package client

import (
"net/http"

"github.com/Trendyol/chaki/module"
nrmodule "github.com/Trendyol/chaki/modules/newrelic"
"github.com/newrelic/go-agent/v3/newrelic"
)

type httpRoundTripper struct {
tr http.RoundTripper
}

func newRoundTripper(tr http.RoundTripper) http.RoundTripper {
return &httpRoundTripper{tr}
}

func (t *httpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
txn := newrelic.FromContext(req.Context())
sgm := newrelic.ExternalSegment{
StartTime: txn.StartSegmentNow(),
URL: req.URL.String(),
}
defer sgm.End()

return t.tr.RoundTrip(req)
}

func WithClient() nrmodule.Option {
sm := module.NewSubModule().Provide(newRoundTripper)
return nrmodule.WithSubModule(sm)
}
Loading
Loading