Add OpenTracing support to TimeRequestHistogram (#2023)

* Vendor open tracing

* Extend TimeRequestHistorgram to also emit opentracing spans.

* Update some of the labels
This commit is contained in:
Tom Wilkie
2016-11-22 14:43:15 +00:00
committed by GitHub
parent d15e884cb1
commit 21ee72a384
20 changed files with 1962 additions and 47 deletions

View File

@@ -91,7 +91,7 @@ type AWSCollector interface {
// ReportStore is a thing that we can get reports from.
type ReportStore interface {
FetchReports([]string) (map[string]report.Report, []string, error)
FetchReports(context.Context, []string) (map[string]report.Report, []string, error)
}
// AWSCollectorConfig has everything we need to make an AWS collector.
@@ -215,10 +215,10 @@ func (c *awsCollector) CreateTables() error {
}
// getReportKeys gets the s3 keys for reports in this range
func (c *awsCollector) getReportKeys(userid string, row int64, start, end time.Time) ([]string, error) {
func (c *awsCollector) getReportKeys(ctx context.Context, userid string, row int64, start, end time.Time) ([]string, error) {
rowKey := fmt.Sprintf("%s-%s", userid, strconv.FormatInt(row, 10))
var resp *dynamodb.QueryOutput
err := instrument.TimeRequestHistogram("Query", dynamoRequestDuration, func() error {
err := instrument.TimeRequestHistogram(ctx, "DynamoDB.Query", dynamoRequestDuration, func(_ context.Context) error {
var err error
resp, err = c.db.Query(&dynamodb.QueryInput{
TableName: aws.String(c.tableName),
@@ -263,7 +263,7 @@ func (c *awsCollector) getReportKeys(userid string, row int64, start, end time.T
return result, nil
}
func (c *awsCollector) getReports(reportKeys []string) ([]report.Report, error) {
func (c *awsCollector) getReports(ctx context.Context, reportKeys []string) ([]report.Report, error) {
missing := reportKeys
stores := []ReportStore{c.inProcess}
@@ -277,7 +277,7 @@ func (c *awsCollector) getReports(reportKeys []string) ([]report.Report, error)
if store == nil {
continue
}
found, newMissing, err := store.FetchReports(missing)
found, newMissing, err := store.FetchReports(ctx, missing)
missing = newMissing
if err != nil {
log.Warningf("Error fetching from cache: %v", err)
@@ -311,12 +311,12 @@ func (c *awsCollector) Report(ctx context.Context) (report.Report, error) {
// Queries will only every span 2 rows max.
var reportKeys []string
if rowStart != rowEnd {
reportKeys1, err := c.getReportKeys(userid, rowStart, start, now)
reportKeys1, err := c.getReportKeys(ctx, userid, rowStart, start, now)
if err != nil {
return report.MakeReport(), err
}
reportKeys2, err := c.getReportKeys(userid, rowEnd, start, now)
reportKeys2, err := c.getReportKeys(ctx, userid, rowEnd, start, now)
if err != nil {
return report.MakeReport(), err
}
@@ -324,13 +324,13 @@ func (c *awsCollector) Report(ctx context.Context) (report.Report, error) {
reportKeys = append(reportKeys, reportKeys1...)
reportKeys = append(reportKeys, reportKeys2...)
} else {
if reportKeys, err = c.getReportKeys(userid, rowEnd, start, now); err != nil {
if reportKeys, err = c.getReportKeys(ctx, userid, rowEnd, start, now); err != nil {
return report.MakeReport(), err
}
}
log.Debugf("Fetching %d reports from %v to %v", len(reportKeys), start, now)
reports, err := c.getReports(reportKeys)
reports, err := c.getReports(ctx, reportKeys)
if err != nil {
return report.MakeReport(), err
}
@@ -367,7 +367,7 @@ func (c *awsCollector) Add(ctx context.Context, rep report.Report, buf []byte) e
return err
}
reportSize, err := c.s3.StoreReportBytes(reportKey, buf)
reportSize, err := c.s3.StoreReportBytes(ctx, reportKey, buf)
if err != nil {
return err
}
@@ -375,7 +375,7 @@ func (c *awsCollector) Add(ctx context.Context, rep report.Report, buf []byte) e
// third, put it in memcache
if c.memcache != nil {
_, err = c.memcache.StoreReportBytes(reportKey, buf)
_, err = c.memcache.StoreReportBytes(ctx, reportKey, buf)
if err != nil {
// NOTE: We don't abort here because failing to store in memcache
// doesn't actually break anything else -- it's just an
@@ -389,7 +389,7 @@ func (c *awsCollector) Add(ctx context.Context, rep report.Report, buf []byte) e
Add(float64(len(reportKey)))
var resp *dynamodb.PutItemOutput
err = instrument.TimeRequestHistogram("PutItem", dynamoRequestDuration, func() error {
err = instrument.TimeRequestHistogram(ctx, "S3.PutItem", dynamoRequestDuration, func(_ context.Context) error {
var err error
resp, err = c.db.PutItem(&dynamodb.PutItemInput{
TableName: aws.String(c.tableName),
@@ -502,7 +502,7 @@ func newInProcessStore(size int, expiration time.Duration) inProcessStore {
}
// FetchReports retrieves the given reports from the store.
func (c inProcessStore) FetchReports(keys []string) (map[string]report.Report, []string, error) {
func (c inProcessStore) FetchReports(_ context.Context, keys []string) (map[string]report.Report, []string, error) {
found := map[string]report.Report{}
missing := []string{}
for _, key := range keys {

View File

@@ -11,6 +11,7 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/bradfitz/gomemcache/memcache"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/weaveworks/scope/common/instrument"
"github.com/weaveworks/scope/report"
@@ -149,10 +150,10 @@ func memcacheStatusCode(err error) string {
}
// FetchReports gets reports from memcache.
func (c *MemcacheClient) FetchReports(keys []string) (map[string]report.Report, []string, error) {
func (c *MemcacheClient) FetchReports(ctx context.Context, keys []string) (map[string]report.Report, []string, error) {
defer memcacheRequests.Add(float64(len(keys)))
var found map[string]*memcache.Item
err := instrument.TimeRequestHistogramStatus("Get", memcacheRequestDuration, memcacheStatusCode, func() error {
err := instrument.TimeRequestHistogramStatus(ctx, "Memcache.GetMulti", memcacheRequestDuration, memcacheStatusCode, func(_ context.Context) error {
var err error
found, err = c.client.GetMulti(keys)
return err
@@ -206,8 +207,8 @@ func (c *MemcacheClient) FetchReports(keys []string) (map[string]report.Report,
}
// StoreReportBytes stores a report.
func (c *MemcacheClient) StoreReportBytes(key string, rpt []byte) (int, error) {
err := instrument.TimeRequestHistogramStatus("Put", memcacheRequestDuration, memcacheStatusCode, func() error {
func (c *MemcacheClient) StoreReportBytes(ctx context.Context, key string, rpt []byte) (int, error) {
err := instrument.TimeRequestHistogramStatus(ctx, "Memcache.Put", memcacheRequestDuration, memcacheStatusCode, func(_ context.Context) error {
item := memcache.Item{Key: key, Value: rpt, Expiration: c.expiration}
return c.client.Set(&item)
})

View File

@@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"github.com/weaveworks/scope/common/instrument"
"github.com/weaveworks/scope/report"
@@ -40,7 +41,7 @@ func NewS3Client(config *aws.Config, bucketName string) S3Store {
}
// FetchReports fetches multiple reports in parallel from S3.
func (store *S3Store) FetchReports(keys []string) (map[string]report.Report, []string, error) {
func (store *S3Store) FetchReports(ctx context.Context, keys []string) (map[string]report.Report, []string, error) {
type result struct {
key string
report *report.Report
@@ -52,7 +53,7 @@ func (store *S3Store) FetchReports(keys []string) (map[string]report.Report, []s
for _, key := range keys {
go func(key string) {
r := result{key: key}
r.report, r.err = store.fetchReport(key)
r.report, r.err = store.fetchReport(ctx, key)
ch <- r
}(key)
}
@@ -68,9 +69,9 @@ func (store *S3Store) FetchReports(keys []string) (map[string]report.Report, []s
return reports, []string{}, nil
}
func (store *S3Store) fetchReport(key string) (*report.Report, error) {
func (store *S3Store) fetchReport(ctx context.Context, key string) (*report.Report, error) {
var resp *s3.GetObjectOutput
err := instrument.TimeRequestHistogram("Get", s3RequestDuration, func() error {
err := instrument.TimeRequestHistogram(ctx, "S3.Get", s3RequestDuration, func(_ context.Context) error {
var err error
resp, err = store.s3.GetObject(&s3.GetObjectInput{
Bucket: aws.String(store.bucketName),
@@ -85,8 +86,8 @@ func (store *S3Store) fetchReport(key string) (*report.Report, error) {
}
// StoreReportBytes stores a report.
func (store *S3Store) StoreReportBytes(key string, buf []byte) (int, error) {
err := instrument.TimeRequestHistogram("Put", s3RequestDuration, func() error {
func (store *S3Store) StoreReportBytes(ctx context.Context, key string, buf []byte) (int, error) {
err := instrument.TimeRequestHistogram(ctx, "S3.Put", s3RequestDuration, func(_ context.Context) error {
_, err := store.s3.PutObject(&s3.PutObjectInput{
Body: bytes.NewReader(buf),
Bucket: aws.String(store.bucketName),

View File

@@ -92,11 +92,11 @@ func (cr *sqsControlRouter) getResponseQueueURL() *string {
return cr.responseQueueURL
}
func (cr *sqsControlRouter) getOrCreateQueue(name string) (*string, error) {
func (cr *sqsControlRouter) getOrCreateQueue(ctx context.Context, name string) (*string, error) {
// CreateQueue creates a queue or if it already exists, returns url of said queue
var createQueueRes *sqs.CreateQueueOutput
var err error
err = instrument.TimeRequestHistogram("CreateQueue", sqsRequestDuration, func() error {
err = instrument.TimeRequestHistogram(ctx, "SQS.CreateQueue", sqsRequestDuration, func(_ context.Context) error {
createQueueRes, err = cr.service.CreateQueue(&sqs.CreateQueueInput{
QueueName: aws.String(name),
})
@@ -112,11 +112,12 @@ func (cr *sqsControlRouter) loop() {
var (
responseQueueURL *string
err error
ctx = context.Background()
)
for {
// This app has a random id and uses this as a return path for all responses from probes.
name := fmt.Sprintf("%scontrol-app-%d", cr.prefix, rand.Int63())
responseQueueURL, err = cr.getOrCreateQueue(name)
responseQueueURL, err = cr.getOrCreateQueue(ctx, name)
if err != nil {
log.Errorf("Failed to create queue: %v", err)
time.Sleep(1 * time.Second)
@@ -129,7 +130,7 @@ func (cr *sqsControlRouter) loop() {
for {
var res *sqs.ReceiveMessageOutput
var err error
err = instrument.TimeRequestHistogram("ReceiveMessage", sqsRequestDuration, func() error {
err = instrument.TimeRequestHistogram(ctx, "SQS.ReceiveMessage", sqsRequestDuration, func(_ context.Context) error {
res, err = cr.service.ReceiveMessage(&sqs.ReceiveMessageInput{
QueueUrl: responseQueueURL,
WaitTimeSeconds: longPollTime,
@@ -144,14 +145,14 @@ func (cr *sqsControlRouter) loop() {
if len(res.Messages) == 0 {
continue
}
if err := cr.deleteMessages(responseQueueURL, res.Messages); err != nil {
if err := cr.deleteMessages(ctx, responseQueueURL, res.Messages); err != nil {
log.Errorf("Error deleting message from %s: %v", *responseQueueURL, err)
}
cr.handleResponses(res)
}
}
func (cr *sqsControlRouter) deleteMessages(queueURL *string, messages []*sqs.Message) error {
func (cr *sqsControlRouter) deleteMessages(ctx context.Context, queueURL *string, messages []*sqs.Message) error {
entries := []*sqs.DeleteMessageBatchRequestEntry{}
for _, message := range messages {
entries = append(entries, &sqs.DeleteMessageBatchRequestEntry{
@@ -159,7 +160,7 @@ func (cr *sqsControlRouter) deleteMessages(queueURL *string, messages []*sqs.Mes
Id: message.MessageId,
})
}
return instrument.TimeRequestHistogram("DeleteMessageBatch", sqsRequestDuration, func() error {
return instrument.TimeRequestHistogram(ctx, "SQS.DeleteMessageBatch", sqsRequestDuration, func(_ context.Context) error {
_, err := cr.service.DeleteMessageBatch(&sqs.DeleteMessageBatchInput{
QueueUrl: queueURL,
Entries: entries,
@@ -188,14 +189,14 @@ func (cr *sqsControlRouter) handleResponses(res *sqs.ReceiveMessageOutput) {
}
}
func (cr *sqsControlRouter) sendMessage(queueURL *string, message interface{}) error {
func (cr *sqsControlRouter) sendMessage(ctx context.Context, queueURL *string, message interface{}) error {
buf := bytes.Buffer{}
if err := json.NewEncoder(&buf).Encode(message); err != nil {
return err
}
log.Infof("sendMessage to %s: %s", *queueURL, buf.String())
return instrument.TimeRequestHistogram("SendMessage", sqsRequestDuration, func() error {
return instrument.TimeRequestHistogram(ctx, "SQS.SendMessage", sqsRequestDuration, func(_ context.Context) error {
_, err := cr.service.SendMessage(&sqs.SendMessageInput{
QueueUrl: queueURL,
MessageBody: aws.String(buf.String()),
@@ -218,7 +219,7 @@ func (cr *sqsControlRouter) Handle(ctx context.Context, probeID string, req xfer
}
var probeQueueURL *sqs.GetQueueUrlOutput
err = instrument.TimeRequestHistogram("GetQueueUrl", sqsRequestDuration, func() error {
err = instrument.TimeRequestHistogram(ctx, "SQS.GetQueueUrl", sqsRequestDuration, func(_ context.Context) error {
probeQueueName := fmt.Sprintf("%sprobe-%s-%s", cr.prefix, userID, probeID)
probeQueueURL, err = cr.service.GetQueueUrl(&sqs.GetQueueUrlInput{
QueueName: aws.String(probeQueueName),
@@ -242,8 +243,8 @@ func (cr *sqsControlRouter) Handle(ctx context.Context, probeID string, req xfer
}()
// Next, send the request to that queue
if err := instrument.TimeRequestHistogram("SendMessage", sqsRequestDuration, func() error {
return cr.sendMessage(probeQueueURL.QueueUrl, sqsRequestMessage{
if err := instrument.TimeRequestHistogram(ctx, "SQS.SendMessage", sqsRequestDuration, func(ctx context.Context) error {
return cr.sendMessage(ctx, probeQueueURL.QueueUrl, sqsRequestMessage{
ID: id,
Request: req,
ResponseQueueURL: *responseQueueURL,
@@ -268,13 +269,14 @@ func (cr *sqsControlRouter) Register(ctx context.Context, probeID string, handle
}
name := fmt.Sprintf("%sprobe-%s-%s", cr.prefix, userID, probeID)
queueURL, err := cr.getOrCreateQueue(name)
queueURL, err := cr.getOrCreateQueue(ctx, name)
if err != nil {
return 0, err
}
pwID := rand.Int63()
pw := &probeWorker{
ctx: ctx,
router: cr,
requestQueueURL: queueURL,
handler: handler,
@@ -302,6 +304,7 @@ func (cr *sqsControlRouter) Deregister(_ context.Context, probeID string, id int
// a probeWorker encapsulates a goroutine serving a probe's websocket connection.
type probeWorker struct {
ctx context.Context
router *sqsControlRouter
requestQueueURL *string
handler xfer.ControlHandlerFunc
@@ -326,7 +329,7 @@ func (pw *probeWorker) loop() {
var res *sqs.ReceiveMessageOutput
var err error
err = instrument.TimeRequestHistogram("ReceiveMessage", sqsRequestDuration, func() error {
err = instrument.TimeRequestHistogram(pw.ctx, "SQS.ReceiveMessage", sqsRequestDuration, func(_ context.Context) error {
res, err = pw.router.service.ReceiveMessage(&sqs.ReceiveMessageInput{
QueueUrl: pw.requestQueueURL,
WaitTimeSeconds: longPollTime,
@@ -341,7 +344,7 @@ func (pw *probeWorker) loop() {
if len(res.Messages) == 0 {
continue
}
if err := pw.router.deleteMessages(pw.requestQueueURL, res.Messages); err != nil {
if err := pw.router.deleteMessages(pw.ctx, pw.requestQueueURL, res.Messages); err != nil {
log.Errorf("Error deleting message from %s: %v", *pw.requestQueueURL, err)
}
@@ -354,7 +357,7 @@ func (pw *probeWorker) loop() {
response := pw.handler(sqsRequest.Request)
if err := pw.router.sendMessage(&sqsRequest.ResponseQueueURL, sqsResponseMessage{
if err := pw.router.sendMessage(pw.ctx, &sqsRequest.ResponseQueueURL, sqsResponseMessage{
ID: sqsRequest.ID,
Response: response,
}); err != nil {

View File

@@ -3,7 +3,10 @@ package instrument
import (
"time"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
)
// ErrorCode converts an error in to an http-style error-code.
@@ -16,26 +19,35 @@ func ErrorCode(err error) string {
// TimeRequestHistogram runs 'f' and records how long it took in the given Prometheus
// histogram metric. If 'f' returns successfully, record a "200". Otherwise, record
// "500".
// "500". It will also emit an OpenTracing span if you have a global tracer configured.
//
// If you want more complicated logic for translating errors into statuses,
// use 'TimeRequestStatus'.
func TimeRequestHistogram(method string, metric *prometheus.HistogramVec, f func() error) error {
return TimeRequestHistogramStatus(method, metric, ErrorCode, f)
func TimeRequestHistogram(ctx context.Context, method string, metric *prometheus.HistogramVec, f func(context.Context) error) error {
return TimeRequestHistogramStatus(ctx, method, metric, ErrorCode, f)
}
// TimeRequestHistogramStatus runs 'f' and records how long it took in the given
// Prometheus histogram metric.
// Prometheus histogram metric. It will also emit an OpenTracing span if you have
// a global tracer configured.
//
// toStatusCode is a function that translates errors returned by 'f' into
// HTTP-like status codes.
func TimeRequestHistogramStatus(method string, metric *prometheus.HistogramVec, toStatusCode func(error) string, f func() error) error {
func TimeRequestHistogramStatus(ctx context.Context, method string, metric *prometheus.HistogramVec, toStatusCode func(error) string, f func(context.Context) error) error {
if toStatusCode == nil {
toStatusCode = ErrorCode
}
sp, newCtx := opentracing.StartSpanFromContext(ctx, method)
ext.SpanKindRPCClient.Set(sp)
startTime := time.Now()
err := f()
duration := time.Now().Sub(startTime)
metric.WithLabelValues(method, toStatusCode(err)).Observe(duration.Seconds())
err := f(newCtx)
if err != nil {
ext.Error.Set(sp, true)
}
sp.Finish()
metric.WithLabelValues(method, toStatusCode(err)).Observe(time.Now().Sub(startTime).Seconds())
return err
}

21
vendor/github.com/opentracing/opentracing-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 The OpenTracing Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,158 @@
package ext
import opentracing "github.com/opentracing/opentracing-go"
// These constants define common tag names recommended for better portability across
// tracing systems and languages/platforms.
//
// The tag names are defined as typed strings, so that in addition to the usual use
//
// span.setTag(TagName, value)
//
// they also support value type validation via this additional syntax:
//
// TagName.Set(span, value)
//
var (
//////////////////////////////////////////////////////////////////////
// SpanKind (client/server)
//////////////////////////////////////////////////////////////////////
// SpanKind hints at relationship between spans, e.g. client/server
SpanKind = spanKindTagName("span.kind")
// SpanKindRPCClient marks a span representing the client-side of an RPC
// or other remote call
SpanKindRPCClientEnum = SpanKindEnum("client")
SpanKindRPCClient = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCClientEnum}
// SpanKindRPCServer marks a span representing the server-side of an RPC
// or other remote call
SpanKindRPCServerEnum = SpanKindEnum("server")
SpanKindRPCServer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindRPCServerEnum}
//////////////////////////////////////////////////////////////////////
// Component name
//////////////////////////////////////////////////////////////////////
// Component is a low-cardinality identifier of the module, library,
// or package that is generating a span.
Component = stringTagName("component")
//////////////////////////////////////////////////////////////////////
// Sampling hint
//////////////////////////////////////////////////////////////////////
// SamplingPriority determines the priority of sampling this Span.
SamplingPriority = uint16TagName("sampling.priority")
//////////////////////////////////////////////////////////////////////
// Peer tags. These tags can be emitted by either client-side of
// server-side to describe the other side/service in a peer-to-peer
// communications, like an RPC call.
//////////////////////////////////////////////////////////////////////
// PeerService records the service name of the peer
PeerService = stringTagName("peer.service")
// PeerHostname records the host name of the peer
PeerHostname = stringTagName("peer.hostname")
// PeerHostIPv4 records IP v4 host address of the peer
PeerHostIPv4 = uint32TagName("peer.ipv4")
// PeerHostIPv6 records IP v6 host address of the peer
PeerHostIPv6 = stringTagName("peer.ipv6")
// PeerPort records port number of the peer
PeerPort = uint16TagName("peer.port")
//////////////////////////////////////////////////////////////////////
// HTTP Tags
//////////////////////////////////////////////////////////////////////
// HTTPUrl should be the URL of the request being handled in this segment
// of the trace, in standard URI format. The protocol is optional.
HTTPUrl = stringTagName("http.url")
// HTTPMethod is the HTTP method of the request, and is case-insensitive.
HTTPMethod = stringTagName("http.method")
// HTTPStatusCode is the numeric HTTP status code (200, 404, etc) of the
// HTTP response.
HTTPStatusCode = uint16TagName("http.status_code")
//////////////////////////////////////////////////////////////////////
// Error Tag
//////////////////////////////////////////////////////////////////////
// Error indicates that operation represented by the span resulted in an error.
Error = boolTagName("error")
)
// ---
// SpanKindEnum represents common span types
type SpanKindEnum string
type spanKindTagName string
// Set adds a string tag to the `span`
func (tag spanKindTagName) Set(span opentracing.Span, value SpanKindEnum) {
span.SetTag(string(tag), value)
}
type rpcServerOption struct {
clientContext opentracing.SpanContext
}
func (r rpcServerOption) Apply(o *opentracing.StartSpanOptions) {
if r.clientContext != nil {
opentracing.ChildOf(r.clientContext).Apply(o)
}
SpanKindRPCServer.Apply(o)
}
// RPCServerOption returns a StartSpanOption appropriate for an RPC server span
// with `client` representing the metadata for the remote peer Span if available.
// In case client == nil, due to the client not being instrumented, this RPC
// server span will be a root span.
func RPCServerOption(client opentracing.SpanContext) opentracing.StartSpanOption {
return rpcServerOption{client}
}
// ---
type stringTagName string
// Set adds a string tag to the `span`
func (tag stringTagName) Set(span opentracing.Span, value string) {
span.SetTag(string(tag), value)
}
// ---
type uint32TagName string
// Set adds a uint32 tag to the `span`
func (tag uint32TagName) Set(span opentracing.Span, value uint32) {
span.SetTag(string(tag), value)
}
// ---
type uint16TagName string
// Set adds a uint16 tag to the `span`
func (tag uint16TagName) Set(span opentracing.Span, value uint16) {
span.SetTag(string(tag), value)
}
// ---
type boolTagName string
// Add adds a bool tag to the `span`
func (tag boolTagName) Set(span opentracing.Span, value bool) {
span.SetTag(string(tag), value)
}

View File

@@ -0,0 +1,27 @@
package opentracing
var (
globalTracer Tracer = NoopTracer{}
)
// InitGlobalTracer sets the [singleton] opentracing.Tracer returned by
// GlobalTracer(). Those who use GlobalTracer (rather than directly manage an
// opentracing.Tracer instance) should call InitGlobalTracer as early as
// possible in main(), prior to calling the `StartSpan` global func below.
// Prior to calling `InitGlobalTracer`, any Spans started via the `StartSpan`
// (etc) globals are noops.
func InitGlobalTracer(tracer Tracer) {
globalTracer = tracer
}
// GlobalTracer returns the global singleton `Tracer` implementation.
// Before `InitGlobalTracer()` is called, the `GlobalTracer()` is a noop
// implementation that drops all data handed to it.
func GlobalTracer() Tracer {
return globalTracer
}
// StartSpan defers to `Tracer.StartSpan`. See `GlobalTracer()`.
func StartSpan(operationName string, opts ...StartSpanOption) Span {
return globalTracer.StartSpan(operationName, opts...)
}

View File

@@ -0,0 +1,57 @@
package opentracing
import "golang.org/x/net/context"
type contextKey struct{}
var activeSpanKey = contextKey{}
// ContextWithSpan returns a new `context.Context` that holds a reference to
// `span`'s SpanContext.
func ContextWithSpan(ctx context.Context, span Span) context.Context {
return context.WithValue(ctx, activeSpanKey, span)
}
// SpanFromContext returns the `Span` previously associated with `ctx`, or
// `nil` if no such `Span` could be found.
//
// NOTE: context.Context != SpanContext: the former is Go's intra-process
// context propagation mechanism, and the latter houses OpenTracing's per-Span
// identity and baggage information.
func SpanFromContext(ctx context.Context) Span {
val := ctx.Value(activeSpanKey)
if sp, ok := val.(Span); ok {
return sp
}
return nil
}
// StartSpanFromContext starts and returns a Span with `operationName`, using
// any Span found within `ctx` as a ChildOfRef. If no such parent could be
// found, StartSpanFromContext creates a root (parentless) Span.
//
// The second return value is a context.Context object built around the
// returned Span.
//
// Example usage:
//
// SomeFunction(ctx context.Context, ...) {
// sp, ctx := opentracing.StartSpanFromContext(ctx, "SomeFunction")
// defer sp.Finish()
// ...
// }
func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) {
return startSpanFromContextWithTracer(ctx, GlobalTracer(), operationName, opts...)
}
// startSpanFromContextWithTracer is factored out for testing purposes.
func startSpanFromContextWithTracer(ctx context.Context, tracer Tracer, operationName string, opts ...StartSpanOption) (Span, context.Context) {
var span Span
if parentSpan := SpanFromContext(ctx); parentSpan != nil {
opts = append(opts, ChildOf(parentSpan.Context()))
span = tracer.StartSpan(operationName, opts...)
} else {
span = tracer.StartSpan(operationName, opts...)
}
return span, ContextWithSpan(ctx, span)
}

View File

@@ -0,0 +1,241 @@
package log
import (
"fmt"
"math"
)
type fieldType int
const (
stringType fieldType = iota
boolType
intType
int32Type
uint32Type
int64Type
uint64Type
float32Type
float64Type
errorType
objectType
lazyLoggerType
)
// Field instances are constructed via LogBool, LogString, and so on.
// Tracing implementations may then handle them via the Field.Marshal
// method.
//
// "heavily influenced by" (i.e., partially stolen from)
// https://github.com/uber-go/zap
type Field struct {
key string
fieldType fieldType
numericVal int64
stringVal string
interfaceVal interface{}
}
// String adds a string-valued key:value pair to a Span.LogFields() record
func String(key, val string) Field {
return Field{
key: key,
fieldType: stringType,
stringVal: val,
}
}
// Bool adds a bool-valued key:value pair to a Span.LogFields() record
func Bool(key string, val bool) Field {
var numericVal int64
if val {
numericVal = 1
}
return Field{
key: key,
fieldType: boolType,
numericVal: numericVal,
}
}
// Int adds an int-valued key:value pair to a Span.LogFields() record
func Int(key string, val int) Field {
return Field{
key: key,
fieldType: intType,
numericVal: int64(val),
}
}
// Int32 adds an int32-valued key:value pair to a Span.LogFields() record
func Int32(key string, val int32) Field {
return Field{
key: key,
fieldType: int32Type,
numericVal: int64(val),
}
}
// Int64 adds an int64-valued key:value pair to a Span.LogFields() record
func Int64(key string, val int64) Field {
return Field{
key: key,
fieldType: int64Type,
numericVal: val,
}
}
// Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record
func Uint32(key string, val uint32) Field {
return Field{
key: key,
fieldType: uint32Type,
numericVal: int64(val),
}
}
// Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record
func Uint64(key string, val uint64) Field {
return Field{
key: key,
fieldType: uint64Type,
numericVal: int64(val),
}
}
// Float32 adds a float32-valued key:value pair to a Span.LogFields() record
func Float32(key string, val float32) Field {
return Field{
key: key,
fieldType: float32Type,
numericVal: int64(math.Float32bits(val)),
}
}
// Float64 adds a float64-valued key:value pair to a Span.LogFields() record
func Float64(key string, val float64) Field {
return Field{
key: key,
fieldType: float64Type,
numericVal: int64(math.Float64bits(val)),
}
}
// Error adds an error with the key "error" to a Span.LogFields() record
func Error(err error) Field {
return Field{
key: "error",
fieldType: errorType,
interfaceVal: err,
}
}
// Object adds an object-valued key:value pair to a Span.LogFields() record
func Object(key string, obj interface{}) Field {
return Field{
key: key,
fieldType: objectType,
interfaceVal: obj,
}
}
// LazyLogger allows for user-defined, late-bound logging of arbitrary data
type LazyLogger func(fv Encoder)
// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
// implementation will call the LazyLogger function at an indefinite time in
// the future (after Lazy() returns).
func Lazy(ll LazyLogger) Field {
return Field{
fieldType: lazyLoggerType,
interfaceVal: ll,
}
}
// Encoder allows access to the contents of a Field (via a call to
// Field.Marshal).
//
// Tracer implementations typically provide an implementation of Encoder;
// OpenTracing callers typically do not need to concern themselves with it.
type Encoder interface {
EmitString(key, value string)
EmitBool(key string, value bool)
EmitInt(key string, value int)
EmitInt32(key string, value int32)
EmitInt64(key string, value int64)
EmitUint32(key string, value uint32)
EmitUint64(key string, value uint64)
EmitFloat32(key string, value float32)
EmitFloat64(key string, value float64)
EmitObject(key string, value interface{})
EmitLazyLogger(value LazyLogger)
}
// Marshal passes a Field instance through to the appropriate
// field-type-specific method of an Encoder.
func (lf Field) Marshal(visitor Encoder) {
switch lf.fieldType {
case stringType:
visitor.EmitString(lf.key, lf.stringVal)
case boolType:
visitor.EmitBool(lf.key, lf.numericVal != 0)
case intType:
visitor.EmitInt(lf.key, int(lf.numericVal))
case int32Type:
visitor.EmitInt32(lf.key, int32(lf.numericVal))
case int64Type:
visitor.EmitInt64(lf.key, int64(lf.numericVal))
case uint32Type:
visitor.EmitUint32(lf.key, uint32(lf.numericVal))
case uint64Type:
visitor.EmitUint64(lf.key, uint64(lf.numericVal))
case float32Type:
visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
case float64Type:
visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
case errorType:
visitor.EmitString(lf.key, lf.interfaceVal.(error).Error())
case objectType:
visitor.EmitObject(lf.key, lf.interfaceVal)
case lazyLoggerType:
visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
}
}
// Key returns the field's key.
func (lf Field) Key() string {
return lf.key
}
// Value returns the field's value as interface{}.
func (lf Field) Value() interface{} {
switch lf.fieldType {
case stringType:
return lf.stringVal
case boolType:
return lf.numericVal != 0
case intType:
return int(lf.numericVal)
case int32Type:
return int32(lf.numericVal)
case int64Type:
return int64(lf.numericVal)
case uint32Type:
return uint32(lf.numericVal)
case uint64Type:
return uint64(lf.numericVal)
case float32Type:
return math.Float32frombits(uint32(lf.numericVal))
case float64Type:
return math.Float64frombits(uint64(lf.numericVal))
case errorType, objectType, lazyLoggerType:
return lf.interfaceVal
default:
return nil
}
}
// String returns a string representation of the key and value.
func (lf Field) String() string {
return fmt.Sprint(lf.key, ":", lf.Value())
}

View File

@@ -0,0 +1,54 @@
package log
import "fmt"
// InterleavedKVToFields converts keyValues a la Span.LogKV() to a Field slice
// a la Span.LogFields().
func InterleavedKVToFields(keyValues ...interface{}) ([]Field, error) {
if len(keyValues)%2 != 0 {
return nil, fmt.Errorf("non-even keyValues len: %d", len(keyValues))
}
fields := make([]Field, len(keyValues)/2)
for i := 0; i*2 < len(keyValues); i++ {
key, ok := keyValues[i*2].(string)
if !ok {
return nil, fmt.Errorf(
"non-string key (pair #%d): %T",
i, keyValues[i*2])
}
switch typedVal := keyValues[i*2+1].(type) {
case bool:
fields[i] = Bool(key, typedVal)
case string:
fields[i] = String(key, typedVal)
case int:
fields[i] = Int(key, typedVal)
case int8:
fields[i] = Int32(key, int32(typedVal))
case int16:
fields[i] = Int32(key, int32(typedVal))
case int32:
fields[i] = Int32(key, typedVal)
case int64:
fields[i] = Int64(key, typedVal)
case uint:
fields[i] = Uint64(key, uint64(typedVal))
case uint64:
fields[i] = Uint64(key, typedVal)
case uint8:
fields[i] = Uint32(key, uint32(typedVal))
case uint16:
fields[i] = Uint32(key, uint32(typedVal))
case uint32:
fields[i] = Uint32(key, typedVal)
case float32:
fields[i] = Float32(key, typedVal)
case float64:
fields[i] = Float64(key, typedVal)
default:
// When in doubt, coerce to a string
fields[i] = String(key, fmt.Sprint(typedVal))
}
}
return fields, nil
}

View File

@@ -0,0 +1,105 @@
package mocktracer
import (
"fmt"
"reflect"
"time"
"github.com/opentracing/opentracing-go/log"
)
// MockLogRecord represents data logged to a Span via Span.LogFields or
// Span.LogKV.
type MockLogRecord struct {
Timestamp time.Time
Fields []MockKeyValue
}
// MockKeyValue represents a single key:value pair.
type MockKeyValue struct {
Key string
// All MockLogRecord values are coerced to strings via fmt.Sprint(), though
// we retain their type separately.
ValueKind reflect.Kind
ValueString string
}
// EmitString belongs to the log.Encoder interface
func (m *MockKeyValue) EmitString(key, value string) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitBool belongs to the log.Encoder interface
func (m *MockKeyValue) EmitBool(key string, value bool) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitInt belongs to the log.Encoder interface
func (m *MockKeyValue) EmitInt(key string, value int) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitInt32 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitInt32(key string, value int32) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitInt64 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitInt64(key string, value int64) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitUint32 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitUint32(key string, value uint32) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitUint64 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitUint64(key string, value uint64) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitFloat32 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitFloat32(key string, value float32) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitFloat64 belongs to the log.Encoder interface
func (m *MockKeyValue) EmitFloat64(key string, value float64) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitObject belongs to the log.Encoder interface
func (m *MockKeyValue) EmitObject(key string, value interface{}) {
m.Key = key
m.ValueKind = reflect.TypeOf(value).Kind()
m.ValueString = fmt.Sprint(value)
}
// EmitLazyLogger belongs to the log.Encoder interface
func (m *MockKeyValue) EmitLazyLogger(value log.LazyLogger) {
var meta MockKeyValue
value(&meta)
m.Key = meta.Key
m.ValueKind = meta.ValueKind
m.ValueString = meta.ValueString
}

View File

@@ -0,0 +1,283 @@
package mocktracer
import (
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/opentracing/opentracing-go/log"
)
// MockSpanContext is an opentracing.SpanContext implementation.
//
// It is entirely unsuitable for production use, but appropriate for tests
// that want to verify tracing behavior in other frameworks/applications.
//
// By default all spans have Sampled=true flag, unless {"sampling.priority": 0}
// tag is set.
type MockSpanContext struct {
TraceID int
SpanID int
Sampled bool
Baggage map[string]string
}
var mockIDSource = uint32(42)
func nextMockID() int {
atomic.AddUint32(&mockIDSource, 1)
return int(atomic.LoadUint32(&mockIDSource))
}
// ForeachBaggageItem belongs to the SpanContext interface
func (c MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
for k, v := range c.Baggage {
if !handler(k, v) {
break
}
}
}
// WithBaggageItem creates a new context with an extra baggage item.
func (c MockSpanContext) WithBaggageItem(key, value string) MockSpanContext {
var newBaggage map[string]string
if c.Baggage == nil {
newBaggage = map[string]string{key: value}
} else {
newBaggage = make(map[string]string, len(c.Baggage)+1)
for k, v := range c.Baggage {
newBaggage[k] = v
}
newBaggage[key] = value
}
// Use positional parameters so the compiler will help catch new fields.
return MockSpanContext{c.TraceID, c.SpanID, c.Sampled, newBaggage}
}
// MockSpan is an opentracing.Span implementation that exports its internal
// state for testing purposes.
type MockSpan struct {
sync.RWMutex
ParentID int
OperationName string
StartTime time.Time
FinishTime time.Time
// All of the below are protected by the embedded RWMutex.
SpanContext MockSpanContext
tags map[string]interface{}
logs []MockLogRecord
tracer *MockTracer
}
func newMockSpan(t *MockTracer, name string, opts opentracing.StartSpanOptions) *MockSpan {
tags := opts.Tags
if tags == nil {
tags = map[string]interface{}{}
}
traceID := nextMockID()
parentID := int(0)
var baggage map[string]string
sampled := true
if len(opts.References) > 0 {
traceID = opts.References[0].ReferencedContext.(MockSpanContext).TraceID
parentID = opts.References[0].ReferencedContext.(MockSpanContext).SpanID
sampled = opts.References[0].ReferencedContext.(MockSpanContext).Sampled
baggage = opts.References[0].ReferencedContext.(MockSpanContext).Baggage
}
spanContext := MockSpanContext{traceID, nextMockID(), sampled, baggage}
startTime := opts.StartTime
if startTime.IsZero() {
startTime = time.Now()
}
return &MockSpan{
ParentID: parentID,
OperationName: name,
StartTime: startTime,
tags: tags,
logs: []MockLogRecord{},
SpanContext: spanContext,
tracer: t,
}
}
// Tags returns a copy of tags accumulated by the span so far
func (s *MockSpan) Tags() map[string]interface{} {
s.RLock()
defer s.RUnlock()
tags := make(map[string]interface{})
for k, v := range s.tags {
tags[k] = v
}
return tags
}
// Tag returns a single tag
func (s *MockSpan) Tag(k string) interface{} {
s.RLock()
defer s.RUnlock()
return s.tags[k]
}
// Logs returns a copy of logs accumulated in the span so far
func (s *MockSpan) Logs() []MockLogRecord {
s.RLock()
defer s.RUnlock()
logs := make([]MockLogRecord, len(s.logs))
copy(logs, s.logs)
return logs
}
// Context belongs to the Span interface
func (s *MockSpan) Context() opentracing.SpanContext {
return s.SpanContext
}
// SetTag belongs to the Span interface
func (s *MockSpan) SetTag(key string, value interface{}) opentracing.Span {
s.Lock()
defer s.Unlock()
if key == string(ext.SamplingPriority) {
if v, ok := value.(uint16); ok {
s.SpanContext.Sampled = v > 0
return s
}
if v, ok := value.(int); ok {
s.SpanContext.Sampled = v > 0
return s
}
}
s.tags[key] = value
return s
}
// SetBaggageItem belongs to the Span interface
func (s *MockSpan) SetBaggageItem(key, val string) opentracing.Span {
s.Lock()
defer s.Unlock()
s.SpanContext = s.SpanContext.WithBaggageItem(key, val)
return s
}
// BaggageItem belongs to the Span interface
func (s *MockSpan) BaggageItem(key string) string {
s.RLock()
defer s.RUnlock()
return s.SpanContext.Baggage[key]
}
// Finish belongs to the Span interface
func (s *MockSpan) Finish() {
s.Lock()
s.FinishTime = time.Now()
s.Unlock()
s.tracer.recordSpan(s)
}
// FinishWithOptions belongs to the Span interface
func (s *MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {
s.Lock()
s.FinishTime = opts.FinishTime
s.Unlock()
// Handle any late-bound LogRecords.
for _, lr := range opts.LogRecords {
s.logFieldsWithTimestamp(lr.Timestamp, lr.Fields...)
}
// Handle (deprecated) BulkLogData.
for _, ld := range opts.BulkLogData {
if ld.Payload != nil {
s.logFieldsWithTimestamp(
ld.Timestamp,
log.String("event", ld.Event),
log.Object("payload", ld.Payload))
} else {
s.logFieldsWithTimestamp(
ld.Timestamp,
log.String("event", ld.Event))
}
}
s.tracer.recordSpan(s)
}
// String allows printing span for debugging
func (s *MockSpan) String() string {
return fmt.Sprintf(
"traceId=%d, spanId=%d, parentId=%d, sampled=%t, name=%s",
s.SpanContext.TraceID, s.SpanContext.SpanID, s.ParentID,
s.SpanContext.Sampled, s.OperationName)
}
// LogFields belongs to the Span interface
func (s *MockSpan) LogFields(fields ...log.Field) {
s.logFieldsWithTimestamp(time.Now(), fields...)
}
// The caller MUST NOT hold s.Lock
func (s *MockSpan) logFieldsWithTimestamp(ts time.Time, fields ...log.Field) {
lr := MockLogRecord{
Timestamp: ts,
Fields: make([]MockKeyValue, len(fields)),
}
for i, f := range fields {
outField := &(lr.Fields[i])
f.Marshal(outField)
}
s.Lock()
defer s.Unlock()
s.logs = append(s.logs, lr)
}
// LogKV belongs to the Span interface.
//
// This implementations coerces all "values" to strings, though that is not
// something all implementations need to do. Indeed, a motivated person can and
// probably should have this do a typed switch on the values.
func (s *MockSpan) LogKV(keyValues ...interface{}) {
if len(keyValues)%2 != 0 {
s.LogFields(log.Error(fmt.Errorf("Non-even keyValues len: %v", len(keyValues))))
return
}
fields, err := log.InterleavedKVToFields(keyValues...)
if err != nil {
s.LogFields(log.Error(err), log.String("function", "LogKV"))
return
}
s.LogFields(fields...)
}
// LogEvent belongs to the Span interface
func (s *MockSpan) LogEvent(event string) {
s.LogFields(log.String("event", event))
}
// LogEventWithPayload belongs to the Span interface
func (s *MockSpan) LogEventWithPayload(event string, payload interface{}) {
s.LogFields(log.String("event", event), log.Object("payload", payload))
}
// Log belongs to the Span interface
func (s *MockSpan) Log(data opentracing.LogData) {
panic("MockSpan.Log() no longer supported")
}
// SetOperationName belongs to the Span interface
func (s *MockSpan) SetOperationName(operationName string) opentracing.Span {
s.Lock()
defer s.Unlock()
s.OperationName = operationName
return s
}
// Tracer belongs to the Span interface
func (s *MockSpan) Tracer() opentracing.Tracer {
return s.tracer
}

View File

@@ -0,0 +1,105 @@
package mocktracer
import (
"sync"
"github.com/opentracing/opentracing-go"
)
// New returns a MockTracer opentracing.Tracer implementation that's intended
// to facilitate tests of OpenTracing instrumentation.
func New() *MockTracer {
t := &MockTracer{
finishedSpans: []*MockSpan{},
injectors: make(map[interface{}]Injector),
extractors: make(map[interface{}]Extractor),
}
// register default injectors/extractors
textPropagator := new(TextMapPropagator)
t.RegisterInjector(opentracing.TextMap, textPropagator)
t.RegisterExtractor(opentracing.TextMap, textPropagator)
httpPropagator := &TextMapPropagator{HTTPHeaders: true}
t.RegisterInjector(opentracing.HTTPHeaders, httpPropagator)
t.RegisterExtractor(opentracing.HTTPHeaders, httpPropagator)
return t
}
// MockTracer is only intended for testing OpenTracing instrumentation.
//
// It is entirely unsuitable for production use, but appropriate for tests
// that want to verify tracing behavior in other frameworks/applications.
type MockTracer struct {
sync.RWMutex
finishedSpans []*MockSpan
injectors map[interface{}]Injector
extractors map[interface{}]Extractor
}
// FinishedSpans returns all spans that have been Finish()'ed since the
// MockTracer was constructed or since the last call to its Reset() method.
func (t *MockTracer) FinishedSpans() []*MockSpan {
t.RLock()
defer t.RUnlock()
spans := make([]*MockSpan, len(t.finishedSpans))
copy(spans, t.finishedSpans)
return spans
}
// Reset clears the internally accumulated finished spans. Note that any
// extant MockSpans will still append to finishedSpans when they Finish(),
// even after a call to Reset().
func (t *MockTracer) Reset() {
t.Lock()
defer t.Unlock()
t.finishedSpans = []*MockSpan{}
}
// StartSpan belongs to the Tracer interface.
func (t *MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
sso := opentracing.StartSpanOptions{}
for _, o := range opts {
o.Apply(&sso)
}
return newMockSpan(t, operationName, sso)
}
// RegisterInjector registers injector for given format
func (t *MockTracer) RegisterInjector(format interface{}, injector Injector) {
t.injectors[format] = injector
}
// RegisterExtractor registers extractor for given format
func (t *MockTracer) RegisterExtractor(format interface{}, extractor Extractor) {
t.extractors[format] = extractor
}
// Inject belongs to the Tracer interface.
func (t *MockTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error {
spanContext, ok := sm.(MockSpanContext)
if !ok {
return opentracing.ErrInvalidCarrier
}
injector, ok := t.injectors[format]
if !ok {
return opentracing.ErrUnsupportedFormat
}
return injector.Inject(spanContext, carrier)
}
// Extract belongs to the Tracer interface.
func (t *MockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
extractor, ok := t.extractors[format]
if !ok {
return nil, opentracing.ErrUnsupportedFormat
}
return extractor.Extract(carrier)
}
func (t *MockTracer) recordSpan(span *MockSpan) {
t.Lock()
defer t.Unlock()
t.finishedSpans = append(t.finishedSpans, span)
}

View File

@@ -0,0 +1,120 @@
package mocktracer
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/opentracing/opentracing-go"
)
const mockTextMapIdsPrefix = "mockpfx-ids-"
const mockTextMapBaggagePrefix = "mockpfx-baggage-"
var emptyContext = MockSpanContext{}
// Injector is responsible for injecting SpanContext instances in a manner suitable
// for propagation via a format-specific "carrier" object. Typically the
// injection will take place across an RPC boundary, but message queues and
// other IPC mechanisms are also reasonable places to use an Injector.
type Injector interface {
// Inject takes `SpanContext` and injects it into `carrier`. The actual type
// of `carrier` depends on the `format` passed to `Tracer.Inject()`.
//
// Implementations may return opentracing.ErrInvalidCarrier or any other
// implementation-specific error if injection fails.
Inject(ctx MockSpanContext, carrier interface{}) error
}
// Extractor is responsible for extracting SpanContext instances from a
// format-specific "carrier" object. Typically the extraction will take place
// on the server side of an RPC boundary, but message queues and other IPC
// mechanisms are also reasonable places to use an Extractor.
type Extractor interface {
// Extract decodes a SpanContext instance from the given `carrier`,
// or (nil, opentracing.ErrSpanContextNotFound) if no context could
// be found in the `carrier`.
Extract(carrier interface{}) (MockSpanContext, error)
}
// TextMapPropagator implements Injector/Extractor for TextMap and HTTPHeaders formats.
type TextMapPropagator struct {
HTTPHeaders bool
}
// Inject implements the Injector interface
func (t *TextMapPropagator) Inject(spanContext MockSpanContext, carrier interface{}) error {
writer, ok := carrier.(opentracing.TextMapWriter)
if !ok {
return opentracing.ErrInvalidCarrier
}
// Ids:
writer.Set(mockTextMapIdsPrefix+"traceid", strconv.Itoa(spanContext.TraceID))
writer.Set(mockTextMapIdsPrefix+"spanid", strconv.Itoa(spanContext.SpanID))
writer.Set(mockTextMapIdsPrefix+"sampled", fmt.Sprint(spanContext.Sampled))
// Baggage:
for baggageKey, baggageVal := range spanContext.Baggage {
safeVal := baggageVal
if t.HTTPHeaders {
safeVal = url.QueryEscape(baggageVal)
}
writer.Set(mockTextMapBaggagePrefix+baggageKey, safeVal)
}
return nil
}
// Extract implements the Extractor interface
func (t *TextMapPropagator) Extract(carrier interface{}) (MockSpanContext, error) {
reader, ok := carrier.(opentracing.TextMapReader)
if !ok {
return emptyContext, opentracing.ErrInvalidCarrier
}
rval := MockSpanContext{0, 0, true, nil}
err := reader.ForeachKey(func(key, val string) error {
lowerKey := strings.ToLower(key)
switch {
case lowerKey == mockTextMapIdsPrefix+"traceid":
// Ids:
i, err := strconv.Atoi(val)
if err != nil {
return err
}
rval.TraceID = i
case lowerKey == mockTextMapIdsPrefix+"spanid":
// Ids:
i, err := strconv.Atoi(val)
if err != nil {
return err
}
rval.SpanID = i
case lowerKey == mockTextMapIdsPrefix+"sampled":
b, err := strconv.ParseBool(val)
if err != nil {
return err
}
rval.Sampled = b
case strings.HasPrefix(lowerKey, mockTextMapBaggagePrefix):
// Baggage:
if rval.Baggage == nil {
rval.Baggage = make(map[string]string)
}
safeVal := val
if t.HTTPHeaders {
// unescape errors are ignored, nothing can be done
if rawVal, err := url.QueryUnescape(val); err == nil {
safeVal = rawVal
}
}
rval.Baggage[lowerKey[len(mockTextMapBaggagePrefix):]] = safeVal
}
return nil
})
if rval.TraceID == 0 || rval.SpanID == 0 {
return emptyContext, opentracing.ErrSpanContextNotFound
}
if err != nil {
return emptyContext, err
}
return rval, nil
}

53
vendor/github.com/opentracing/opentracing-go/noop.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
package opentracing
import "github.com/opentracing/opentracing-go/log"
// A NoopTracer is a trivial implementation of Tracer for which all operations
// are no-ops.
type NoopTracer struct{}
type noopSpan struct{}
type noopSpanContext struct{}
var (
defaultNoopSpanContext = noopSpanContext{}
defaultNoopSpan = noopSpan{}
defaultNoopTracer = NoopTracer{}
)
const (
emptyString = ""
)
// noopSpanContext:
func (n noopSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
// noopSpan:
func (n noopSpan) Context() SpanContext { return defaultNoopSpanContext }
func (n noopSpan) SetBaggageItem(key, val string) Span { return defaultNoopSpan }
func (n noopSpan) BaggageItem(key string) string { return emptyString }
func (n noopSpan) SetTag(key string, value interface{}) Span { return n }
func (n noopSpan) LogFields(fields ...log.Field) {}
func (n noopSpan) LogKV(keyVals ...interface{}) {}
func (n noopSpan) Finish() {}
func (n noopSpan) FinishWithOptions(opts FinishOptions) {}
func (n noopSpan) SetOperationName(operationName string) Span { return n }
func (n noopSpan) Tracer() Tracer { return defaultNoopTracer }
func (n noopSpan) LogEvent(event string) {}
func (n noopSpan) LogEventWithPayload(event string, payload interface{}) {}
func (n noopSpan) Log(data LogData) {}
// StartSpan belongs to the Tracer interface.
func (n NoopTracer) StartSpan(operationName string, opts ...StartSpanOption) Span {
return defaultNoopSpan
}
// Inject belongs to the Tracer interface.
func (n NoopTracer) Inject(sp SpanContext, format interface{}, carrier interface{}) error {
return nil
}
// Extract belongs to the Tracer interface.
func (n NoopTracer) Extract(format interface{}, carrier interface{}) (SpanContext, error) {
return nil, ErrSpanContextNotFound
}

View File

@@ -0,0 +1,176 @@
package opentracing
import (
"errors"
"net/http"
)
///////////////////////////////////////////////////////////////////////////////
// CORE PROPAGATION INTERFACES:
///////////////////////////////////////////////////////////////////////////////
var (
// ErrUnsupportedFormat occurs when the `format` passed to Tracer.Inject() or
// Tracer.Extract() is not recognized by the Tracer implementation.
ErrUnsupportedFormat = errors.New("opentracing: Unknown or unsupported Inject/Extract format")
// ErrSpanContextNotFound occurs when the `carrier` passed to
// Tracer.Extract() is valid and uncorrupted but has insufficient
// information to extract a SpanContext.
ErrSpanContextNotFound = errors.New("opentracing: SpanContext not found in Extract carrier")
// ErrInvalidSpanContext errors occur when Tracer.Inject() is asked to
// operate on a SpanContext which it is not prepared to handle (for
// example, since it was created by a different tracer implementation).
ErrInvalidSpanContext = errors.New("opentracing: SpanContext type incompatible with tracer")
// ErrInvalidCarrier errors occur when Tracer.Inject() or Tracer.Extract()
// implementations expect a different type of `carrier` than they are
// given.
ErrInvalidCarrier = errors.New("opentracing: Invalid Inject/Extract carrier")
// ErrSpanContextCorrupted occurs when the `carrier` passed to
// Tracer.Extract() is of the expected type but is corrupted.
ErrSpanContextCorrupted = errors.New("opentracing: SpanContext data corrupted in Extract carrier")
)
///////////////////////////////////////////////////////////////////////////////
// BUILTIN PROPAGATION FORMATS:
///////////////////////////////////////////////////////////////////////////////
// BuiltinFormat is used to demarcate the values within package `opentracing`
// that are intended for use with the Tracer.Inject() and Tracer.Extract()
// methods.
type BuiltinFormat byte
const (
// Binary represents SpanContexts as opaque binary data.
//
// For Tracer.Inject(): the carrier must be an `io.Writer`.
//
// For Tracer.Extract(): the carrier must be an `io.Reader`.
Binary BuiltinFormat = iota
// TextMap represents SpanContexts as key:value string pairs.
//
// Unlike HTTPHeaders, the TextMap format does not restrict the key or
// value character sets in any way.
//
// For Tracer.Inject(): the carrier must be a `TextMapWriter`.
//
// For Tracer.Extract(): the carrier must be a `TextMapReader`.
TextMap
// HTTPHeaders represents SpanContexts as HTTP header string pairs.
//
// Unlike TextMap, the HTTPHeaders format requires that the keys and values
// be valid as HTTP headers as-is (i.e., character casing may be unstable
// and special characters are disallowed in keys, values should be
// URL-escaped, etc).
//
// For Tracer.Inject(): the carrier must be a `TextMapWriter`.
//
// For Tracer.Extract(): the carrier must be a `TextMapReader`.
//
// See HTTPHeaderCarrier for an implementation of both TextMapWriter
// and TextMapReader that defers to an http.Header instance for storage.
// For example, Inject():
//
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
// err := span.Tracer().Inject(
// span, opentracing.HTTPHeaders, carrier)
//
// Or Extract():
//
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
// span, err := tracer.Extract(
// opentracing.HTTPHeaders, carrier)
//
HTTPHeaders
)
// TextMapWriter is the Inject() carrier for the TextMap builtin format. With
// it, the caller can encode a SpanContext for propagation as entries in a map
// of unicode strings.
type TextMapWriter interface {
// Set a key:value pair to the carrier. Multiple calls to Set() for the
// same key leads to undefined behavior.
//
// NOTE: The backing store for the TextMapWriter may contain data unrelated
// to SpanContext. As such, Inject() and Extract() implementations that
// call the TextMapWriter and TextMapReader interfaces must agree on a
// prefix or other convention to distinguish their own key:value pairs.
Set(key, val string)
}
// TextMapReader is the Extract() carrier for the TextMap builtin format. With it,
// the caller can decode a propagated SpanContext as entries in a map of
// unicode strings.
type TextMapReader interface {
// ForeachKey returns TextMap contents via repeated calls to the `handler`
// function. If any call to `handler` returns a non-nil error, ForeachKey
// terminates and returns that error.
//
// NOTE: The backing store for the TextMapReader may contain data unrelated
// to SpanContext. As such, Inject() and Extract() implementations that
// call the TextMapWriter and TextMapReader interfaces must agree on a
// prefix or other convention to distinguish their own key:value pairs.
//
// The "foreach" callback pattern reduces unnecessary copying in some cases
// and also allows implementations to hold locks while the map is read.
ForeachKey(handler func(key, val string) error) error
}
// TextMapCarrier allows the use of regular map[string]string
// as both TextMapWriter and TextMapReader.
type TextMapCarrier map[string]string
// ForeachKey conforms to the TextMapReader interface.
func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
for k, v := range c {
if err := handler(k, v); err != nil {
return err
}
}
return nil
}
// Set implements Set() of opentracing.TextMapWriter
func (c TextMapCarrier) Set(key, val string) {
c[key] = val
}
// HTTPHeadersCarrier satisfies both TextMapWriter and TextMapReader.
//
// Example usage for server side:
//
// carrier := opentracing.HttpHeadersCarrier(httpReq.Header)
// spanContext, err := tracer.Extract(opentracing.HttpHeaders, carrier)
//
// Example usage for client side:
//
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
// err := tracer.Inject(
// span.Context(),
// opentracing.HttpHeaders,
// carrier)
//
type HTTPHeadersCarrier http.Header
// Set conforms to the TextMapWriter interface.
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Add(key, val)
}
// ForeachKey conforms to the TextMapReader interface.
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
for k, vals := range c {
for _, v := range vals {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}

185
vendor/github.com/opentracing/opentracing-go/span.go generated vendored Normal file
View File

@@ -0,0 +1,185 @@
package opentracing
import (
"time"
"github.com/opentracing/opentracing-go/log"
)
// SpanContext represents Span state that must propagate to descendant Spans and across process
// boundaries (e.g., a <trace_id, span_id, sampled> tuple).
type SpanContext interface {
// ForeachBaggageItem grants access to all baggage items stored in the
// SpanContext.
// The handler function will be called for each baggage key/value pair.
// The ordering of items is not guaranteed.
//
// The bool return value indicates if the handler wants to continue iterating
// through the rest of the baggage items; for example if the handler is trying to
// find some baggage item by pattern matching the name, it can return false
// as soon as the item is found to stop further iterations.
ForeachBaggageItem(handler func(k, v string) bool)
}
// Span represents an active, un-finished span in the OpenTracing system.
//
// Spans are created by the Tracer interface.
type Span interface {
// Sets the end timestamp and finalizes Span state.
//
// With the exception of calls to Context() (which are always allowed),
// Finish() must be the last call made to any span instance, and to do
// otherwise leads to undefined behavior.
Finish()
// FinishWithOptions is like Finish() but with explicit control over
// timestamps and log data.
FinishWithOptions(opts FinishOptions)
// Context() yields the SpanContext for this Span. Note that the return
// value of Context() is still valid after a call to Span.Finish(), as is
// a call to Span.Context() after a call to Span.Finish().
Context() SpanContext
// Sets or changes the operation name.
SetOperationName(operationName string) Span
// Adds a tag to the span.
//
// If there is a pre-existing tag set for `key`, it is overwritten.
//
// Tag values can be numeric types, strings, or bools. The behavior of
// other tag value types is undefined at the OpenTracing level. If a
// tracing system does not know how to handle a particular value type, it
// may ignore the tag, but shall not panic.
SetTag(key string, value interface{}) Span
// LogFields is an efficient and type-checked way to record key:value
// logging data about a Span, though the programming interface is a little
// more verbose than LogKV(). Here's an example:
//
// span.LogFields(
// log.String("event", "soft error"),
// log.String("type", "cache timeout"),
// log.Int("waited.millis", 1500))
//
// Also see Span.FinishWithOptions() and FinishOptions.BulkLogData.
LogFields(fields ...log.Field)
// LogKV is a concise, readable way to record key:value logging data about
// a Span, though unfortunately this also makes it less efficient and less
// type-safe than LogFields(). Here's an example:
//
// span.LogKV(
// "event", "soft error",
// "type", "cache timeout",
// "waited.millis", 1500)
//
// For LogKV (as opposed to LogFields()), the parameters must appear as
// key-value pairs, like
//
// span.LogKV(key1, val1, key2, val2, key3, val3, ...)
//
// The keys must all be strings. The values may be strings, numeric types,
// bools, Go error instances, or arbitrary structs.
//
// (Note to implementors: consider the log.InterleavedKVToFields() helper)
LogKV(alternatingKeyValues ...interface{})
// SetBaggageItem sets a key:value pair on this Span and its SpanContext
// that also propagates to descendants of this Span.
//
// SetBaggageItem() enables powerful functionality given a full-stack
// opentracing integration (e.g., arbitrary application data from a mobile
// app can make it, transparently, all the way into the depths of a storage
// system), and with it some powerful costs: use this feature with care.
//
// IMPORTANT NOTE #1: SetBaggageItem() will only propagate baggage items to
// *future* causal descendants of the associated Span.
//
// IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and
// value is copied into every local *and remote* child of the associated
// Span, and that can add up to a lot of network and cpu overhead.
//
// Returns a reference to this Span for chaining.
SetBaggageItem(restrictedKey, value string) Span
// Gets the value for a baggage item given its key. Returns the empty string
// if the value isn't found in this Span.
BaggageItem(restrictedKey string) string
// Provides access to the Tracer that created this Span.
Tracer() Tracer
// Deprecated: use LogFields or LogKV
LogEvent(event string)
// Deprecated: use LogFields or LogKV
LogEventWithPayload(event string, payload interface{})
// Deprecated: use LogFields or LogKV
Log(data LogData)
}
// LogRecord is data associated with a single Span log. Every LogRecord
// instance must specify at least one Field.
type LogRecord struct {
Timestamp time.Time
Fields []log.Field
}
// FinishOptions allows Span.FinishWithOptions callers to override the finish
// timestamp and provide log data via a bulk interface.
type FinishOptions struct {
// FinishTime overrides the Span's finish time, or implicitly becomes
// time.Now() if FinishTime.IsZero().
//
// FinishTime must resolve to a timestamp that's >= the Span's StartTime
// (per StartSpanOptions).
FinishTime time.Time
// LogRecords allows the caller to specify the contents of many LogFields()
// calls with a single slice. May be nil.
//
// None of the LogRecord.Timestamp values may be .IsZero() (i.e., they must
// be set explicitly). Also, they must be >= the Span's start timestamp and
// <= the FinishTime (or time.Now() if FinishTime.IsZero()). Otherwise the
// behavior of FinishWithOptions() is undefined.
//
// If specified, the caller hands off ownership of LogRecords at
// FinishWithOptions() invocation time.
//
// If specified, the (deprecated) BulkLogData must be nil or empty.
LogRecords []LogRecord
// BulkLogData is DEPRECATED.
BulkLogData []LogData
}
// LogData is DEPRECATED
type LogData struct {
Timestamp time.Time
Event string
Payload interface{}
}
// ToLogRecord converts a deprecated LogData to a non-deprecated LogRecord
func (ld *LogData) ToLogRecord() LogRecord {
var literalTimestamp time.Time
if ld.Timestamp.IsZero() {
literalTimestamp = time.Now()
} else {
literalTimestamp = ld.Timestamp
}
rval := LogRecord{
Timestamp: literalTimestamp,
}
if ld.Payload == nil {
rval.Fields = []log.Field{
log.String("event", ld.Event),
}
} else {
rval.Fields = []log.Field{
log.String("event", ld.Event),
log.Object("payload", ld.Payload),
}
}
return rval
}

305
vendor/github.com/opentracing/opentracing-go/tracer.go generated vendored Normal file
View File

@@ -0,0 +1,305 @@
package opentracing
import "time"
// Tracer is a simple, thin interface for Span creation and SpanContext
// propagation.
type Tracer interface {
// Create, start, and return a new Span with the given `operationName` and
// incorporate the given StartSpanOption `opts`. (Note that `opts` borrows
// from the "functional options" pattern, per
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
//
// A Span with no SpanReference options (e.g., opentracing.ChildOf() or
// opentracing.FollowsFrom()) becomes the root of its own trace.
//
// Examples:
//
// var tracer opentracing.Tracer = ...
//
// // The root-span case:
// sp := tracer.StartSpan("GetFeed")
//
// // The vanilla child span case:
// sp := tracer.StartSpan(
// "GetFeed",
// opentracing.ChildOf(parentSpan.Context()))
//
// // All the bells and whistles:
// sp := tracer.StartSpan(
// "GetFeed",
// opentracing.ChildOf(parentSpan.Context()),
// opentracing.Tag("user_agent", loggedReq.UserAgent),
// opentracing.StartTime(loggedReq.Timestamp),
// )
//
StartSpan(operationName string, opts ...StartSpanOption) Span
// Inject() takes the `sm` SpanContext instance and injects it for
// propagation within `carrier`. The actual type of `carrier` depends on
// the value of `format`.
//
// OpenTracing defines a common set of `format` values (see BuiltinFormat),
// and each has an expected carrier type.
//
// Other packages may declare their own `format` values, much like the keys
// used by `context.Context` (see
// https://godoc.org/golang.org/x/net/context#WithValue).
//
// Example usage (sans error handling):
//
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
// err := tracer.Inject(
// span.Context(),
// opentracing.HttpHeaders,
// carrier)
//
// NOTE: All opentracing.Tracer implementations MUST support all
// BuiltinFormats.
//
// Implementations may return opentracing.ErrUnsupportedFormat if `format`
// is not supported by (or not known by) the implementation.
//
// Implementations may return opentracing.ErrInvalidCarrier or any other
// implementation-specific error if the format is supported but injection
// fails anyway.
//
// See Tracer.Extract().
Inject(sm SpanContext, format interface{}, carrier interface{}) error
// Extract() returns a SpanContext instance given `format` and `carrier`.
//
// OpenTracing defines a common set of `format` values (see BuiltinFormat),
// and each has an expected carrier type.
//
// Other packages may declare their own `format` values, much like the keys
// used by `context.Context` (see
// https://godoc.org/golang.org/x/net/context#WithValue).
//
// Example usage (with StartSpan):
//
//
// carrier := opentracing.HttpHeadersCarrier(httpReq.Header)
// clientContext, err := tracer.Extract(opentracing.HttpHeaders, carrier)
//
// // ... assuming the ultimate goal here is to resume the trace with a
// // server-side Span:
// var serverSpan opentracing.Span
// if err == nil {
// span = tracer.StartSpan(
// rpcMethodName, ext.RPCServerOption(clientContext))
// } else {
// span = tracer.StartSpan(rpcMethodName)
// }
//
//
// NOTE: All opentracing.Tracer implementations MUST support all
// BuiltinFormats.
//
// Return values:
// - A successful Extract returns a SpanContext instance and a nil error
// - If there was simply no SpanContext to extract in `carrier`, Extract()
// returns (nil, opentracing.ErrSpanContextNotFound)
// - If `format` is unsupported or unrecognized, Extract() returns (nil,
// opentracing.ErrUnsupportedFormat)
// - If there are more fundamental problems with the `carrier` object,
// Extract() may return opentracing.ErrInvalidCarrier,
// opentracing.ErrSpanContextCorrupted, or implementation-specific
// errors.
//
// See Tracer.Inject().
Extract(format interface{}, carrier interface{}) (SpanContext, error)
}
// StartSpanOptions allows Tracer.StartSpan() callers and implementors a
// mechanism to override the start timestamp, specify Span References, and make
// a single Tag or multiple Tags available at Span start time.
//
// StartSpan() callers should look at the StartSpanOption interface and
// implementations available in this package.
//
// Tracer implementations can convert a slice of `StartSpanOption` instances
// into a `StartSpanOptions` struct like so:
//
// func StartSpan(opName string, opts ...opentracing.StartSpanOption) {
// sso := opentracing.StartSpanOptions{}
// for _, o := range opts {
// o.Apply(&sso)
// }
// ...
// }
//
type StartSpanOptions struct {
// Zero or more causal references to other Spans (via their SpanContext).
// If empty, start a "root" Span (i.e., start a new trace).
References []SpanReference
// StartTime overrides the Span's start time, or implicitly becomes
// time.Now() if StartTime.IsZero().
StartTime time.Time
// Tags may have zero or more entries; the restrictions on map values are
// identical to those for Span.SetTag(). May be nil.
//
// If specified, the caller hands off ownership of Tags at
// StartSpan() invocation time.
Tags map[string]interface{}
}
// StartSpanOption instances (zero or more) may be passed to Tracer.StartSpan.
//
// StartSpanOption borrows from the "functional options" pattern, per
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
type StartSpanOption interface {
Apply(*StartSpanOptions)
}
// SpanReferenceType is an enum type describing different categories of
// relationships between two Spans. If Span-2 refers to Span-1, the
// SpanReferenceType describes Span-1 from Span-2's perspective. For example,
// ChildOfRef means that Span-1 created Span-2.
//
// NOTE: Span-1 and Span-2 do *not* necessarily depend on each other for
// completion; e.g., Span-2 may be part of a background job enqueued by Span-1,
// or Span-2 may be sitting in a distributed queue behind Span-1.
type SpanReferenceType int
const (
// ChildOfRef refers to a parent Span that caused *and* somehow depends
// upon the new child Span. Often (but not always), the parent Span cannot
// finish until the child Span does.
//
// An timing diagram for a ChildOfRef that's blocked on the new Span:
//
// [-Parent Span---------]
// [-Child Span----]
//
// See http://opentracing.io/spec/
//
// See opentracing.ChildOf()
ChildOfRef SpanReferenceType = iota
// FollowsFromRef refers to a parent Span that does not depend in any way
// on the result of the new child Span. For instance, one might use
// FollowsFromRefs to describe pipeline stages separated by queues,
// or a fire-and-forget cache insert at the tail end of a web request.
//
// A FollowsFromRef Span is part of the same logical trace as the new Span:
// i.e., the new Span is somehow caused by the work of its FollowsFromRef.
//
// All of the following could be valid timing diagrams for children that
// "FollowFrom" a parent.
//
// [-Parent Span-] [-Child Span-]
//
//
// [-Parent Span--]
// [-Child Span-]
//
//
// [-Parent Span-]
// [-Child Span-]
//
// See http://opentracing.io/spec/
//
// See opentracing.FollowsFrom()
FollowsFromRef
)
// SpanReference is a StartSpanOption that pairs a SpanReferenceType and a
// referenced SpanContext. See the SpanReferenceType documentation for
// supported relationships. If SpanReference is created with
// ReferencedContext==nil, it has no effect. Thus it allows for a more concise
// syntax for starting spans:
//
// sc, _ := tracer.Extract(someFormat, someCarrier)
// span := tracer.StartSpan("operation", opentracing.ChildOf(sc))
//
// The `ChildOf(sc)` option above will not panic if sc == nil, it will just
// not add the parent span reference to the options.
type SpanReference struct {
Type SpanReferenceType
ReferencedContext SpanContext
}
// Apply satisfies the StartSpanOption interface.
func (r SpanReference) Apply(o *StartSpanOptions) {
if r.ReferencedContext != nil {
o.References = append(o.References, r)
}
}
// ChildOf returns a StartSpanOption pointing to a dependent parent span.
// If sc == nil, the option has no effect.
//
// See ChildOfRef, SpanReference
func ChildOf(sc SpanContext) SpanReference {
return SpanReference{
Type: ChildOfRef,
ReferencedContext: sc,
}
}
// FollowsFrom returns a StartSpanOption pointing to a parent Span that caused
// the child Span but does not directly depend on its result in any way.
// If sc == nil, the option has no effect.
//
// See FollowsFromRef, SpanReference
func FollowsFrom(sc SpanContext) SpanReference {
return SpanReference{
Type: FollowsFromRef,
ReferencedContext: sc,
}
}
// StartTime is a StartSpanOption that sets an explicit start timestamp for the
// new Span.
type StartTime time.Time
// Apply satisfies the StartSpanOption interface.
func (t StartTime) Apply(o *StartSpanOptions) {
o.StartTime = time.Time(t)
}
// Tags are a generic map from an arbitrary string key to an opaque value type.
// The underlying tracing system is responsible for interpreting and
// serializing the values.
type Tags map[string]interface{}
// Apply satisfies the StartSpanOption interface.
func (t Tags) Apply(o *StartSpanOptions) {
if o.Tags == nil {
o.Tags = make(map[string]interface{})
}
for k, v := range t {
o.Tags[k] = v
}
}
// Tag may be passed as a StartSpanOption to add a tag to new spans,
// or its Set method may be used to apply the tag to an existing Span,
// for example:
//
// tracer.StartSpan("opName", Tag{"Key", value})
//
// or
//
// Tag{"key", value}.Set(span)
type Tag struct {
Key string
Value interface{}
}
// Apply satisfies the StartSpanOption interface.
func (t Tag) Apply(o *StartSpanOptions) {
if o.Tags == nil {
o.Tags = make(map[string]interface{})
}
o.Tags[t.Key] = t.Value
}
// Set applies the tag to an existing Span.
func (t Tag) Set(s Span) {
s.SetTag(t.Key, t.Value)
}

8
vendor/manifest vendored
View File

@@ -1116,6 +1116,14 @@
"branch": "master",
"path": "/libcontainer/utils"
},
{
"importpath": "github.com/opentracing/opentracing-go",
"repository": "https://github.com/opentracing/opentracing-go",
"vcs": "git",
"revision": "902ca977fd85455c364050f985eba376b44315f0",
"branch": "master",
"notests": true
},
{
"importpath": "github.com/paypal/ionet",
"repository": "https://github.com/paypal/ionet",