1.

The OpenTracing – Go package is an API for openTracing’s Golang platform

The Jaeger-client-Go software package is a library for implementing The OpenTracing Go Tracer for Jaeger

Special note: The import path for this library is based on github.com/uber. Do not attempt to import using github.com/jaegertracing, this will not compile. Officials will revisit the issue in the next release

Correct import method:

import "github.com/uber/jaeger-client-go"
Copy the code

Error import mode:

import "github.com/jaegertracing/jaeger-client-go"
Copy the code

The reason why these two packages are mentioned in this article is that one is the OpenTracing API of Golang platform, and the other implements OpenTracing. By combining the two, you can better understand how to use them in projects.

2.Tracer

Tracer in OpenTracing – Go is a simple interface type used for creating spans and propagating SpanContext.

type Tracer interface { StartSpan(operationName string, opts ... StartSpanOption) Span Inject(sm SpanContext, format interface{}, carrier interface{}) error Extract(format interface{}, carrier interface{}) (SpanContext, error) }Copy the code

This section, special special introductionStartSpan.StartSpanMethod uses the givenoperationNameAnd offers a range ofStartSpanOptionOptions to create, open, and return a new Span.

Type StartSpanOptions struct {// Zero or more causal references to other spans (via their SpanContext) // If empty, open a root 'Span' (for example: SpanReference []SpanReference // this field overwrites the StartTime of a Span if starttime.iszero (), StartTime time. time // The label may have zero or more entries; This map value has the same restrictions as sp.settag (). Can be nil. // If specified, the caller hands over ownership of the tag when StartSpan () is called. Tags map[string]interface{} } type StartSpanOption interface { Apply(*StartSpanOptions) }Copy the code

StartSpanOption is an interface type. The interface type is used because any method that implements the Apply(*StartSpanOptions) method can be used as an argument to the StartSpan method. This is also the universal pattern in OpenTracing – Go, which is like a universal standard that anyone who implements it can use here. It’s a pattern we’ll see a lot more in the future.

The important thing is the StartSpanOptions. All the StartSpanOption parameters provided by the StartSpan method will be applied to this structure. There is nothing special about it. It mainly covers three aspects: the start time of a Span, the list of references of a Span to other Spans, and the mapping of multiple tags.

The StartSpanOptions structure allows the tracer.startSPAN () caller and implementer to use a mechanism to override the start timestamp, specify Span References and provide single or multiple tags at the start time of the Span.

The StartSpan() caller should look for the StartSpanOption interface and implementer available in this package. The first StartSpanOption interface implementer: SpanReference, which is used to set References in the StartSpanOptions structure

type SpanReferenceType int const ( ChildOfRef SpanReferenceType = iota FollowsFromRef ) type SpanReference struct { Type  SpanReferenceType ReferencedContext SpanContext } func (r SpanReference) Apply(o *StartSpanOptions) { if r.ReferencedContext ! = nil { o.References = append(o.References, r) } } func ChildOf(sc SpanContext) SpanReference { return SpanReference{ Type: ChildOfRef, ReferencedContext: sc, } } func FollowsFrom(sc SpanContext) SpanReference { return SpanReference{ Type: FollowsFromRef, ReferencedContext: sc, } }Copy the code

As you can see from the above method, it doesn’t matter if the ReferencedContext provided by SpanReference at creation time is nil. For example, in the code below

sc, _ := tracer.Extract(someFormat, someCarrier)
span := tracer.StartSpan("operation", opentracing.ChildOf(sc))
Copy the code

The provided sc== nil opentracing.ChildOf method also does not panic

The second StartSpanOption interface implementer: StartTime, which is used to set StartSpanOptions in the StartSpanOptions structure

type StartTime time.Time // Apply satisfies the StartSpanOption interface. func (t StartTime) Apply(o *StartSpanOptions)  { o.StartTime = time.Time(t) }Copy the code

StartTime is used to explicitly set the StartTime of a new Span.

The third StartSpanOption interface implementer: Tags, which is used to set the Tags in the StartSpanOptions structure

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
	}
}
Copy the code

The fourth StartSpanOption interface implementer: Tag, also used to set Tags in the StartSpanOptions structure

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) }Copy the code

Not only can a Tag be used as StartSpanOption to add a Tag to a new Span, but you can also Set a Tag on an existing Span using the Set method

The Tracer implementer can convert a slice of the StartSpanOption interface instance to a StartSpanOptions structure as shown below

func StartSpan(opName string, opts ... opentracing.StartSpanOption) { sso := opentracing.StartSpanOptions{} for _, o := range opts { o.Apply(&sso) } ... }Copy the code

If a Span does not provide a SpanReference option, it becomes the root of its own trace trace. Such as:

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),
	)
Copy the code

You probably don’t know much about StartSpan until now. It doesn’t matter. Let’s learn how jaeger-client-Go implements the StartSpan method in our Tracer interface and records StartTime and other startup information.

func (t *Tracer) StartSpan( operationName string, options ... opentracing.StartSpanOption, ) opentracing.Span { sso := opentracing.StartSpanOptions{} for _, o := range options { o.Apply(&sso) } return t.startSpanWithOptions(operationName, sso) }Copy the code

Look at this, the whole code idea is as mentioned above, all options are applied to SSO. We then call the following startSpanWithOptions method, which returns OpenTracing.Span, which we’ll look at later after SpanContext and Span.

Inject/ExtractI’ll talk a little bit about the methods, and I’ll talk a little bit more about the methods, because at the beginning, our focus was how does Tracer turn on oneSpanThe injected and extracted content will be used at a later time.

The Inject() method takes an instance of type SM SpanContext and then propagates it into a carrier carrier whose actual type 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 can declare their own format values, like the key used in golang Context.context

func WithValue(parent Context, key, val interface{}) Context
Copy the code

Note: All openTracing.Tracer implementations must support the built-in format. If the implementor format does not support or unknown, then returns the opentracing. ErrUnsupportedFormat if format support, but failed in injection, Implementers will return opentracing. ErrInvalidCarrier or other implementer specific error.

The Extract() method returns a SpanContext instance based on the format and carrier carrier provided

The return value:

  • A successful extraction method execution returns a SpanContext instance, and a nil error
  • If the carriercarrierThe Extract() method returns nil andopentracing.ErrSpanContextNotFound
  • ifformatUnsupported or unrecognized, Extract() returns nil oropentracing.ErrUnsupportedFormat
  • ifcarrierObject has more basic problems, and the Extract() method returnsopentracing.ErrInvalidCarrier.opentracing.ErrSpanContextCorruptedor

Other implementor-specific errors.

3. The Span and SpanContext

Both Span and SpanContext in OpenTracing – Go are interface types

type SpanContext interface {
	ForeachBaggageItem(handler func(k, v string) bool)
}
Copy the code

SpanContext represents the Span state that must be propagated to subsequent spans and across process boundaries (for example, a
meta-ancestor).
,>

The required method, ForeachBaggageItem, authorizes access to all baggage Items stored on the SpanContext, where the method’s argument handler is a function type that will be applied to eachBaggage key-value pair. The order of items is not guaranteed, and the resulting return value is a bool indicating whether the handler should continue iterating through subsequent baggage items. For example, if the Handler wanted to find some Baggage Item by pattern matching name, once it found that item, it could return false to stop further iteration.

At this point, you can pull out Jaeger-client-Go to see his implementation

Type TraceID struct {High, Low uint64} // SpanID Indicates the unique 64-bit identifier of a SPAN. Type SpanID uint64 // SpanContext indicates the identity and status of the transmitted SPAN. Type SpanContext struct {// TraceID represents the globally unique ID of the trace. TraceID is usually produced as a random number. TraceID // spanID indicates the spanID that must be unique in its trace, but need not be globally unique. SpanID spanID // parentID indicates the ID of the parent SPAN. If the current span is a root span, the value must be 0 parentID SpanID // Distributed context package. It is an instant snapshot. Baggage map[string]string // When extracting the context from the TextMap carrier, you can set the debugID to some associated ID. DebugID String // samplingState Shares samplingState *samplingState across all spans // Identifies span context as a parent remote bool} func  (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { for k, v := range c.baggage { if ! handler(k, v) { break } } }Copy the code

Now look at Span

type Span interface { Finish() FinishWithOptions(opts FinishOptions) Context() SpanContext SetOperationName(operationName string) Span SetTag(key string, value interface{}) Span LogFields(fields ... log.Field) LogKV(alternatingKeyValues ... interface{}) SetBaggageItem(restrictedKey, value string) Span BaggageItem(restrictedKey string) string 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) }Copy the code

A Span represents an active, unfinished Span in the OpenTracing system. Created by the Tracer interface. Let’s look at the methods we need to implement in Span

The first is the Finish() method, which sets the end timestamp of the Span and sets the end state of the Span. Finish() must be the last call to any SPAN instance, except for the call to Context() (always allowed), otherwise undefined behavior will result.

The second method is FinishWithOptions(opts FinishOptions), which is just like Finish() but has explicit control over timestamp and log data. Parameter a data of type FinishOptions.

// LogRecord is the Span log associated data. Each LogRecord instance must specify at least one Field type LogRecord struct {Timestamp time.time Fields [] log.field} // FinishOptions FinishOptions struct {// FinishTime overrides the Span's finish time, Or advertised. Now() if finishtime.iszero (). If finishtime.iszero () sets the Span end time implicitly to the current time. // FinishTime must be resolved to a timestamp greater than or equal to sp.startTime. FinishTime time.time // LogRecords allows the caller to specify the contents of many LogFields() calls using a single slice. It could be zero. // logRecord.timestamp is called. IsZero() cannot return true, which means that its timestamp must be set explicitly. // Also, their values must be greater than or equal to the start time of Span and less than or equal to the end time of Span. // Otherwise the behavior of FinishWithOptions() is unknown. // // If specified, the caller hands over ownership of LogRecords when FinishWithOptions() is called. // // If specified, deprecated fields must be set to nil or null. LogRecords []LogRecord // BulkLogData is deprecated. BulkLogData []LogData }Copy the code

The third method is Context() SpanContext which returns the SpanContext for that Span. Note that the return value of Context() is still valid after the sp.finish () method call is complete. The fourth method is SetOperationName(operationName string) Span which is used to set or change the operationName. Return a reference to this Span for linking. The fifth method is SetTag(key String, value Interface {}) Span which is used to add a label to a Span. If the key already exists, the operation overrides the previous value. A value can be a number, a string, or a Boolean. Other types of data as values are unknown at the OpenTracing level. If the trace system does not know how to handle a particular value type, it will ignore the Tag but will not panic. Similarly, the method will return a reference to a Span that can be chained.

The sixth method is LogFields(fields… Log.field) this method is an efficient and type-aware way to log Span key:value log data, although the programming interface is more verbose than LogKV(). Here is an example:

span.LogFields(
	log.String("event", "soft error"),
	log.String("type", "cache timeout"),
    log.Int("waited.millis", 1500))
Copy the code

The seventh method is LogKV(alternatingKeyValues… Interface {}), which is a concise, readable way to record key: value record data about a Span, although unfortunately this also makes LogKV less efficient and type-safe than LogFields (). Example:

span.LogKV(
	"event", "soft error",
    "type", "cache timeout",
    "waited.millis", 1500)
Copy the code

For LogKV(as opposed to LogFields ()), the argument must be displayed as a key-value pair, like sp.logkv (key1, val1, key2, val2, key3, val3…). All keys must be strings, and values can be strings, numbers, Booleans, Go Error instances, or any structure type.

The eighth method, SetBaggageItem(restrictedKey, Value String) Span, is used to set a key-value pair on a Span and its SpanContext propagates it to descendants of that Span. SetBaggageItem () implements powerful features and provides full-stack OpenTracing integration (for example, any application data from a mobile app can be transparently pushed into a storage system). And comes with some powerful costs: use this feature with caution. Two things to note in particular are: 1.SetBaggageItem() only propagates the row baggage items to the Span of the association of the future causal relationship. 2. It needs to be used with extreme caution. Because each key and value is copied to each local and remote child node of the associated Span, this can add significant network and CPU overhead.

Again, the method returns a reference to Span, which can be used for chained operations

The ninth method is BaggageItem(restrictedKey String) String, which is used to get the value of the key specified in baggage and return an empty string if the given value is not found in Span.

The tenth method, Tracer() Tracer, is used to access the Tracer that created the Span

Subsequent methods that are already out of date need not be concerned.

Take a look at the specific Span implementer in Jaeger-client-Go

Type Span struct {referenceCounter int32 sync.rwmutex tracer * tracer // referenceCounter int32 sync.rwmutex tracer * tracer // TODO: (breaking change) change to use a pointer context SpanContext operationName string Identifies the span as the root of the SPAN tree in the current process. firstInProcess bool Indicates the start time stamp of the SPAN, with subtle precision. StartTime time. time // duration Returns the duration of the span with precision in microseconds. Duration time.Duration // Attach to current SPAN to Tag list tags []Tag // Current SPAN to micro diary logs []opentracing.LogRecord // References References References references []Reference Observer ContribSpanObserver }Copy the code

At this point, look back at the startSpanWithOptions that Tracer calls in the StartSpan function that creates a Span in jaeger-client-go

func (t *Tracer) startSpanWithOptions( operationName string, options opentracing.StartSpanOptions, ) opentracing.Span { if options.StartTime.IsZero() { options.StartTime = t.timeNow() } // Predicate whether the given span context is an empty reference // or may be used as parent / debug ID / baggage items source isEmptyReference := func(ctx SpanContext) bool { return ! ctx.IsValid() && ! ctx.isDebugIDContainerOnly() && len(ctx.baggage) == 0 } var references []Reference var parent SpanContext var hasParent bool // need this because `parent` is a value, not reference var ctx SpanContext var isSelfRef bool for _, ref := range options.References { ctxRef, ok := ref.ReferencedContext.(SpanContext) if ! ok { t.logger.Error(fmt.Sprintf( "Reference contains invalid type of SpanReference: %s", reflect.ValueOf(ref.ReferencedContext))) continue } if isEmptyReference(ctxRef) { continue } if ref.Type == selfRefType { isSelfRef = true ctx = ctxRef continue } if ctxRef.IsValid() { // we don't want empty context that contains only debug-id or baggage references = append(references, Reference{Type: ref.Type, Context: ctxRef}) } if ! hasParent { parent = ctxRef hasParent = ref.Type == opentracing.ChildOfRef } } if ! hasParent && ! isEmptyReference(parent) { // If ChildOfRef wasn't found but a FollowFromRef exists, use the context from // the FollowFromRef as the parent hasParent = true } rpcServer := false if v, ok := options.Tags[ext.SpanKindRPCServer.Key]; ok { rpcServer = (v == ext.SpanKindRPCServerEnum || v == string(ext.SpanKindRPCServerEnum)) } var internalTags []Tag newTrace := false if ! isSelfRef { if ! hasParent || ! parent.IsValid() { newTrace = true ctx.traceID.Low = t.randomID() if t.options.gen128Bit { ctx.traceID.High = t.options.highTraceIDGenerator() } ctx.spanID = SpanID(ctx.traceID.Low) ctx.parentID = 0 ctx.samplingState = &samplingState{ localRootSpan: ctx.spanID, } if hasParent && parent.isDebugIDContainerOnly() && t.isDebugAllowed(operationName) { ctx.samplingState.setDebugAndSampled() internalTags = append(internalTags, Tag{key: JaegerDebugHeader, value: parent.debugID}) } } else { ctx.traceID = parent.traceID if rpcServer && t.options.zipkinSharedRPCSpan { // Support Zipkin's one-span-per-RPC model ctx.spanID = parent.spanID ctx.parentID = parent.parentID } else { ctx.spanID = SpanID(t.randomID()) ctx.parentID = parent.spanID } ctx.samplingState = parent.samplingState if parent.remote { ctx.samplingState.setFinal() ctx.samplingState.localRootSpan = ctx.spanID } } if hasParent { // copy baggage items if l := len(parent.baggage); l > 0 { ctx.baggage = make(map[string]string, len(parent.baggage)) for k, v := range parent.baggage { ctx.baggage[k] = v } } } } sp := t.newSpan() sp.context = ctx sp.tracer = t sp.operationName  = operationName sp.startTime = options.StartTime sp.duration = 0 sp.references = references sp.firstInProcess = rpcServer || sp.context.parentID == 0 if ! sp.isSamplingFinalized() { decision := t.sampler.OnCreateSpan(sp) sp.applySamplingDecision(decision, false) } sp.observer = t.observer.OnStartSpan(sp, operationName, options) if tagsTotalLength := len(options.Tags) + len(internalTags); tagsTotalLength > 0 { if sp.tags == nil || cap(sp.tags) < tagsTotalLength { sp.tags = make([]Tag, 0, tagsTotalLength) } sp.tags = append(sp.tags, internalTags...) for k, v := range options.Tags { sp.setTagInternal(k, v, false) } } t.emitNewSpanMetrics(sp, newTrace) return sp }Copy the code

Now let’s look at the implementation of this method. Except for the sp. Observer section at the end, I don’t believe there is anything you don’t understand by now.

4. Carrier carrier format in propagation

As we mentioned in the previous article, all platforms require OpenTracing implementations to support two carrier formats: a Text Map style and a binary style, which is the default format in OpenTracing

// BuiltinFormat is used in the OpenTracing package to delimit values intended to be used with 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 a 'IO.Reader'. Binary BuiltinFormat = iota // TextMap will represent SpanContexts as key: value string pairs. // // unlike HTTPHeaders, the TextMap format does not restrict the key or value character set 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 an HTTP header string pair. // // Unlike TextMap, the HTTPHeaders format requires that the key and value be valid as HTTP headers as-is // (that is, character case may be unstable, special characters are not allowed in keys, values should escape URLS, etc.) // // for tracer.inject (): The carrier must be a 'TextMapWriter'. // // for tracer.extract (): The Carrier must be a 'TextMapReader'. HTTPHeaders) // TextMapWriter is the Inject() carrier of the built-in TextMap format. With // It allows the caller to encode the SpanContext to be propagated as an entry in a Unicode string map. Type TextMapWriter Interface {// Sets a key-value pair to the carrier. Calling the Set() method more than once on the same key results in undefined behavior. // Note: The back store of TextMapWriter may contain data unrelated to SpanContext. Thus, the Inject () and Extract () implementations that call TextMapWriter and TextMapReader interfaces must agree on prefixes or other conventions to distinguish their own key: value pairs. Set(key, val string)} // TextMapReader is an Extract () vector for TextMap's built-in format. // Using it, the caller can decode the propagated SpanContext as an entry in a Unicode string map. Type TextMapReader interface {// ForeachKey returns TextMap contents by repeatedly calling 'handler' functions. // If any calls to 'handler' return a non-nil error, ForeachKey terminates and returns the error. // Note: The back store of TextMapReader may contain data unrelated to SpanContext. Therefore, the TextMapWriter and TextMapReader interface Inject () and Extract () implementations // must agree on prefixes or other conventions to distinguish their own key: value pairs. The // // "foreach" callback pattern reduces unnecessary replication in some cases, and also allows the implementer to hold the lock while reading the map. ForeachKey(handler func(key, val string) error) error }Copy the code

For example, HTTPHeadersCarrier is of type http.header. TextMapWriter and TextMapReader can be used as the carrier of Inject() and Extract() methods.

type HTTPHeadersCarrier http.Header // Set conforms to the TextMapWriter interface. func (c HTTPHeadersCarrier) Set(key,  val string) { h := http.Header(c) h.Set(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 }Copy the code

Example client:

carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
err := tracer.Inject(
	span.Context(),
	opentracing.HTTPHeaders,
	carrier)
Copy the code

Server example:

carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) clientContext, err := tracer.Extract(opentracing.HTTPHeaders, Var serverSpan opentracing.Span if err == nil {Span = tracer.startSpan ( rpcMethodName, ext.RPCServerOption(clientContext)) } else { span = tracer.StartSpan(rpcMethodName) }Copy the code

5.NoopTracer

So here’s what NoopTracer is all about. It implements the Tracer interface, noopSpan implements the Span interface, and noopSpanContext implements the SpanContext interface, but it doesn’t do anything in those implementations.

type NoopTracer struct{} type noopSpan struct{} type noopSpanContext struct{} var ( defaultNoopSpanContext SpanContext =  noopSpanContext{} defaultNoopSpan Span = noopSpan{} defaultNoopTracer Tracer = 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 n } 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 }Copy the code

6.globalTracer

The global tracker defaults to a NoopTracer.

type registeredTracer struct { tracer Tracer isRegistered bool } var ( globalTracer = registeredTracer{NoopTracer{}, false} ) func SetGlobalTracer(tracer Tracer) { globalTracer = registeredTracer{tracer, True}} // GlobalTracer returns the global singleton 'Tracer' implementation // Before 'SetGlobalTracer()' is called, 'GlobalTracer()' is a NOOP implementation that will discard all data passed to it. func GlobalTracer() Tracer { return globalTracer.tracer }Copy the code

Of course, you can set this up by calling SetGlobalTracer instead of using the default NoopTracer, paying special attention to SetGlobalTracer setting the singleton OpenTracing.tracer returned by GlobalTracer (). Those using GlobalTracer (rather than managing openTracer.tracer instances directly) should call SetGlobalTracer as early as possible before main () and then call the StartSpan global function below. Prior to the call to SetGlobalTracer, all spans started with global variables such as StartSpan are NOops.

Therefore, in practical applications, after Tracer is instantiated, we need to SetGlobalTracer first, then GlobalTracer.

func main() { t, io, err := tracer.NewTracer(name, "") if err ! = nil { log.Fatal(err) } defer io.Close() opentracing.SetGlobalTracer(t) .... }Copy the code

7. tag

In the previous article, when talking about spans, we mentioned common Span tags, such as db.instance to indicate the database host, http.status_code to indicate the HTTP return status code, or error, which can be set to true if the operation represented by Span fails. Both are defined in the Ext directory of OpenTracing -go

var ( ////////////////////////////////////////////////////////////////////// // SpanKind (client/server or producer/consumer) ////////////////////////////////////////////////////////////////////// // 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} // SpanKindProducer marks a span representing the producer-side of a // message bus SpanKindProducerEnum = SpanKindEnum("producer") SpanKindProducer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindProducerEnum} // SpanKindConsumer marks a span representing the consumer-side of a // message bus SpanKindConsumerEnum = SpanKindEnum("consumer") SpanKindConsumer = opentracing.Tag{Key: string(SpanKind), Value: SpanKindConsumerEnum} ////////////////////////////////////////////////////////////////////// // 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 or // 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") // PeerAddress records the address name of the peer. This may be a "ip:port", // a bare "hostname", A FQDN or even a database DSN substring // like "mysql://[email protected]:3306/dbname" PeerAddress = StringTagName("peer.address") // PeerHostname records the host name of the peer PeerHostname = StringTagName("peer.hostname") // PeerHostIPv4 records IP v4 host address of the peer PeerHostIPv4 = IPv4TagName("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") ////////////////////////////////////////////////////////////////////// // DB Tags ////////////////////////////////////////////////////////////////////// // DBInstance is database instance name. DBInstance = StringTagName("db.instance") // DBStatement is a database statement for the given database type. // It can be a query or a prepared statement (i.e., before substitution). DBStatement = StringTagName("db.statement") // DBType is a database type. For any SQL database, "sql". // For others, the lower-case database category, e.g. "redis" DBType = StringTagName("db.type") // DBUser is a username for accessing database. DBUser = StringTagName("db.user") ////////////////////////////////////////////////////////////////////// // Message Bus Tag ////////////////////////////////////////////////////////////////////// // MessageBusDestination is an address at which messages can be exchanged MessageBusDestination = StringTagName("message_bus.destination") ////////////////////////////////////////////////////////////////////// // Error Tag ////////////////////////////////////////////////////////////////////// // Error indicates that operation represented by the span resulted in an error. Error = BoolTagName("error") )Copy the code

These constants define recommended generic tag names for better portability between tracking systems and languages/platforms. The different application scenarios are explained in the comments.

For different data types of tag values, there is a specific wrapper type, which is Set to receive the Span and the value to be Set, and in the Set method, to call sp.settag ().


// StringTagName is a common tag name to be set to a string value
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)
}
Copy the code