You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-12-24 00:01:31 +02:00
update tracing, fixed docker-compose and removed vendor dir
This commit is contained in:
@@ -5,10 +5,10 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ErrForbidden is returned when an authenticated user does not have a
|
||||
@@ -26,8 +26,8 @@ func Authenticate(authenticator *auth.Authenticator) web.Middleware {
|
||||
|
||||
// Wrap this handler around the next one provided.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.Authenticate")
|
||||
defer span.End()
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.Authenticate")
|
||||
defer span.Finish()
|
||||
|
||||
authHdr := r.Header.Get("Authorization")
|
||||
if authHdr == "" {
|
||||
@@ -65,8 +65,8 @@ func HasRole(roles ...string) web.Middleware {
|
||||
f := func(after web.Handler) web.Handler {
|
||||
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.HasRole")
|
||||
defer span.End()
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.HasRole")
|
||||
defer span.Finish()
|
||||
|
||||
claims, ok := ctx.Value(auth.Key).(auth.Claims)
|
||||
if !ok {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Errors handles errors coming out of the call chain. It detects normal
|
||||
@@ -19,20 +19,13 @@ func Errors(log *log.Logger) web.Middleware {
|
||||
|
||||
// Create the handler that will be attached in the middleware chain.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.Errors")
|
||||
defer span.End()
|
||||
|
||||
// If the context is missing this value, request the service
|
||||
// to be shutdown gracefully.
|
||||
v, ok := ctx.Value(web.KeyValues).(*web.Values)
|
||||
if !ok {
|
||||
return web.NewShutdownError("web value missing from context")
|
||||
}
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.Errors")
|
||||
defer span.Finish()
|
||||
|
||||
if err := before(ctx, w, r, params); err != nil {
|
||||
|
||||
// Log the error.
|
||||
log.Printf("%s : ERROR : %+v", v.TraceID, err)
|
||||
log.Printf("%d : ERROR : %+v", span.Context().TraceID(), err)
|
||||
|
||||
// Respond to the error.
|
||||
if err := web.RespondError(ctx, w, err); err != nil {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Logger writes some information about the request to the logs in the
|
||||
@@ -19,8 +19,8 @@ func Logger(log *log.Logger) web.Middleware {
|
||||
|
||||
// Create the handler that will be attached in the middleware chain.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.Logger")
|
||||
defer span.End()
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.Logger")
|
||||
defer span.Finish()
|
||||
|
||||
// If the context is missing this value, request the service
|
||||
// to be shutdown gracefully.
|
||||
@@ -31,8 +31,8 @@ func Logger(log *log.Logger) web.Middleware {
|
||||
|
||||
err := before(ctx, w, r, params)
|
||||
|
||||
log.Printf("%s : (%d) : %s %s -> %s (%s)\n",
|
||||
v.TraceID,
|
||||
log.Printf("%d : (%d) : %s %s -> %s (%s)\n",
|
||||
span.Context().TraceID(),
|
||||
v.StatusCode,
|
||||
r.Method, r.URL.Path,
|
||||
r.RemoteAddr, time.Since(v.Now),
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// m contains the global program counters for the application.
|
||||
@@ -29,8 +29,8 @@ func Metrics() web.Middleware {
|
||||
|
||||
// Wrap this handler around the next one provided.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.Metrics")
|
||||
defer span.End()
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.Metrics")
|
||||
defer span.Finish()
|
||||
|
||||
err := before(ctx, w, r, params)
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"github.com/pkg/errors"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Panics recovers from panics and converts the panic to an error so it is
|
||||
@@ -19,8 +19,8 @@ func Panics() web.Middleware {
|
||||
|
||||
// Wrap this handler around the next one provided.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) (err error) {
|
||||
ctx, span := trace.StartSpan(ctx, "internal.mid.Panics")
|
||||
defer span.End()
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "internal.mid.Panics")
|
||||
defer span.Finish()
|
||||
|
||||
// Defer a function to recover from a panic and set the err return variable
|
||||
// after the fact. Using the errors package will generate a stack trace.
|
||||
|
||||
64
example-project/internal/mid/trace.go
Normal file
64
example-project/internal/mid/trace.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package mid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
|
||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Trace adds the base tracing info for requests
|
||||
func Trace() web.Middleware {
|
||||
|
||||
// This is the actual middleware function to be executed.
|
||||
f := func(before web.Handler) web.Handler {
|
||||
|
||||
// Wrap this handler around the next one provided.
|
||||
h := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||
// Span options with request info
|
||||
opts := []ddtrace.StartSpanOption{
|
||||
tracer.SpanType(ext.SpanTypeWeb),
|
||||
tracer.ResourceName(r.URL.Path),
|
||||
tracer.Tag(ext.HTTPMethod, r.Method),
|
||||
tracer.Tag(ext.HTTPURL, r.RequestURI),
|
||||
}
|
||||
|
||||
// Continue server side request tracing from previous request.
|
||||
if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil {
|
||||
opts = append(opts, tracer.ChildOf(spanctx))
|
||||
}
|
||||
|
||||
// Start the span for tracking
|
||||
span, ctx := tracer.StartSpanFromContext(ctx, "http.request", opts...)
|
||||
defer span.Finish()
|
||||
|
||||
// If the context is missing this value, request the service
|
||||
// to be shutdown gracefully.
|
||||
v, ok := ctx.Value(web.KeyValues).(*web.Values)
|
||||
if !ok {
|
||||
return web.NewShutdownError("web value missing from context")
|
||||
}
|
||||
|
||||
// Execute the request handler
|
||||
err := before(ctx, w, r, params)
|
||||
|
||||
// Set the span status code for the trace
|
||||
span.SetTag(ext.HTTPCode, v.StatusCode)
|
||||
|
||||
// If there was an error, append it to the span
|
||||
if err != nil {
|
||||
span.SetTag(ext.Error, fmt.Sprintf("%+v", err))
|
||||
}
|
||||
|
||||
// Return the error so it can be handled further up the chain.
|
||||
return err
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Error variables for factory validation.
|
||||
var (
|
||||
ErrLoggerNotProvided = errors.New("logger not provided")
|
||||
ErrHostNotProvided = errors.New("host not provided")
|
||||
)
|
||||
|
||||
// Log provides support for logging inside this package.
|
||||
// Unfortunately, the opentrace API calls into the ExportSpan
|
||||
// function directly with no means to pass user defined arguments.
|
||||
type Log func(format string, v ...interface{})
|
||||
|
||||
// Exporter provides support to batch spans and send them
|
||||
// to the sidecar for processing.
|
||||
type Exporter struct {
|
||||
log Log // Handler function for logging.
|
||||
host string // IP:port of the sidecare consuming the trace data.
|
||||
batchSize int // Size of the batch of spans before sending.
|
||||
sendInterval time.Duration // Time to send a batch if batch size is not met.
|
||||
sendTimeout time.Duration // Time to wait for the sidecar to respond on send.
|
||||
client http.Client // Provides APIs for performing the http send.
|
||||
batch []*trace.SpanData // Maintains the batch of span data to be sent.
|
||||
mu sync.Mutex // Provide synchronization to access the batch safely.
|
||||
timer *time.Timer // Signals when the sendInterval is met.
|
||||
}
|
||||
|
||||
// NewExporter creates an exporter for use.
|
||||
func NewExporter(log Log, host string, batchSize int, sendInterval, sendTimeout time.Duration) (*Exporter, error) {
|
||||
if log == nil {
|
||||
return nil, ErrLoggerNotProvided
|
||||
}
|
||||
if host == "" {
|
||||
return nil, ErrHostNotProvided
|
||||
}
|
||||
|
||||
tr := http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 2,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
|
||||
e := Exporter{
|
||||
log: log,
|
||||
host: host,
|
||||
batchSize: batchSize,
|
||||
sendInterval: sendInterval,
|
||||
sendTimeout: sendTimeout,
|
||||
client: http.Client{
|
||||
Transport: &tr,
|
||||
},
|
||||
batch: make([]*trace.SpanData, 0, batchSize),
|
||||
timer: time.NewTimer(sendInterval),
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
// Close sends the remaining spans that have not been sent yet.
|
||||
func (e *Exporter) Close() (int, error) {
|
||||
var sendBatch []*trace.SpanData
|
||||
e.mu.Lock()
|
||||
{
|
||||
sendBatch = e.batch
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
err := e.send(sendBatch)
|
||||
if err != nil {
|
||||
return len(sendBatch), err
|
||||
}
|
||||
|
||||
return len(sendBatch), nil
|
||||
}
|
||||
|
||||
// ExportSpan is called by opentracing when spans are created. It implements
|
||||
// the Exporter interface.
|
||||
func (e *Exporter) ExportSpan(span *trace.SpanData) {
|
||||
sendBatch := e.saveBatch(span)
|
||||
if sendBatch != nil {
|
||||
go func() {
|
||||
e.log("trace : Exporter : ExportSpan : Sending Batch[%d]", len(sendBatch))
|
||||
if err := e.send(sendBatch); err != nil {
|
||||
e.log("trace : Exporter : ExportSpan : ERROR : %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Saves the span data to the batch. If the batch should be sent,
|
||||
// returns a batch to send.
|
||||
func (e *Exporter) saveBatch(span *trace.SpanData) []*trace.SpanData {
|
||||
var sendBatch []*trace.SpanData
|
||||
|
||||
e.mu.Lock()
|
||||
{
|
||||
// We want to append this new span to the collection.
|
||||
e.batch = append(e.batch, span)
|
||||
|
||||
// Do we need to send the current batch?
|
||||
switch {
|
||||
case len(e.batch) == e.batchSize:
|
||||
|
||||
// We hit the batch size. Now save the current
|
||||
// batch for sending and start a new batch.
|
||||
sendBatch = e.batch
|
||||
e.batch = make([]*trace.SpanData, 0, e.batchSize)
|
||||
e.timer.Reset(e.sendInterval)
|
||||
|
||||
default:
|
||||
|
||||
// We did not hit the batch size but maybe send what
|
||||
// we have based on time.
|
||||
select {
|
||||
case <-e.timer.C:
|
||||
|
||||
// The time has expired so save the current
|
||||
// batch for sending and start a new batch.
|
||||
sendBatch = e.batch
|
||||
e.batch = make([]*trace.SpanData, 0, e.batchSize)
|
||||
e.timer.Reset(e.sendInterval)
|
||||
|
||||
// It's not time yet, just move on.
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
return sendBatch
|
||||
}
|
||||
|
||||
// send uses HTTP to send the data to the tracing sidecare for processing.
|
||||
func (e *Exporter) send(sendBatch []*trace.SpanData) error {
|
||||
data, err := json.Marshal(sendBatch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", e.host, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(req.Context(), e.sendTimeout)
|
||||
defer cancel()
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
ch <- fmt.Errorf("error on call : status[%s]", resp.Status)
|
||||
return
|
||||
}
|
||||
ch <- fmt.Errorf("error on call : status[%s] : %s", resp.Status, string(data))
|
||||
return
|
||||
}
|
||||
|
||||
ch <- nil
|
||||
}()
|
||||
|
||||
return <-ch
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// Success and failure markers.
|
||||
const (
|
||||
success = "\u2713"
|
||||
failed = "\u2717"
|
||||
)
|
||||
|
||||
// inputSpans represents spans of data for the tests.
|
||||
var inputSpans = []*trace.SpanData{
|
||||
{Name: "span1"},
|
||||
{Name: "span2"},
|
||||
{Name: "span3"},
|
||||
}
|
||||
|
||||
// inputSpansJSON represents a JSON representation of the span data.
|
||||
var inputSpansJSON = `[{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span1","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false},{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span2","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false},{"TraceID":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"SpanID":[0,0,0,0,0,0,0,0],"TraceOptions":0,"ParentSpanID":[0,0,0,0,0,0,0,0],"SpanKind":0,"Name":"span3","StartTime":"0001-01-01T00:00:00Z","EndTime":"0001-01-01T00:00:00Z","Attributes":null,"Annotations":null,"MessageEvents":null,"Code":0,"Message":"","Links":null,"HasRemoteParent":false}]`
|
||||
|
||||
// =============================================================================
|
||||
|
||||
// logger is required to create an Exporter.
|
||||
var logger = func(format string, v ...interface{}) {
|
||||
log.Printf(format, v)
|
||||
}
|
||||
|
||||
// MakeExporter abstracts the error handling aspects of creating an Exporter.
|
||||
func makeExporter(host string, batchSize int, sendInterval, sendTimeout time.Duration) *Exporter {
|
||||
exporter, err := NewExporter(logger, host, batchSize, sendInterval, sendTimeout)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to create exporter, ", err)
|
||||
}
|
||||
return exporter
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
var saveTests = []struct {
|
||||
name string
|
||||
e *Exporter
|
||||
input []*trace.SpanData
|
||||
output []*trace.SpanData
|
||||
lastSaveDelay time.Duration // The delay before the last save. For testing intervals.
|
||||
isInputMatchBatch bool // If the input should match the internal exporter collection after the last save.
|
||||
isSendBatch bool // If the last save should return nil or batch data.
|
||||
}{
|
||||
{"NoSend", makeExporter("test", 10, time.Minute, time.Second), inputSpans, nil, time.Nanosecond, true, false},
|
||||
{"SendOnBatchSize", makeExporter("test", 3, time.Minute, time.Second), inputSpans, inputSpans, time.Nanosecond, false, true},
|
||||
{"SendOnTime", makeExporter("test", 4, time.Millisecond, time.Second), inputSpans, inputSpans, 2 * time.Millisecond, false, true},
|
||||
}
|
||||
|
||||
// TestSave validates the save batch functionality is working.
|
||||
func TestSave(t *testing.T) {
|
||||
t.Log("Given the need to validate saving span data to a batch.")
|
||||
{
|
||||
for i, tt := range saveTests {
|
||||
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||
{
|
||||
// Save the input of span data.
|
||||
l := len(tt.input) - 1
|
||||
var batch []*trace.SpanData
|
||||
for i, span := range tt.input {
|
||||
|
||||
// If this is the last save, take the configured delay.
|
||||
// We might be testing invertal based batching.
|
||||
if l == i {
|
||||
time.Sleep(tt.lastSaveDelay)
|
||||
}
|
||||
batch = tt.e.saveBatch(span)
|
||||
}
|
||||
|
||||
// Compare the internal collection with what we saved.
|
||||
if tt.isInputMatchBatch {
|
||||
if len(tt.e.batch) != len(tt.input) {
|
||||
t.Log("\t\tGot :", len(tt.e.batch))
|
||||
t.Log("\t\tWant:", len(tt.input))
|
||||
t.Errorf("\t%s\tShould have the same number of spans as input.", failed)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould have the same number of spans as input.", success)
|
||||
}
|
||||
} else {
|
||||
if len(tt.e.batch) != 0 {
|
||||
t.Log("\t\tGot :", len(tt.e.batch))
|
||||
t.Log("\t\tWant:", 0)
|
||||
t.Errorf("\t%s\tShould have zero spans.", failed)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould have zero spans.", success)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the return provided or didn't provide a batch to send.
|
||||
if !tt.isSendBatch && batch != nil {
|
||||
t.Errorf("\t%s\tShould not have a batch to send.", failed)
|
||||
} else if !tt.isSendBatch {
|
||||
t.Logf("\t%s\tShould not have a batch to send.", success)
|
||||
}
|
||||
if tt.isSendBatch && batch == nil {
|
||||
t.Errorf("\t%s\tShould have a batch to send.", failed)
|
||||
} else if tt.isSendBatch {
|
||||
t.Logf("\t%s\tShould have a batch to send.", success)
|
||||
}
|
||||
|
||||
// Compare the batch to send.
|
||||
if !reflect.DeepEqual(tt.output, batch) {
|
||||
t.Log("\t\tGot :", batch)
|
||||
t.Log("\t\tWant:", tt.output)
|
||||
t.Errorf("\t%s\tShould have an expected match of the batch to send.", failed)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould have an expected match of the batch to send.", success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
var sendTests = []struct {
|
||||
name string
|
||||
e *Exporter
|
||||
input []*trace.SpanData
|
||||
pass bool
|
||||
}{
|
||||
{"success", makeExporter("test", 3, time.Minute, time.Hour), inputSpans, true},
|
||||
{"failure", makeExporter("test", 3, time.Minute, time.Hour), inputSpans[:2], false},
|
||||
{"timeout", makeExporter("test", 3, time.Minute, time.Nanosecond), inputSpans, false},
|
||||
}
|
||||
|
||||
// mockServer returns a pointer to a server to handle the mock get call.
|
||||
func mockServer() *httptest.Server {
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
d, _ := ioutil.ReadAll(r.Body)
|
||||
data := string(d)
|
||||
if data != inputSpansJSON {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, data)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
return httptest.NewServer(http.HandlerFunc(f))
|
||||
}
|
||||
|
||||
// TestSend validates spans can be sent to the sidecar.
|
||||
func TestSend(t *testing.T) {
|
||||
s := mockServer()
|
||||
defer s.Close()
|
||||
|
||||
t.Log("Given the need to validate sending span data to the sidecar.")
|
||||
{
|
||||
for i, tt := range sendTests {
|
||||
t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name)
|
||||
{
|
||||
// Set the URL for the call.
|
||||
tt.e.host = s.URL
|
||||
|
||||
// Send the span data.
|
||||
err := tt.e.send(tt.input)
|
||||
if tt.pass {
|
||||
if err != nil {
|
||||
t.Errorf("\t%s\tShould be able to send the batch successfully: %v", failed, err)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould be able to send the batch successfully.", success)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("\t%s\tShould not be able to send the batch successfully : %v", failed, err)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould not be able to send the batch successfully.", success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestClose validates the flushing of the final batched spans.
|
||||
func TestClose(t *testing.T) {
|
||||
s := mockServer()
|
||||
defer s.Close()
|
||||
|
||||
t.Log("Given the need to validate flushing the remaining batched spans.")
|
||||
{
|
||||
t.Logf("\tTest: %d\tWhen running test: %s", 0, "FlushWithData")
|
||||
{
|
||||
e, err := NewExporter(logger, "test", 10, time.Minute, time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("\t%s\tShould be able to create an Exporter : %v", failed, err)
|
||||
}
|
||||
t.Logf("\t%s\tShould be able to create an Exporter.", success)
|
||||
|
||||
// Set the URL for the call.
|
||||
e.host = s.URL
|
||||
|
||||
// Save the input of span data.
|
||||
for _, span := range inputSpans {
|
||||
e.saveBatch(span)
|
||||
}
|
||||
|
||||
// Close the Exporter and we should get those spans sent.
|
||||
sent, err := e.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("\t%s\tShould be able to flush the Exporter : %v", failed, err)
|
||||
}
|
||||
t.Logf("\t%s\tShould be able to flush the Exporter.", success)
|
||||
|
||||
if sent != len(inputSpans) {
|
||||
t.Log("\t\tGot :", sent)
|
||||
t.Log("\t\tWant:", len(inputSpans))
|
||||
t.Fatalf("\t%s\tShould have flushed the expected number of spans.", failed)
|
||||
}
|
||||
t.Logf("\t%s\tShould have flushed the expected number of spans.", success)
|
||||
}
|
||||
|
||||
t.Logf("\tTest: %d\tWhen running test: %s", 0, "FlushWithError")
|
||||
{
|
||||
e, err := NewExporter(logger, "test", 10, time.Minute, time.Hour)
|
||||
if err != nil {
|
||||
t.Fatalf("\t%s\tShould be able to create an Exporter : %v", failed, err)
|
||||
}
|
||||
t.Logf("\t%s\tShould be able to create an Exporter.", success)
|
||||
|
||||
// Set the URL for the call.
|
||||
e.host = s.URL
|
||||
|
||||
// Save the input of span data.
|
||||
for _, span := range inputSpans[:2] {
|
||||
e.saveBatch(span)
|
||||
}
|
||||
|
||||
// Close the Exporter and we should get those spans sent.
|
||||
if _, err := e.Close(); err == nil {
|
||||
t.Fatalf("\t%s\tShould not be able to flush the Exporter.", failed)
|
||||
}
|
||||
t.Logf("\t%s\tShould not be able to flush the Exporter.", success)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
// TestExporterFailure validates misuse cases are covered.
|
||||
func TestExporterFailure(t *testing.T) {
|
||||
t.Log("Given the need to validate Exporter initializes properly.")
|
||||
{
|
||||
t.Logf("\tTest: %d\tWhen not passing a proper logger.", 0)
|
||||
{
|
||||
_, err := NewExporter(nil, "test", 10, time.Minute, time.Hour)
|
||||
if err == nil {
|
||||
t.Errorf("\t%s\tShould not be able to create an Exporter.", failed)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould not be able to create an Exporter.", success)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("\tTest: %d\tWhen not passing a proper host.", 1)
|
||||
{
|
||||
_, err := NewExporter(logger, "", 10, time.Minute, time.Hour)
|
||||
if err == nil {
|
||||
t.Errorf("\t%s\tShould not be able to create an Exporter.", failed)
|
||||
} else {
|
||||
t.Logf("\t%s\tShould not be able to create an Exporter.", success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dimfeld/httptreemux"
|
||||
"go.opencensus.io/plugin/ochttp"
|
||||
"go.opencensus.io/plugin/ochttp/propagation/tracecontext"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
// ctxKey represents the type of value for the context key.
|
||||
@@ -22,7 +19,6 @@ const KeyValues ctxKey = 1
|
||||
|
||||
// Values represent state for each request.
|
||||
type Values struct {
|
||||
TraceID string
|
||||
Now time.Time
|
||||
StatusCode int
|
||||
}
|
||||
@@ -36,7 +32,6 @@ type Handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, p
|
||||
// data/logic on this App struct
|
||||
type App struct {
|
||||
*httptreemux.TreeMux
|
||||
och *ochttp.Handler
|
||||
shutdown chan os.Signal
|
||||
log *log.Logger
|
||||
mw []Middleware
|
||||
@@ -51,17 +46,6 @@ func NewApp(shutdown chan os.Signal, log *log.Logger, mw ...Middleware) *App {
|
||||
mw: mw,
|
||||
}
|
||||
|
||||
// Create an OpenCensus HTTP Handler which wraps the router. This will start
|
||||
// the initial span and annotate it with information about the request/response.
|
||||
//
|
||||
// This is configured to use the W3C TraceContext standard to set the remote
|
||||
// parent if an client request includes the appropriate headers.
|
||||
// https://w3c.github.io/trace-context/
|
||||
app.och = &ochttp.Handler{
|
||||
Handler: app.TreeMux,
|
||||
Propagation: &tracecontext.HTTPFormat{},
|
||||
}
|
||||
|
||||
return &app
|
||||
}
|
||||
|
||||
@@ -84,16 +68,12 @@ func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {
|
||||
|
||||
// The function to execute for each request.
|
||||
h := func(w http.ResponseWriter, r *http.Request, params map[string]string) {
|
||||
ctx, span := trace.StartSpan(r.Context(), "internal.platform.web")
|
||||
defer span.End()
|
||||
|
||||
// Set the context with the required values to
|
||||
// process the request.
|
||||
v := Values{
|
||||
TraceID: span.SpanContext().TraceID.String(),
|
||||
Now: time.Now(),
|
||||
}
|
||||
ctx = context.WithValue(ctx, KeyValues, &v)
|
||||
ctx := context.WithValue(r.Context(), KeyValues, &v)
|
||||
|
||||
// Call the wrapped handler functions.
|
||||
if err := handler(ctx, w, r, params); err != nil {
|
||||
@@ -106,10 +86,3 @@ func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {
|
||||
// Add this handler for the specified verb and route.
|
||||
a.TreeMux.Handle(verb, path, h)
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler interface. It overrides the ServeHTTP
|
||||
// of the embedded TreeMux by using the ochttp.Handler instead. That Handler
|
||||
// wraps the TreeMux handler so the routes are served.
|
||||
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
a.och.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user