Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func main() {

if flag == "true" {
// 3. use the middleware
e.Use(apmecho.Middleware(DefaultComponentName))
e.Use(apmecho.Middleware(opentracing.GlobalTracer(),
apmecho.ComponentName(DefaultComponentName)))
}

e.GET("/", func(c echo.Context) error {
Expand Down
8 changes: 5 additions & 3 deletions examples/server.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package main

import (
"net/http"
"os"

"github.com/labstack/echo/v4"
apmecho "github.com/opentracing-contrib/echo"
"github.com/opentracing-contrib/echo/examples/tracer"
"github.com/opentracing/opentracing-go"
"net/http"
"os"
)

const (
Expand All @@ -32,7 +33,8 @@ func main() {

if flag == "true" {
// 3. use the middleware
e.Use(apmecho.Middleware(DefaultComponentName))
e.Use(apmecho.Middleware(opentracing.GlobalTracer(),
apmecho.ComponentName(DefaultComponentName)))
}

e.GET("/", func(c echo.Context) error {
Expand Down
7 changes: 4 additions & 3 deletions examples/tracer/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package tracer

import (
"fmt"
"io"

"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go/config"
"io"
)

// Init creates a new instance of Jaeger tracer.
Expand All @@ -13,7 +14,7 @@ func Init(serviceName string) (opentracing.Tracer, io.Closer) {
cfg, err := config.FromEnv()

if err != nil {
fmt.Println("cannot parse jaeger env vars: %v\n", err.Error())
fmt.Printf("cannot parse jaeger env vars: %v\n", err.Error())
//os.Exit(1)
return nil, nil
}
Expand All @@ -24,7 +25,7 @@ func Init(serviceName string) (opentracing.Tracer, io.Closer) {

tracer, closer, err := cfg.NewTracer()
if err != nil {
fmt.Println("cannot initialize jaeger tracer: %v\n", err.Error())
fmt.Printf("cannot initialize jaeger tracer: %v\n", err.Error())
//os.Exit(1)
return nil, nil
}
Expand Down
164 changes: 131 additions & 33 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,167 @@
package apm

import (
"fmt"
"net/http"
"net/url"

"github.com/labstack/echo/v4"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"net/http"
"net/url"
)

type mwOptions struct {
opNameFunc func(r *http.Request) string
spanObserver func(span opentracing.Span, r *http.Request)
const defaultComponentName = "labstack/echo"

type options struct {
opNameFunc func(c echo.Context) string
spanFilter func(c echo.Context) bool
spanObserver func(span opentracing.Span, c echo.Context)
urlTagFunc func(u *url.URL) string
componentName string
}

func Middleware(componentName string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Option controls the behavior of the Middleware.
type Option func(*options)

// OperationNameFunc returns a Option that uses given function f
// to generate operation name for each server-side span.
func OperationNameFunc(f func(c echo.Context) string) Option {
return func(o *options) {
o.opNameFunc = f
}
}

r := c.Request();
tracer := opentracing.GlobalTracer()
// ComponentName returns a Option that sets the component name
// for the server-side span.
func ComponentName(componentName string) Option {
return func(o *options) {
o.componentName = componentName
}
}

opts := mwOptions{
componentName: componentName,
opNameFunc: func(r *http.Request) string {
return "HTTP " + r.Method + " " + r.URL.Path
},
spanObserver: func(span opentracing.Span, r *http.Request) {
// SpanFilter returns a Option that filters requests from creating a span
// for the server-side span.
// Span won't be created if it returns false.
func SpanFilter(f func(c echo.Context) bool) Option {
return func(o *options) {
o.spanFilter = f
}
}

// SpanObserver returns a Option that observe the span
// for the server-side span.
func SpanObserver(f func(span opentracing.Span, c echo.Context)) Option {
return func(o *options) {
o.spanObserver = f
}
}

// URLTagFunc returns a Option that uses given function f
// to set the span's http.url tag. Can be used to change the default
// http.url tag, eg to redact sensitive information.
func URLTagFunc(f func(u *url.URL) string) Option {
return func(o *options) {
o.urlTagFunc = f
}
}

// Middleware wraps an http.Handler and traces incoming requests.
// Additionally, it adds the span to the request's context.
//
// By default, the operation name of the spans is set to "HTTP {method}".
// This can be overriden with options.
//
// Example:
// e := echo.New()
// e.Use(apm.Middleware())
// e.GET("/", func(c echo.Context) error {
// return c.String(http.StatusOK, "Hello, World!")
// })
// e.Logger.Fatal(e.Start(":1323"))
//
// The options allow fine tuning the behavior of the middleware.
//
// Example:
// mw := apm.Middleware(
// tracer,
// apm.OperationNameFunc(func(c echo.Context) string {
// return c.Request().Proto + " " + c.Request().Method + ":/api/customers"
// }),
// apm.SpanObserver(func(sp opentracing.Span, c echo.Context) {
// sp.SetTag("http.uri", c.Request().URL.EscapedPath())
// }),
// )
func Middleware(tracer opentracing.Tracer, o ...Option) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {

r := c.Request()
opts := options{
opNameFunc: func(ec echo.Context) string {
url := ec.Path()
if url == "" {
url = ec.Request().URL.String()
}
return ec.Request().Proto + " " + ec.Request().Method + " " + url
},
spanFilter: func(c echo.Context) bool { return true },
spanObserver: func(span opentracing.Span, c echo.Context) {},
urlTagFunc: func(u *url.URL) string {
return u.String()
},
}

for _, opt := range o {
opt(&opts)
}

if !opts.spanFilter(c) {
return next(c)
}

carrier := opentracing.HTTPHeadersCarrier(r.Header)
ctx, _ := tracer.Extract(opentracing.HTTPHeaders, carrier)
op := opts.opNameFunc(r)
sp := opentracing.StartSpan(op, ext.RPCServerOption(ctx))
defer sp.Finish()

op := opts.opNameFunc(c)
sp := tracer.StartSpan(op, ext.RPCServerOption(ctx))

ext.HTTPMethod.Set(sp, r.Method)
ext.HTTPUrl.Set(sp, opts.urlTagFunc(r.URL))
opts.spanObserver(sp, r)
ext.Component.Set(sp, opts.componentName)
opts.spanObserver(sp, c)

componentName := opts.componentName
if componentName == "" {
componentName = defaultComponentName
}
ext.Component.Set(sp, componentName)

r = r.WithContext(opentracing.ContextWithSpan(r.Context(), sp))
c.SetRequest(r)

err := tracer.Inject(sp.Context(), opentracing.HTTPHeaders, carrier)

if err != nil {
panic("SpanContext Inject Error!")
}
defer func() {
if v := recover(); v != nil {
err, ok := v.(error)
if !ok {
err = fmt.Errorf("%v", v)
}

if err := next(c); err != nil {
sp.SetTag("error", true)
c.Error(err)
}
c.Error(err)
ext.HTTPStatusCode.Set(sp, uint16(0))
ext.Error.Set(sp, true)
sp.Finish()
return
}

sp.SetTag("error", false)
ext.HTTPStatusCode.Set(sp, uint16(c.Response().Status))
status := c.Response().Status
ext.HTTPStatusCode.Set(sp, uint16(status))
if status >= http.StatusInternalServerError {
ext.Error.Set(sp, true)
}
sp.Finish()
}()

return nil
err := next(c)
return err

}
}
Expand Down
Loading