 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			281 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package loggabletracer
 | |
| 
 | |
| import (
 | |
| 	"time"
 | |
| 
 | |
| 	writer "github.com/ipfs/go-log/writer"
 | |
| 	opentracing "github.com/opentracing/opentracing-go"
 | |
| )
 | |
| 
 | |
| // Tracer extends the opentracing.Tracer interface with methods to
 | |
| // probe implementation state, for use by loggabletracer consumers.
 | |
| type Tracer interface {
 | |
| 	opentracing.Tracer
 | |
| 
 | |
| 	// Options gets the Options used in New() or NewWithOptions().
 | |
| 	Options() Options
 | |
| }
 | |
| 
 | |
| // Options allows creating a customized Tracer via NewWithOptions. The object
 | |
| // must not be updated when there is an active tracer using it.
 | |
| type Options struct {
 | |
| 	// ShouldSample is a function which is called when creating a new Span and
 | |
| 	// determines whether that Span is sampled. The randomized TraceID is supplied
 | |
| 	// to allow deterministic sampling decisions to be made across different nodes.
 | |
| 	// For example,
 | |
| 	//
 | |
| 	//   func(traceID uint64) { return traceID % 64 == 0 }
 | |
| 	//
 | |
| 	// samples every 64th trace on average.
 | |
| 	ShouldSample func(traceID uint64) bool
 | |
| 	// TrimUnsampledSpans turns potentially expensive operations on unsampled
 | |
| 	// Spans into no-ops. More precisely, tags and log events are silently
 | |
| 	// discarded. If NewSpanEventListener is set, the callbacks will still fire.
 | |
| 	TrimUnsampledSpans bool
 | |
| 	// Recorder receives Spans which have been finished.
 | |
| 	Recorder SpanRecorder
 | |
| 	// NewSpanEventListener can be used to enhance the tracer by effectively
 | |
| 	// attaching external code to trace events. See NetTraceIntegrator for a
 | |
| 	// practical example, and event.go for the list of possible events.
 | |
| 	NewSpanEventListener func() func(SpanEvent)
 | |
| 	// DropAllLogs turns log events on all Spans into no-ops.
 | |
| 	// If NewSpanEventListener is set, the callbacks will still fire.
 | |
| 	DropAllLogs bool
 | |
| 	// MaxLogsPerSpan limits the number of Logs in a span (if set to a nonzero
 | |
| 	// value). If a span has more logs than this value, logs are dropped as
 | |
| 	// necessary (and replaced with a log describing how many were dropped).
 | |
| 	//
 | |
| 	// About half of the MaxLogPerSpan logs kept are the oldest logs, and about
 | |
| 	// half are the newest logs.
 | |
| 	//
 | |
| 	// If NewSpanEventListener is set, the callbacks will still fire for all log
 | |
| 	// events. This value is ignored if DropAllLogs is true.
 | |
| 	MaxLogsPerSpan int
 | |
| 	// DebugAssertSingleGoroutine internally records the ID of the goroutine
 | |
| 	// creating each Span and verifies that no operation is carried out on
 | |
| 	// it on a different goroutine.
 | |
| 	// Provided strictly for development purposes.
 | |
| 	// Passing Spans between goroutine without proper synchronization often
 | |
| 	// results in use-after-Finish() errors. For a simple example, consider the
 | |
| 	// following pseudocode:
 | |
| 	//
 | |
| 	//  func (s *Server) Handle(req http.Request) error {
 | |
| 	//    sp := s.StartSpan("server")
 | |
| 	//    defer sp.Finish()
 | |
| 	//    wait := s.queueProcessing(opentracing.ContextWithSpan(context.Background(), sp), req)
 | |
| 	//    select {
 | |
| 	//    case resp := <-wait:
 | |
| 	//      return resp.Error
 | |
| 	//    case <-time.After(10*time.Second):
 | |
| 	//      sp.LogEvent("timed out waiting for processing")
 | |
| 	//      return ErrTimedOut
 | |
| 	//    }
 | |
| 	//  }
 | |
| 	//
 | |
| 	// This looks reasonable at first, but a request which spends more than ten
 | |
| 	// seconds in the queue is abandoned by the main goroutine and its trace
 | |
| 	// finished, leading to use-after-finish when the request is finally
 | |
| 	// processed. Note also that even joining on to a finished Span via
 | |
| 	// StartSpanWithOptions constitutes an illegal operation.
 | |
| 	//
 | |
| 	// Code bases which do not require (or decide they do not want) Spans to
 | |
| 	// be passed across goroutine boundaries can run with this flag enabled in
 | |
| 	// tests to increase their chances of spotting wrong-doers.
 | |
| 	DebugAssertSingleGoroutine bool
 | |
| 	// DebugAssertUseAfterFinish is provided strictly for development purposes.
 | |
| 	// When set, it attempts to exacerbate issues emanating from use of Spans
 | |
| 	// after calling Finish by running additional assertions.
 | |
| 	DebugAssertUseAfterFinish bool
 | |
| 	// EnableSpanPool enables the use of a pool, so that the tracer reuses spans
 | |
| 	// after Finish has been called on it. Adds a slight performance gain as it
 | |
| 	// reduces allocations. However, if you have any use-after-finish race
 | |
| 	// conditions the code may panic.
 | |
| 	EnableSpanPool bool
 | |
| }
 | |
| 
 | |
| // DefaultOptions returns an Options object with a 1 in 64 sampling rate and
 | |
| // all options disabled. A Recorder needs to be set manually before using the
 | |
| // returned object with a Tracer.
 | |
| func DefaultOptions() Options {
 | |
| 	return Options{
 | |
| 		ShouldSample:   func(traceID uint64) bool { return traceID%64 == 0 },
 | |
| 		MaxLogsPerSpan: 100,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewWithOptions creates a customized Tracer.
 | |
| func NewWithOptions(opts Options) opentracing.Tracer {
 | |
| 	rval := &LoggableTracer{options: opts}
 | |
| 	rval.accessorPropagator = &accessorPropagator{rval}
 | |
| 	return rval
 | |
| }
 | |
| 
 | |
| // New creates and returns a standard Tracer which defers completed Spans to
 | |
| // `recorder`.
 | |
| // Spans created by this Tracer support the ext.SamplingPriority tag: Setting
 | |
| // ext.SamplingPriority causes the Span to be Sampled from that point on.
 | |
| func New(recorder SpanRecorder) opentracing.Tracer {
 | |
| 	opts := DefaultOptions()
 | |
| 	opts.Recorder = recorder
 | |
| 	return NewWithOptions(opts)
 | |
| }
 | |
| 
 | |
| // Implements the `Tracer` interface.
 | |
| type LoggableTracer struct {
 | |
| 	options            Options
 | |
| 	textPropagator     *textMapPropagator
 | |
| 	binaryPropagator   *binaryPropagator
 | |
| 	accessorPropagator *accessorPropagator
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) StartSpan(
 | |
| 	operationName string,
 | |
| 	opts ...opentracing.StartSpanOption,
 | |
| ) opentracing.Span {
 | |
| 
 | |
| 	if !writer.WriterGroup.Active() {
 | |
| 		return opentracing.NoopTracer.StartSpan(opentracing.NoopTracer{}, operationName)
 | |
| 	}
 | |
| 
 | |
| 	sso := opentracing.StartSpanOptions{}
 | |
| 	for _, o := range opts {
 | |
| 		o.Apply(&sso)
 | |
| 	}
 | |
| 	return t.StartSpanWithOptions(operationName, sso)
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) getSpan() *spanImpl {
 | |
| 	if t.options.EnableSpanPool {
 | |
| 		sp := spanPool.Get().(*spanImpl)
 | |
| 		sp.reset()
 | |
| 		return sp
 | |
| 	}
 | |
| 	return &spanImpl{}
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) StartSpanWithOptions(
 | |
| 	operationName string,
 | |
| 	opts opentracing.StartSpanOptions,
 | |
| ) opentracing.Span {
 | |
| 	if !writer.WriterGroup.Active() {
 | |
| 		return opentracing.NoopTracer.StartSpan(opentracing.NoopTracer{}, operationName)
 | |
| 	}
 | |
| 	// Start time.
 | |
| 	startTime := opts.StartTime
 | |
| 	if startTime.IsZero() {
 | |
| 		startTime = time.Now()
 | |
| 	}
 | |
| 
 | |
| 	// Tags.
 | |
| 	tags := opts.Tags
 | |
| 
 | |
| 	// Build the new span. This is the only allocation: We'll return this as
 | |
| 	// an opentracing.Span.
 | |
| 	sp := t.getSpan()
 | |
| 	// Look for a parent in the list of References.
 | |
| 	//
 | |
| 	// TODO: would be nice if loggabletracer did something with all
 | |
| 	// References, not just the first one.
 | |
| ReferencesLoop:
 | |
| 	for _, ref := range opts.References {
 | |
| 		switch ref.Type {
 | |
| 		case opentracing.ChildOfRef,
 | |
| 			opentracing.FollowsFromRef:
 | |
| 
 | |
| 			refCtx, ok := ref.ReferencedContext.(SpanContext)
 | |
| 			if !ok {
 | |
| 				// Could be a noopSpanContext
 | |
| 				// Ignore that parent.
 | |
| 				continue
 | |
| 			}
 | |
| 			sp.raw.Context.TraceID = refCtx.TraceID
 | |
| 			sp.raw.Context.SpanID = randomID()
 | |
| 			sp.raw.Context.Sampled = refCtx.Sampled
 | |
| 			sp.raw.ParentSpanID = refCtx.SpanID
 | |
| 
 | |
| 			if l := len(refCtx.Baggage); l > 0 {
 | |
| 				sp.raw.Context.Baggage = make(map[string]string, l)
 | |
| 				for k, v := range refCtx.Baggage {
 | |
| 					sp.raw.Context.Baggage[k] = v
 | |
| 				}
 | |
| 			}
 | |
| 			break ReferencesLoop
 | |
| 		}
 | |
| 	}
 | |
| 	if sp.raw.Context.TraceID == 0 {
 | |
| 		// No parent Span found; allocate new trace and span ids and determine
 | |
| 		// the Sampled status.
 | |
| 		sp.raw.Context.TraceID, sp.raw.Context.SpanID = randomID2()
 | |
| 		sp.raw.Context.Sampled = t.options.ShouldSample(sp.raw.Context.TraceID)
 | |
| 	}
 | |
| 
 | |
| 	return t.startSpanInternal(
 | |
| 		sp,
 | |
| 		operationName,
 | |
| 		startTime,
 | |
| 		tags,
 | |
| 	)
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) startSpanInternal(
 | |
| 	sp *spanImpl,
 | |
| 	operationName string,
 | |
| 	startTime time.Time,
 | |
| 	tags opentracing.Tags,
 | |
| ) opentracing.Span {
 | |
| 	sp.tracer = t
 | |
| 	if t.options.NewSpanEventListener != nil {
 | |
| 		sp.event = t.options.NewSpanEventListener()
 | |
| 	}
 | |
| 	sp.raw.Operation = operationName
 | |
| 	sp.raw.Start = startTime
 | |
| 	sp.raw.Duration = -1
 | |
| 	sp.raw.Tags = tags
 | |
| 	if t.options.DebugAssertSingleGoroutine {
 | |
| 		sp.SetTag(debugGoroutineIDTag, curGoroutineID())
 | |
| 	}
 | |
| 	defer sp.onCreate(operationName)
 | |
| 	return sp
 | |
| }
 | |
| 
 | |
| type delegatorType struct{}
 | |
| 
 | |
| // Delegator is the format to use for DelegatingCarrier.
 | |
| var Delegator delegatorType
 | |
| 
 | |
| func (t *LoggableTracer) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error {
 | |
| 	if !writer.WriterGroup.Active() {
 | |
| 		return opentracing.NoopTracer.Inject(opentracing.NoopTracer{}, sc, format, carrier)
 | |
| 	}
 | |
| 	switch format {
 | |
| 	case opentracing.TextMap, opentracing.HTTPHeaders:
 | |
| 		return t.textPropagator.Inject(sc, carrier)
 | |
| 	case opentracing.Binary:
 | |
| 		return t.binaryPropagator.Inject(sc, carrier)
 | |
| 	}
 | |
| 	if _, ok := format.(delegatorType); ok {
 | |
| 		return t.accessorPropagator.Inject(sc, carrier)
 | |
| 	}
 | |
| 	return opentracing.ErrUnsupportedFormat
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
 | |
| 	if !writer.WriterGroup.Active() {
 | |
| 		return opentracing.NoopTracer.Extract(opentracing.NoopTracer{}, format, carrier)
 | |
| 	}
 | |
| 	switch format {
 | |
| 	case opentracing.TextMap, opentracing.HTTPHeaders:
 | |
| 		return t.textPropagator.Extract(carrier)
 | |
| 	case opentracing.Binary:
 | |
| 		return t.binaryPropagator.Extract(carrier)
 | |
| 	}
 | |
| 	if _, ok := format.(delegatorType); ok {
 | |
| 		return t.accessorPropagator.Extract(carrier)
 | |
| 	}
 | |
| 	return nil, opentracing.ErrUnsupportedFormat
 | |
| }
 | |
| 
 | |
| func (t *LoggableTracer) Options() Options {
 | |
| 	return t.options
 | |
| }
 |