From f831dc0b55d01c05f443785fca95f33397458dce Mon Sep 17 00:00:00 2001 From: Tony Date: Wed, 2 Dec 2020 00:41:47 +0800 Subject: [PATCH] fix import pkg (#651) --- go.mod | 4 + go.sum | 7 + pkg/net/trace/jaeger/config.go | 11 + pkg/net/trace/jaeger/http_transport.go | 314 +++++++++++++++++++++ pkg/net/trace/jaeger/jaeger.go | 49 ++++ pkg/net/trace/jaeger/jaeger_test.go | 53 ++++ pkg/net/trace/jaeger/reference.go | 9 + pkg/net/trace/jaeger/span.go | 345 +++++++++++++++++++++++ pkg/net/trace/jaeger/span_context.go | 369 +++++++++++++++++++++++++ 9 files changed, 1161 insertions(+) create mode 100644 pkg/net/trace/jaeger/config.go create mode 100644 pkg/net/trace/jaeger/http_transport.go create mode 100644 pkg/net/trace/jaeger/jaeger.go create mode 100644 pkg/net/trace/jaeger/jaeger_test.go create mode 100644 pkg/net/trace/jaeger/reference.go create mode 100644 pkg/net/trace/jaeger/span.go create mode 100644 pkg/net/trace/jaeger/span_context.go diff --git a/go.mod b/go.mod index 8e2f518a7..ae6439202 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/mattn/go-isatty v0.0.10 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/montanaflynn/stats v0.5.0 + github.com/opentracing/opentracing-go v1.1.0 github.com/openzipkin/zipkin-go v0.2.1 github.com/otokaze/mock v0.0.0-20190125081256-8282b7a7c7c3 github.com/philchia/agollo v0.0.0-20190728085453-a95533fccea3 @@ -50,8 +51,11 @@ require ( github.com/stretchr/testify v1.4.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc // indirect github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa + github.com/uber/jaeger-client-go v2.25.0+incompatible + github.com/uber/jaeger-lib v2.4.0+incompatible // indirect github.com/urfave/cli/v2 v2.1.1 go.etcd.io/etcd v0.0.0-20200402134248-51bdeb39e698 + go.uber.org/atomic v1.6.0 golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect diff --git a/go.sum b/go.sum index 9de294c66..9ab90b93b 100644 --- a/go.sum +++ b/go.sum @@ -220,6 +220,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx8PXcA= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= @@ -322,6 +323,12 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc h1:yUaosF github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa h1:V/ABqiqsgqmpoIcLDSpJ1KqPfbxRmkcfET5+BRy9ctM= github.com/tsuna/gohbase v0.0.0-20190502052937-24ffed0537aa/go.mod h1:3HfLQly3YNLGxNv/2YOfmz30vcjG9hbuME1GpxoLlGs= +github.com/uber/jaeger-client-go v1.6.0 h1:3+zLlq+4npI5fg8IsgAje3YsP7TcEdNzJScyqFIzxEQ= +github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo= +github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ= +github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= diff --git a/pkg/net/trace/jaeger/config.go b/pkg/net/trace/jaeger/config.go new file mode 100644 index 000000000..bd1e49323 --- /dev/null +++ b/pkg/net/trace/jaeger/config.go @@ -0,0 +1,11 @@ +package jaeger + +import ( + "github.com/go-kratos/kratos/pkg/conf/env" + "github.com/go-kratos/kratos/pkg/net/trace" +) + +func Init() { + c := &Config{Endpoint: "http://127.0.0.1:9191", BatchSize: 120} + trace.SetGlobalTracer(trace.NewTracer(env.AppID, newReport(c), true)) +} diff --git a/pkg/net/trace/jaeger/http_transport.go b/pkg/net/trace/jaeger/http_transport.go new file mode 100644 index 000000000..7a4466da3 --- /dev/null +++ b/pkg/net/trace/jaeger/http_transport.go @@ -0,0 +1,314 @@ +package jaeger + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "time" + + "github.com/opentracing/opentracing-go" + ja "github.com/uber/jaeger-client-go" + "github.com/uber/jaeger-client-go/thrift" + j "github.com/uber/jaeger-client-go/thrift-gen/jaeger" +) + +// Default timeout for http request in seconds +const defaultHTTPTimeout = time.Second * 5 + +// HTTPTransport implements Transport by forwarding spans to a http server. +type HTTPTransport struct { + url string + client *http.Client + batchSize int + spans []*j.Span + process *j.Process + httpCredentials *HTTPBasicAuthCredentials + headers map[string]string +} + +// HTTPBasicAuthCredentials stores credentials for HTTP basic auth. +type HTTPBasicAuthCredentials struct { + username string + password string +} + +// HTTPOption sets a parameter for the HttpCollector +type HTTPOption func(c *HTTPTransport) + +// HTTPTimeout sets maximum timeout for http request. +func HTTPTimeout(duration time.Duration) HTTPOption { + return func(c *HTTPTransport) { c.client.Timeout = duration } +} + +// HTTPBatchSize sets the maximum batch size, after which a collect will be +// triggered. The default batch size is 100 spans. +func HTTPBatchSize(n int) HTTPOption { + return func(c *HTTPTransport) { c.batchSize = n } +} + +// HTTPBasicAuth sets the credentials required to perform HTTP basic auth +func HTTPBasicAuth(username string, password string) HTTPOption { + return func(c *HTTPTransport) { + c.httpCredentials = &HTTPBasicAuthCredentials{username: username, password: password} + } +} + +// HTTPRoundTripper configures the underlying Transport on the *http.Client +// that is used +func HTTPRoundTripper(transport http.RoundTripper) HTTPOption { + return func(c *HTTPTransport) { + c.client.Transport = transport + } +} + +// HTTPHeaders defines the HTTP headers that will be attached to the jaeger client's HTTP request +func HTTPHeaders(headers map[string]string) HTTPOption { + return func(c *HTTPTransport) { + c.headers = headers + } +} + +// NewHTTPTransport returns a new HTTP-backend transport. url should be an http +// url of the collector to handle POST request, typically something like: +// http://hostname:14268/api/traces?format=jaeger.thrift +func NewHTTPTransport(url string, options ...HTTPOption) *HTTPTransport { + c := &HTTPTransport{ + url: url, + client: &http.Client{Timeout: defaultHTTPTimeout}, + batchSize: 100, + spans: []*j.Span{}, + } + + for _, option := range options { + option(c) + } + return c +} + +// Append implements Transport. +func (c *HTTPTransport) Append(span *Span) (int, error) { + if c.process == nil { + process := j.NewProcess() + process.ServiceName = span.ServiceName() + c.process = process + } + jSpan := BuildJaegerThrift(span) + c.spans = append(c.spans, jSpan) + if len(c.spans) >= c.batchSize { + return c.Flush() + } + return 0, nil +} + +// Flush implements Transport. +func (c *HTTPTransport) Flush() (int, error) { + count := len(c.spans) + if count == 0 { + return 0, nil + } + err := c.send(c.spans) + c.spans = c.spans[:0] + return count, err +} + +// Close implements Transport. +func (c *HTTPTransport) Close() error { + return nil +} + +func (c *HTTPTransport) send(spans []*j.Span) error { + batch := &j.Batch{ + Spans: spans, + Process: c.process, + } + body, err := serializeThrift(batch) + if err != nil { + return err + } + req, err := http.NewRequest("POST", c.url, body) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/x-thrift") + for k, v := range c.headers { + req.Header.Set(k, v) + } + + if c.httpCredentials != nil { + req.SetBasicAuth(c.httpCredentials.username, c.httpCredentials.password) + } + + resp, err := c.client.Do(req) + if err != nil { + return err + } + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + if resp.StatusCode >= http.StatusBadRequest { + return fmt.Errorf("error from collector: %d", resp.StatusCode) + } + return nil +} + +func serializeThrift(obj thrift.TStruct) (*bytes.Buffer, error) { + t := thrift.NewTMemoryBuffer() + p := thrift.NewTBinaryProtocolTransport(t) + if err := obj.Write(p); err != nil { + return nil, err + } + return t.Buffer, nil +} + +func BuildJaegerThrift(span *Span) *j.Span { + span.Lock() + defer span.Unlock() + startTime := span.startTime.UnixNano() / 1000 + duration := span.duration.Nanoseconds() / int64(time.Microsecond) + jaegerSpan := &j.Span{ + TraceIdLow: int64(span.context.traceID.Low), + TraceIdHigh: int64(span.context.traceID.High), + SpanId: int64(span.context.spanID), + ParentSpanId: int64(span.context.parentID), + OperationName: span.operationName, + Flags: int32(span.context.samplingState.flags()), + StartTime: startTime, + Duration: duration, + Tags: buildTags(span.tags, 100), + Logs: buildLogs(span.logs), + References: buildReferences(span.references), + } + return jaegerSpan +} + +func stringify(value interface{}) string { + if s, ok := value.(string); ok { + return s + } + return fmt.Sprintf("%+v", value) +} + +func truncateString(value string, maxLength int) string { + // we ignore the problem of utf8 runes possibly being sliced in the middle, + // as it is rather expensive to iterate through each tag just to find rune + // boundaries. + if len(value) > maxLength { + return value[:maxLength] + } + return value +} + +func buildTags(tags []Tag, maxTagValueLength int) []*j.Tag { + jTags := make([]*j.Tag, 0, len(tags)) + for _, tag := range tags { + jTag := buildTag(&tag, maxTagValueLength) + jTags = append(jTags, jTag) + } + return jTags +} +func buildTag(tag *Tag, maxTagValueLength int) *j.Tag { + jTag := &j.Tag{Key: tag.key} + switch value := tag.value.(type) { + case string: + vStr := truncateString(value, maxTagValueLength) + jTag.VStr = &vStr + jTag.VType = j.TagType_STRING + case []byte: + if len(value) > maxTagValueLength { + value = value[:maxTagValueLength] + } + jTag.VBinary = value + jTag.VType = j.TagType_BINARY + case int: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case uint: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case int8: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case uint8: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case int16: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case uint16: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case int32: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case uint32: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case int64: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case uint64: + vLong := int64(value) + jTag.VLong = &vLong + jTag.VType = j.TagType_LONG + case float32: + vDouble := float64(value) + jTag.VDouble = &vDouble + jTag.VType = j.TagType_DOUBLE + case float64: + vDouble := float64(value) + jTag.VDouble = &vDouble + jTag.VType = j.TagType_DOUBLE + case bool: + vBool := value + jTag.VBool = &vBool + jTag.VType = j.TagType_BOOL + default: + vStr := truncateString(stringify(value), maxTagValueLength) + jTag.VStr = &vStr + jTag.VType = j.TagType_STRING + } + return jTag +} + +func buildLogs(logs []opentracing.LogRecord) []*j.Log { + jLogs := make([]*j.Log, 0, len(logs)) + for _, log := range logs { + jLog := &j.Log{ + Timestamp: log.Timestamp.UnixNano() / 1000, + Fields: ja.ConvertLogsToJaegerTags(log.Fields), + } + jLogs = append(jLogs, jLog) + } + return jLogs +} + +func buildReferences(references []Reference) []*j.SpanRef { + retMe := make([]*j.SpanRef, 0, len(references)) + for _, ref := range references { + if ref.Type == opentracing.ChildOfRef { + retMe = append(retMe, spanRef(ref.Context, j.SpanRefType_CHILD_OF)) + } else if ref.Type == opentracing.FollowsFromRef { + retMe = append(retMe, spanRef(ref.Context, j.SpanRefType_FOLLOWS_FROM)) + } + } + return retMe +} + +func spanRef(ctx SpanContext, refType j.SpanRefType) *j.SpanRef { + return &j.SpanRef{ + RefType: refType, + TraceIdLow: int64(ctx.traceID.Low), + TraceIdHigh: int64(ctx.traceID.High), + SpanId: int64(ctx.spanID), + } +} diff --git a/pkg/net/trace/jaeger/jaeger.go b/pkg/net/trace/jaeger/jaeger.go new file mode 100644 index 000000000..5feda289e --- /dev/null +++ b/pkg/net/trace/jaeger/jaeger.go @@ -0,0 +1,49 @@ +package jaeger + +import ( + "github.com/go-kratos/kratos/pkg/log" + "github.com/go-kratos/kratos/pkg/net/trace" +) + +type Config struct { + Endpoint string + BatchSize int +} + +type JaegerReporter struct { + transport *HTTPTransport +} + +func newReport(c *Config) *JaegerReporter { + transport := NewHTTPTransport(c.Endpoint) + transport.batchSize = c.BatchSize + return &JaegerReporter{transport: transport} +} + +func (r *JaegerReporter) WriteSpan(raw *trace.Span) (err error) { + ctx := raw.Context() + traceID := TraceID{Low: ctx.TraceID} + spanID := SpanID(ctx.SpanID) + parentID := SpanID(ctx.ParentID) + tags := raw.Tags() + log.Info("[info] write span") + span := &Span{ + context: NewSpanContext(traceID, spanID, parentID, true, nil), + operationName: raw.OperationName(), + startTime: raw.StartTime(), + duration: raw.Duration(), + } + + span.serviceName = raw.ServiceName() + + for _, t := range tags { + span.SetTag(t.Key, t.Value) + } + + r.transport.Append(span) + return nil +} + +func (rpt *JaegerReporter) Close() error { + return rpt.transport.Close() +} diff --git a/pkg/net/trace/jaeger/jaeger_test.go b/pkg/net/trace/jaeger/jaeger_test.go new file mode 100644 index 000000000..d082eb8ea --- /dev/null +++ b/pkg/net/trace/jaeger/jaeger_test.go @@ -0,0 +1,53 @@ +package jaeger + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-kratos/kratos/pkg/net/trace" +) + +func TestJaegerReporter(t *testing.T) { + var handler = func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Errorf("expected 'POST' request, got '%s'", r.Method) + } + + aSpanPayload, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + + t.Logf("%s\n", aSpanPayload) + } + ht := httptest.NewServer(http.HandlerFunc(handler)) + defer ht.Close() + + c := &Config{ + Endpoint: ht.URL, + BatchSize: 1, + } + + //c.Endpoint = "http://127.0.0.1:14268/api/traces" + + report := newReport(c) + t1 := trace.NewTracer("jaeger_test_1", report, true) + t2 := trace.NewTracer("jaeger_test_2", report, true) + sp1 := t1.New("option_1") + sp2 := sp1.Fork("service3", "opt_client") + sp2.SetLog(trace.Log("log_k", "log_v")) + // inject + header := make(http.Header) + t1.Inject(sp2, trace.HTTPFormat, header) + t.Log(header) + sp3, err := t2.Extract(trace.HTTPFormat, header) + if err != nil { + t.Fatal(err) + } + sp3.Finish(nil) + sp2.Finish(nil) + sp1.Finish(nil) + report.Close() +} diff --git a/pkg/net/trace/jaeger/reference.go b/pkg/net/trace/jaeger/reference.go new file mode 100644 index 000000000..becc01685 --- /dev/null +++ b/pkg/net/trace/jaeger/reference.go @@ -0,0 +1,9 @@ +package jaeger + +import "github.com/opentracing/opentracing-go" + +// Reference represents a causal reference to other Spans (via their SpanContext). +type Reference struct { + Type opentracing.SpanReferenceType + Context SpanContext +} diff --git a/pkg/net/trace/jaeger/span.go b/pkg/net/trace/jaeger/span.go new file mode 100644 index 000000000..33dab4ebe --- /dev/null +++ b/pkg/net/trace/jaeger/span.go @@ -0,0 +1,345 @@ +package jaeger + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/log" +) + +// Span implements opentracing.Span +type Span struct { + // referenceCounter used to increase the lifetime of + // the object before return it into the pool. + referenceCounter int32 + + serviceName string + + sync.RWMutex + + // TODO: (breaking change) change to use a pointer + context SpanContext + + // The name of the "operation" this span is an instance of. + // Known as a "span name" in some implementations. + operationName string + + // firstInProcess, if true, indicates that this span is the root of the (sub)tree + // of spans in the current process. In other words it's true for the root spans, + // and the ingress spans when the process joins another trace. + firstInProcess bool + + // startTime is the timestamp indicating when the span began, with microseconds precision. + startTime time.Time + + // duration returns duration of the span with microseconds precision. + // Zero value means duration is unknown. + duration time.Duration + + // tags attached to this span + tags []Tag + + // The span's "micro-log" + logs []opentracing.LogRecord + + // The number of logs dropped because of MaxLogsPerSpan. + numDroppedLogs int + + // references for this span + references []Reference +} + +// Tag is a simple key value wrapper. +// TODO (breaking change) deprecate in the next major release, use opentracing.Tag instead. +type Tag struct { + key string + value interface{} +} + +// NewTag creates a new Tag. +// TODO (breaking change) deprecate in the next major release, use opentracing.Tag instead. +func NewTag(key string, value interface{}) Tag { + return Tag{key: key, value: value} +} + +// SetOperationName sets or changes the operation name. +func (s *Span) SetOperationName(operationName string) opentracing.Span { + s.Lock() + s.operationName = operationName + s.Unlock() + return s +} + +// SetTag implements SetTag() of opentracing.Span +func (s *Span) SetTag(key string, value interface{}) opentracing.Span { + return s.setTagInternal(key, value, true) +} + +func (s *Span) setTagInternal(key string, value interface{}, lock bool) opentracing.Span { + if lock { + s.Lock() + defer s.Unlock() + } + s.appendTagNoLocking(key, value) + return s +} + +// SpanContext returns span context +func (s *Span) SpanContext() SpanContext { + s.Lock() + defer s.Unlock() + return s.context +} + +// StartTime returns span start time +func (s *Span) StartTime() time.Time { + s.Lock() + defer s.Unlock() + return s.startTime +} + +// Duration returns span duration +func (s *Span) Duration() time.Duration { + s.Lock() + defer s.Unlock() + return s.duration +} + +// Tags returns tags for span +func (s *Span) Tags() opentracing.Tags { + s.Lock() + defer s.Unlock() + var result = make(opentracing.Tags, len(s.tags)) + for _, tag := range s.tags { + result[tag.key] = tag.value + } + return result +} + +// Logs returns micro logs for span +func (s *Span) Logs() []opentracing.LogRecord { + s.Lock() + defer s.Unlock() + + logs := append([]opentracing.LogRecord(nil), s.logs...) + if s.numDroppedLogs != 0 { + fixLogs(logs, s.numDroppedLogs) + } + + return logs +} + +// References returns references for this span +func (s *Span) References() []opentracing.SpanReference { + s.Lock() + defer s.Unlock() + + if s.references == nil || len(s.references) == 0 { + return nil + } + + result := make([]opentracing.SpanReference, len(s.references)) + for i, r := range s.references { + result[i] = opentracing.SpanReference{Type: r.Type, ReferencedContext: r.Context} + } + return result +} + +func (s *Span) appendTagNoLocking(key string, value interface{}) { + s.tags = append(s.tags, Tag{key: key, value: value}) +} + +// LogFields implements opentracing.Span API +func (s *Span) LogFields(fields ...log.Field) { + s.Lock() + defer s.Unlock() + if !s.context.IsSampled() { + return + } + s.logFieldsNoLocking(fields...) +} + +// this function should only be called while holding a Write lock +func (s *Span) logFieldsNoLocking(fields ...log.Field) { + lr := opentracing.LogRecord{ + Fields: fields, + Timestamp: time.Now(), + } + s.appendLogNoLocking(lr) +} + +// LogKV implements opentracing.Span API +func (s *Span) LogKV(alternatingKeyValues ...interface{}) { + s.RLock() + sampled := s.context.IsSampled() + s.RUnlock() + if !sampled { + return + } + fields, err := log.InterleavedKVToFields(alternatingKeyValues...) + if err != nil { + s.LogFields(log.Error(err), log.String("function", "LogKV")) + return + } + s.LogFields(fields...) +} + +// LogEvent implements opentracing.Span API +func (s *Span) LogEvent(event string) { + s.Log(opentracing.LogData{Event: event}) +} + +// LogEventWithPayload implements opentracing.Span API +func (s *Span) LogEventWithPayload(event string, payload interface{}) { + s.Log(opentracing.LogData{Event: event, Payload: payload}) +} + +// Log implements opentracing.Span API +func (s *Span) Log(ld opentracing.LogData) { + s.Lock() + defer s.Unlock() + if s.context.IsSampled() { + s.appendLogNoLocking(ld.ToLogRecord()) + } +} + +// this function should only be called while holding a Write lock +func (s *Span) appendLogNoLocking(lr opentracing.LogRecord) { + maxLogs := 100 + // We have too many logs. We don't touch the first numOld logs; we treat the + // rest as a circular buffer and overwrite the oldest log among those. + numOld := (maxLogs - 1) / 2 + numNew := maxLogs - numOld + s.logs[numOld+s.numDroppedLogs%numNew] = lr + s.numDroppedLogs++ +} + +// rotateLogBuffer rotates the records in the buffer: records 0 to pos-1 move at +// the end (i.e. pos circular left shifts). +func rotateLogBuffer(buf []opentracing.LogRecord, pos int) { + // This algorithm is described in: + // http://www.cplusplus.com/reference/algorithm/rotate + for first, middle, next := 0, pos, pos; first != middle; { + buf[first], buf[next] = buf[next], buf[first] + first++ + next++ + if next == len(buf) { + next = middle + } else if first == middle { + middle = next + } + } +} + +func fixLogs(logs []opentracing.LogRecord, numDroppedLogs int) { + // We dropped some log events, which means that we used part of Logs as a + // circular buffer (see appendLog). De-circularize it. + numOld := (len(logs) - 1) / 2 + numNew := len(logs) - numOld + rotateLogBuffer(logs[numOld:], numDroppedLogs%numNew) + + // Replace the log in the middle (the oldest "new" log) with information + // about the dropped logs. This means that we are effectively dropping one + // more "new" log. + numDropped := numDroppedLogs + 1 + logs[numOld] = opentracing.LogRecord{ + // Keep the timestamp of the last dropped event. + Timestamp: logs[numOld].Timestamp, + Fields: []log.Field{ + log.String("event", "dropped Span logs"), + log.Int("dropped_log_count", numDropped), + log.String("component", "jaeger-client"), + }, + } +} + +func (s *Span) fixLogsIfDropped() { + if s.numDroppedLogs == 0 { + return + } + fixLogs(s.logs, s.numDroppedLogs) + s.numDroppedLogs = 0 +} + +// SetBaggageItem implements SetBaggageItem() of opentracing.SpanContext +func (s *Span) SetBaggageItem(key, value string) opentracing.Span { + s.context.baggage[key] = value + return s +} + +// BaggageItem implements BaggageItem() of opentracing.SpanContext +func (s *Span) BaggageItem(key string) string { + s.RLock() + defer s.RUnlock() + return s.context.baggage[key] +} + +// Finish implements opentracing.Span API +// After finishing the Span object it returns back to the allocator unless the reporter retains it again, +// so after that, the Span object should no longer be used because it won't be valid anymore. +func (s *Span) Finish() { + s.FinishWithOptions(opentracing.FinishOptions{}) +} + +// FinishWithOptions implements opentracing.Span API +func (s *Span) FinishWithOptions(options opentracing.FinishOptions) { +} + +// Context implements opentracing.Span API +func (s *Span) Context() opentracing.SpanContext { + s.Lock() + defer s.Unlock() + return s.context +} + +// Tracer implements opentracing.Span API +func (s *Span) Tracer() opentracing.Tracer { + return nil +} + +func (s *Span) String() string { + s.RLock() + defer s.RUnlock() + return s.context.String() +} + +// OperationName allows retrieving current operation name. +func (s *Span) OperationName() string { + s.RLock() + defer s.RUnlock() + return s.operationName +} + +// Retain increases object counter to increase the lifetime of the object +func (s *Span) Retain() *Span { + atomic.AddInt32(&s.referenceCounter, 1) + return s +} + +// Release decrements object counter and return to the +// allocator manager when counter will below zero +func (s *Span) Release() { + +} + +// reset span state and release unused data +func (s *Span) reset() { + s.firstInProcess = false + s.context = emptyContext + s.operationName = "" + s.startTime = time.Time{} + s.duration = 0 + atomic.StoreInt32(&s.referenceCounter, 0) + + // Note: To reuse memory we can save the pointers on the heap + s.tags = s.tags[:0] + s.logs = s.logs[:0] + s.numDroppedLogs = 0 + s.references = s.references[:0] +} + +func (s *Span) ServiceName() string { + return s.serviceName +} diff --git a/pkg/net/trace/jaeger/span_context.go b/pkg/net/trace/jaeger/span_context.go new file mode 100644 index 000000000..d580c2d7f --- /dev/null +++ b/pkg/net/trace/jaeger/span_context.go @@ -0,0 +1,369 @@ +package jaeger + +import ( + "errors" + "fmt" + "strconv" + "strings" + "sync" + + "go.uber.org/atomic" +) + +const ( + flagSampled = 1 + flagDebug = 2 + flagFirehose = 8 +) + +var ( + errEmptyTracerStateString = errors.New("Cannot convert empty string to tracer state") + errMalformedTracerStateString = errors.New("String does not match tracer state format") + + emptyContext = SpanContext{} +) + +// TraceID represents unique 128bit identifier of a trace +type TraceID struct { + High, Low uint64 +} + +// SpanID represents unique 64bit identifier of a span +type SpanID uint64 + +// SpanContext represents propagated span identity and state +type SpanContext struct { + // traceID represents globally unique ID of the trace. + // Usually generated as a random number. + traceID TraceID + + // spanID represents span ID that must be unique within its trace, + // but does not have to be globally unique. + spanID SpanID + + // parentID refers to the ID of the parent span. + // Should be 0 if the current span is a root span. + parentID SpanID + + // Distributed Context baggage. The is a snapshot in time. + baggage map[string]string + + // debugID can be set to some correlation ID when the context is being + // extracted from a TextMap carrier. + // + // See JaegerDebugHeader in constants.go + debugID string + + // samplingState is shared across all spans + samplingState *samplingState + + // remote indicates that span context represents a remote parent + remote bool +} + +type samplingState struct { + // Span context's state flags that are propagated across processes. Only lower 8 bits are used. + // We use an int32 instead of byte to be able to use CAS operations. + stateFlags atomic.Int32 + + // When state is not final, sampling will be retried on other span write operations, + // like SetOperationName / SetTag, and the spans will remain writable. + final atomic.Bool + + // localRootSpan stores the SpanID of the first span created in this process for a given trace. + localRootSpan SpanID + + // extendedState allows samplers to keep intermediate state. + // The keys and values in this map are completely opaque: interface{} -> interface{}. + extendedState sync.Map +} + +func (s *samplingState) isLocalRootSpan(id SpanID) bool { + return id == s.localRootSpan +} + +func (s *samplingState) setFlag(newFlag int32) { + swapped := false + for !swapped { + old := s.stateFlags.Load() + swapped = s.stateFlags.CAS(old, old|newFlag) + } +} + +func (s *samplingState) unsetFlag(newFlag int32) { + swapped := false + for !swapped { + old := s.stateFlags.Load() + swapped = s.stateFlags.CAS(old, old&^newFlag) + } +} + +func (s *samplingState) setSampled() { + s.setFlag(flagSampled) +} + +func (s *samplingState) unsetSampled() { + s.unsetFlag(flagSampled) +} + +func (s *samplingState) setDebugAndSampled() { + s.setFlag(flagDebug | flagSampled) +} + +func (s *samplingState) setFirehose() { + s.setFlag(flagFirehose) +} + +func (s *samplingState) setFlags(flags byte) { + s.stateFlags.Store(int32(flags)) +} + +func (s *samplingState) setFinal() { + s.final.Store(true) +} + +func (s *samplingState) flags() byte { + return byte(s.stateFlags.Load()) +} + +func (s *samplingState) isSampled() bool { + return s.stateFlags.Load()&flagSampled == flagSampled +} + +func (s *samplingState) isDebug() bool { + return s.stateFlags.Load()&flagDebug == flagDebug +} + +func (s *samplingState) isFirehose() bool { + return s.stateFlags.Load()&flagFirehose == flagFirehose +} + +func (s *samplingState) isFinal() bool { + return s.final.Load() +} + +func (s *samplingState) extendedStateForKey(key interface{}, initValue func() interface{}) interface{} { + if value, ok := s.extendedState.Load(key); ok { + return value + } + value := initValue() + value, _ = s.extendedState.LoadOrStore(key, value) + return value +} + +// ForeachBaggageItem implements ForeachBaggageItem() of opentracing.SpanContext +func (c SpanContext) ForeachBaggageItem(handler func(k, v string) bool) { + for k, v := range c.baggage { + if !handler(k, v) { + break + } + } +} + +// IsSampled returns whether this trace was chosen for permanent storage +// by the sampling mechanism of the tracer. +func (c SpanContext) IsSampled() bool { + return c.samplingState.isSampled() +} + +// IsDebug indicates whether sampling was explicitly requested by the service. +func (c SpanContext) IsDebug() bool { + return c.samplingState.isDebug() +} + +// IsSamplingFinalized indicates whether the sampling decision has been finalized. +func (c SpanContext) IsSamplingFinalized() bool { + return c.samplingState.isFinal() +} + +// IsFirehose indicates whether the firehose flag was set +func (c SpanContext) IsFirehose() bool { + return c.samplingState.isFirehose() +} + +// ExtendedSamplingState returns the custom state object for a given key. If the value for this key does not exist, +// it is initialized via initValue function. This state can be used by samplers (e.g. x.PrioritySampler). +func (c SpanContext) ExtendedSamplingState(key interface{}, initValue func() interface{}) interface{} { + return c.samplingState.extendedStateForKey(key, initValue) +} + +// IsValid indicates whether this context actually represents a valid trace. +func (c SpanContext) IsValid() bool { + return c.traceID.IsValid() && c.spanID != 0 +} + +// SetFirehose enables firehose mode for this trace. +func (c SpanContext) SetFirehose() { + c.samplingState.setFirehose() +} + +func (c SpanContext) String() string { + if c.traceID.High == 0 { + return fmt.Sprintf("%016x:%016x:%016x:%x", c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load()) + } + return fmt.Sprintf("%016x%016x:%016x:%016x:%x", c.traceID.High, c.traceID.Low, uint64(c.spanID), uint64(c.parentID), c.samplingState.stateFlags.Load()) +} + +// ContextFromString reconstructs the Context encoded in a string +func ContextFromString(value string) (SpanContext, error) { + var context SpanContext + if value == "" { + return emptyContext, errEmptyTracerStateString + } + parts := strings.Split(value, ":") + if len(parts) != 4 { + return emptyContext, errMalformedTracerStateString + } + var err error + if context.traceID, err = TraceIDFromString(parts[0]); err != nil { + return emptyContext, err + } + if context.spanID, err = SpanIDFromString(parts[1]); err != nil { + return emptyContext, err + } + if context.parentID, err = SpanIDFromString(parts[2]); err != nil { + return emptyContext, err + } + flags, err := strconv.ParseUint(parts[3], 10, 8) + if err != nil { + return emptyContext, err + } + context.samplingState = &samplingState{} + context.samplingState.setFlags(byte(flags)) + return context, nil +} + +// TraceID returns the trace ID of this span context +func (c SpanContext) TraceID() TraceID { + return c.traceID +} + +// SpanID returns the span ID of this span context +func (c SpanContext) SpanID() SpanID { + return c.spanID +} + +// ParentID returns the parent span ID of this span context +func (c SpanContext) ParentID() SpanID { + return c.parentID +} + +// Flags returns the bitmap containing such bits as 'sampled' and 'debug'. +func (c SpanContext) Flags() byte { + return c.samplingState.flags() +} + +// NewSpanContext creates a new instance of SpanContext +func NewSpanContext(traceID TraceID, spanID, parentID SpanID, sampled bool, baggage map[string]string) SpanContext { + samplingState := &samplingState{} + if sampled { + samplingState.setSampled() + } + + return SpanContext{ + traceID: traceID, + spanID: spanID, + parentID: parentID, + samplingState: samplingState, + baggage: baggage} +} + +// CopyFrom copies data from ctx into this context, including span identity and baggage. +// TODO This is only used by interop.go. Remove once TChannel Go supports OpenTracing. +func (c *SpanContext) CopyFrom(ctx *SpanContext) { + c.traceID = ctx.traceID + c.spanID = ctx.spanID + c.parentID = ctx.parentID + c.samplingState = ctx.samplingState + if l := len(ctx.baggage); l > 0 { + c.baggage = make(map[string]string, l) + for k, v := range ctx.baggage { + c.baggage[k] = v + } + } else { + c.baggage = nil + } +} + +// WithBaggageItem creates a new context with an extra baggage item. +func (c SpanContext) WithBaggageItem(key, value string) SpanContext { + 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 SpanContext{c.traceID, c.spanID, c.parentID, newBaggage, "", c.samplingState, c.remote} +} + +// isDebugIDContainerOnly returns true when the instance of the context is only +// used to return the debug/correlation ID from extract() method. This happens +// in the situation when "jaeger-debug-id" header is passed in the carrier to +// the extract() method, but the request otherwise has no span context in it. +// Previously this would've returned opentracing.ErrSpanContextNotFound from the +// extract method, but now it returns a dummy context with only debugID filled in. +// +// See JaegerDebugHeader in constants.go +// See TextMapPropagator#Extract +func (c *SpanContext) isDebugIDContainerOnly() bool { + return !c.traceID.IsValid() && c.debugID != "" +} + +// ------- TraceID ------- + +func (t TraceID) String() string { + if t.High == 0 { + return fmt.Sprintf("%x", t.Low) + } + return fmt.Sprintf("%x%016x", t.High, t.Low) +} + +// TraceIDFromString creates a TraceID from a hexadecimal string +func TraceIDFromString(s string) (TraceID, error) { + var hi, lo uint64 + var err error + if len(s) > 32 { + return TraceID{}, fmt.Errorf("TraceID cannot be longer than 32 hex characters: %s", s) + } else if len(s) > 16 { + hiLen := len(s) - 16 + if hi, err = strconv.ParseUint(s[0:hiLen], 16, 64); err != nil { + return TraceID{}, err + } + if lo, err = strconv.ParseUint(s[hiLen:], 16, 64); err != nil { + return TraceID{}, err + } + } else { + if lo, err = strconv.ParseUint(s, 16, 64); err != nil { + return TraceID{}, err + } + } + return TraceID{High: hi, Low: lo}, nil +} + +// IsValid checks if the trace ID is valid, i.e. not zero. +func (t TraceID) IsValid() bool { + return t.High != 0 || t.Low != 0 +} + +// ------- SpanID ------- + +func (s SpanID) String() string { + return fmt.Sprintf("%x", uint64(s)) +} + +// SpanIDFromString creates a SpanID from a hexadecimal string +func SpanIDFromString(s string) (SpanID, error) { + if len(s) > 16 { + return SpanID(0), fmt.Errorf("SpanID cannot be longer than 16 hex characters: %s", s) + } + id, err := strconv.ParseUint(s, 16, 64) + if err != nil { + return SpanID(0), err + } + return SpanID(id), nil +}