Skip to content

Commit

Permalink
Add NewRelic integration
Browse files Browse the repository at this point in the history
Can be enabled by adding following section:

  "newrelic": {
      "app_name": "<name>",
      "license_key": "<key>"
  }

Updates gorilla/mux to 1.6.0
  • Loading branch information
nezorflame authored and buger committed Jan 11, 2018
1 parent aae4bf9 commit 2580fd3
Show file tree
Hide file tree
Showing 112 changed files with 10,724 additions and 2,174 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.idea/
.vagrant/
.vscode/
temp/
/middleware/bundles
/middleware/e1d21f942ec746ed416ab97fe1bf07e8/
Expand All @@ -23,6 +24,7 @@ build/
/tyk
logs/
*.mprof
*.cov
/apps/test.json
logs
lint_results.txt
Expand Down
7 changes: 7 additions & 0 deletions api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,13 @@ func loadGlobalApps() {
copy(specs, apiSpecs)
apisMu.RUnlock()
loadApps(specs, mainRouter)

if config.Global.NewRelic.AppName != "" {
log.WithFields(logrus.Fields{
"prefix": "main",
}).Info("Adding NewRelic instrumentation")
AddNewRelicInstrumentation(NewRelicApplication, mainRouter)
}
}

// Create the individual API (app) specs based on live configurations and assign middleware
Expand Down
6 changes: 6 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ type SecurityConfig struct {
Certificates CertificatesConfig `json:"certificates"`
}

type NewRelicConfig struct {
AppName string `json:"app_name"`
LicenseKey string `json:"license_key"`
}

// Config is the configuration object used by tyk to set up various parameters.
type Config struct {
// OriginalPath is the path to the config file that was read. If
Expand Down Expand Up @@ -263,6 +268,7 @@ type Config struct {
LogLevel string `json:"log_level"`
Security SecurityConfig `json:"security"`
EnableKeyLogging bool `json:"enable_key_logging"`
NewRelic NewRelicConfig `json:"newrelic"`
}

type CertData struct {
Expand Down
1 change: 0 additions & 1 deletion coprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ func (c *CoProcessor) ObjectFromRequest(r *http.Request) *coprocess.Object {

object.HookType = c.HookType

object.Metadata = make(map[string]string)
object.Spec = make(map[string]string)

// Append spec data:
Expand Down
12 changes: 12 additions & 0 deletions lint/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,18 @@ const confSchema = `{
},
"enable_key_logging": {
"type": "boolean"
},
"newrelic": {
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"app_name": {
"type": "string"
},
"license_key": {
"type": "string"
}
}
}
}
}`
10 changes: 10 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"sync"
"time"

"github.com/newrelic/go-agent"

"github.com/Sirupsen/logrus"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
logstashHook "github.com/bshuster-repo/logrus-logstash-hook"
Expand Down Expand Up @@ -58,6 +60,7 @@ var (
RPCListener RPCStorageHandler
DashService DashboardServiceSender
CertificateManager *certs.CertificateManager
NewRelicApplication newrelic.Application

apisMu sync.RWMutex
apiSpecs []*APISpec
Expand Down Expand Up @@ -201,6 +204,10 @@ func setupGlobals() {
}

CertificateManager = certs.NewCertificateManager(getGlobalStorageHandler("cert-", false), certificateSecret, log)

if config.Global.NewRelic.AppName != "" {
NewRelicApplication = SetupNewRelic()
}
}

func buildConnStr(resource string) string {
Expand Down Expand Up @@ -364,6 +371,7 @@ func loadAPIEndpoints(muxer *mux.Router) {
"prefix": "main",
}).Info("Control API hostname set: ", hostname)
}

if *httpProfile {
muxer.HandleFunc("/debug/pprof/{_:.*}", pprof_http.Index)
}
Expand Down Expand Up @@ -1164,6 +1172,7 @@ func start() {
//DefaultQuotaStore.Init(getGlobalStorageHandler(CloudHandler, "orgkey.", false))
DefaultQuotaStore.Init(getGlobalStorageHandler("orgkey.", false))
}

if config.Global.ControlAPIPort == 0 {
loadAPIEndpoints(mainRouter)
}
Expand Down Expand Up @@ -1281,6 +1290,7 @@ func startDRL() {
type mainHandler struct{}

func (_ mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
AddNewRelicInstrumentation(NewRelicApplication, mainRouter)
mainRouter.ServeHTTP(w, r)
}

Expand Down
7 changes: 7 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/gocraft/health"
"github.com/justinas/alice"
newrelic "github.com/newrelic/go-agent"
"github.com/paulbellamy/ratecounter"
cache "github.com/pmylund/go-cache"

Expand Down Expand Up @@ -53,6 +54,12 @@ func createMiddleware(mw TykMiddleware) func(http.Handler) http.Handler {

return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if config.Global.NewRelic.AppName != "" {
if txn, ok := w.(newrelic.Transaction); ok {
defer newrelic.StartSegment(txn, mw.Name()).End()
}
}

job := instrument.NewJob("MiddlewareCall")
meta := health.Kvs{
"from_ip": requestIP(r),
Expand Down
3 changes: 1 addition & 2 deletions mw_js_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ func specToJson(spec *APISpec) string {

// ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
func (d *DynamicMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {

t1 := time.Now().UnixNano()

// Createthe proxy object
// Create the proxy object
defer r.Body.Close()
originalBody, err := ioutil.ReadAll(r.Body)
if err != nil {
Expand Down
96 changes: 96 additions & 0 deletions newrelic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

import (
"fmt"
"strconv"

"github.com/Sirupsen/logrus"
"github.com/gocraft/health"
"github.com/gorilla/mux"
"github.com/newrelic/go-agent"
"github.com/newrelic/go-agent/_integrations/nrgorilla/v1"

"github.com/TykTechnologies/tyk/config"
)

// SetupNewRelic creates new newrelic.Application instance
func SetupNewRelic() (app newrelic.Application) {
var err error
logger := log.WithFields(logrus.Fields{"prefix": "newrelic"})

logger.Info("Initializing NewRelic...")

cfg := newrelic.NewConfig(config.Global.NewRelic.AppName, config.Global.NewRelic.LicenseKey)
if config.Global.NewRelic.AppName != "" {
cfg.Enabled = true
}
cfg.Logger = &newRelicLogger{logger}

if app, err = newrelic.NewApplication(cfg); err != nil {
logger.Warn("Error initializing NewRelic, skipping... ", err)
return
}

instrument.AddSink(&newRelicSink{relic: app})
logger.Info("NewRelic initialized")

return
}

// AddNewRelicInstrumentation adds NewRelic instrumentation to the router
func AddNewRelicInstrumentation(app newrelic.Application, r *mux.Router) {
if app != nil {
nrgorilla.InstrumentRoutes(r, app)
}
}

type newRelicLogger struct{ *logrus.Entry }

func (l *newRelicLogger) Error(msg string, c map[string]interface{}) {
l.WithFields(c).Error(msg)
}
func (l *newRelicLogger) Warn(msg string, c map[string]interface{}) {
l.WithFields(c).Warn(msg)
}
func (l *newRelicLogger) Info(msg string, c map[string]interface{}) {
l.WithFields(c).Info(msg)
}
func (l *newRelicLogger) Debug(msg string, c map[string]interface{}) {
l.WithFields(c).Debug(msg)
}
func (l *newRelicLogger) DebugEnabled() bool {
return l.Level >= logrus.DebugLevel
}

type newRelicSink struct {
relic newrelic.Application
health.Sink
}

func (s *newRelicSink) EmitEvent(job string, event string, kvs map[string]string) {
s.relic.RecordCustomEvent(job+":"+event, makeParams(kvs))
}

func (s *newRelicSink) EmitEventErr(job string, event string, err error, kvs map[string]string) {
s.relic.RecordCustomEvent(job+":"+event+":msg:"+err.Error(), makeParams(kvs))
}

func (s *newRelicSink) EmitTiming(job string, event string, nanoseconds int64, kvs map[string]string) {
s.relic.RecordCustomEvent(job+":"+event+":dur(ns):"+strconv.FormatInt(nanoseconds, 10), makeParams(kvs))
}

func (s *newRelicSink) EmitComplete(job string, status health.CompletionStatus, nanoseconds int64, kvs map[string]string) {
s.relic.RecordCustomEvent(job+":health:"+status.String()+":dur(ns):"+strconv.FormatInt(nanoseconds, 10), makeParams(kvs))
}

func (s *newRelicSink) EmitGauge(job string, event string, value float64, kvs map[string]string) {
s.relic.RecordCustomEvent(job+":"+event+":value:"+fmt.Sprintf("%.2f", value), makeParams(kvs))
}

func makeParams(kvs map[string]string) (params map[string]interface{}) {
params = make(map[string]interface{}, len(kvs))
for k, v := range kvs {
params[k] = v
}
return
}
9 changes: 7 additions & 2 deletions rpc_backup_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import (
"net/http"
"strings"

"github.com/Sirupsen/logrus"
"github.com/gorilla/mux"

"github.com/TykTechnologies/tyk/config"
"github.com/TykTechnologies/tyk/storage"

"github.com/Sirupsen/logrus"
)

const RPCKeyPrefix = "rpc:"
Expand Down Expand Up @@ -107,6 +106,12 @@ func doLoadWithBackup(specs []*APISpec) {
loadApps(specs, newRouter)
log.Warning("[RPC Backup] --> API Load Done")

if config.Global.NewRelic.AppName != "" {
log.Warning("[RPC Backup] --> Adding NewRelic instrumentation")
AddNewRelicInstrumentation(NewRelicApplication, mainRouter)
log.Warning("[RPC Backup] --> NewRelic instrumentation added")
}

newServeMux := http.NewServeMux()
newServeMux.Handle("/", mainRouter)

Expand Down
66 changes: 66 additions & 0 deletions vendor/github.com/TykTechnologies/logrus/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/TykTechnologies/logrus/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2580fd3

Please sign in to comment.