mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
299 lines
7.6 KiB
Go
299 lines
7.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackruntime
|
|
|
|
//lint:file-ignore U1000 Some utilities in here are intentionally unused in VCS but are for temporary use while debugging a test.
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"go.opentelemetry.io/otel/trace/embedded"
|
|
)
|
|
|
|
// tracesToTestLog arranges for any traces generated by the current test to
|
|
// be emitted directly into the test log using the log methods of the given
|
|
// [testing.T].
|
|
//
|
|
// This works by temporarily reassigning the global tracer provider and so
|
|
// is not suitable for parallel tests or subtests of tests that have already
|
|
// called this function.
|
|
//
|
|
// The results of this function are pretty chatty, so we should typically not
|
|
// leave this in a test checked in to version control, but it can be helpful to
|
|
// add temporarily during test debugging if it's unclear exactly how different
|
|
// components are interacting with one another.
|
|
func tracesToTestLog(t *testing.T) {
|
|
t.Helper()
|
|
oldProvider := otel.GetTracerProvider()
|
|
if _, ok := oldProvider.(*testLogTracerProvider); ok {
|
|
// This suggests that someone's tried to use tracesToTestLog in
|
|
// a parallel test or in a subtest of a test that already called it.
|
|
t.Fatal("overlapping tracesToTestLog")
|
|
}
|
|
t.Cleanup(func() {
|
|
otel.SetTracerProvider(oldProvider)
|
|
})
|
|
|
|
provider := testLogTracerProvider{
|
|
t: t,
|
|
spanTracker: &spanTracker{
|
|
names: make(map[trace.SpanID]string),
|
|
nextID: 1,
|
|
},
|
|
}
|
|
otel.SetTracerProvider(provider)
|
|
}
|
|
|
|
type testLogTracerProvider struct {
|
|
t *testing.T
|
|
spanTracker *spanTracker
|
|
|
|
embedded.TracerProvider
|
|
}
|
|
|
|
type spanTracker struct {
|
|
names map[trace.SpanID]string
|
|
nextID int
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func (t *spanTracker) StartNew(name string) trace.SpanID {
|
|
t.mu.Lock()
|
|
idRaw := t.nextID
|
|
t.nextID++
|
|
t.mu.Unlock()
|
|
|
|
ret := trace.SpanID{
|
|
0x00, 0x00, 0x00, 0x00,
|
|
byte(idRaw >> 24),
|
|
byte(idRaw >> 16),
|
|
byte(idRaw >> 8),
|
|
byte(idRaw >> 0),
|
|
}
|
|
t.TrackNew(ret, name)
|
|
return ret
|
|
}
|
|
|
|
func (sn *spanTracker) TrackNew(id trace.SpanID, name string) {
|
|
sn.mu.Lock()
|
|
sn.names[id] = name
|
|
sn.mu.Unlock()
|
|
}
|
|
|
|
func (sn *spanTracker) Get(id trace.SpanID) string {
|
|
sn.mu.Lock()
|
|
defer sn.mu.Unlock()
|
|
return sn.names[id]
|
|
}
|
|
|
|
func (sn *spanTracker) SpanDisplay(id trace.SpanID) string {
|
|
// we only use the last 32bits of the ids in this fake tracer, because
|
|
// the others will always be zero. (see testLogTracer.generateSpanID)
|
|
name := sn.Get(id)
|
|
idStr := testingSpanIDString(id)
|
|
if name == "" {
|
|
return idStr
|
|
}
|
|
return fmt.Sprintf("%s(%q)", idStr, name)
|
|
}
|
|
|
|
func (sn *spanTracker) SpanAttrDisplay(kv attribute.KeyValue) string {
|
|
v := kv.Value.AsInterface()
|
|
switch string(kv.Key) {
|
|
case "promise.waiting_for_id", "promise.waiter_id",
|
|
"promising.resolved_by", "promising.resolved_id",
|
|
"promising.delegated_from", "promising.delegated_to",
|
|
"promising.responsible_for":
|
|
|
|
// These conventionally contain stringified span IDs, which
|
|
// are 16 hex digits.
|
|
if v, ok := v.(string); ok && len(v) == 16 {
|
|
if bytes, err := hex.DecodeString(v); err == nil {
|
|
var spanID trace.SpanID
|
|
copy(spanID[:], bytes)
|
|
return sn.SpanDisplay(spanID)
|
|
}
|
|
}
|
|
}
|
|
// If all else fails we'll just GoString it
|
|
return fmt.Sprintf("%#v", v)
|
|
}
|
|
|
|
var _ trace.TracerProvider = (*testLogTracerProvider)(nil)
|
|
|
|
// Tracer implements trace.TracerProvider.
|
|
func (p testLogTracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
|
|
p.t.Helper()
|
|
return &testLogTracer{
|
|
t: p.t,
|
|
nextSpanID: 1,
|
|
spanTracker: p.spanTracker,
|
|
}
|
|
}
|
|
|
|
type testLogTracer struct {
|
|
t *testing.T
|
|
spanTracker *spanTracker
|
|
nextSpanID uint32
|
|
mu sync.Mutex
|
|
|
|
embedded.Tracer
|
|
}
|
|
|
|
var _ trace.Tracer = (*testLogTracer)(nil)
|
|
|
|
var fakeTraceIDForTesting = trace.TraceID{
|
|
0xfe, 0xed, 0xfa, 0xce,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
|
|
// Start implements trace.Tracer.
|
|
func (t *testLogTracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
|
t.t.Helper()
|
|
|
|
parentSpanCtx := trace.SpanContextFromContext(ctx)
|
|
|
|
dispName := spanName
|
|
switch dispName { // some shorthands for common names
|
|
case "async task":
|
|
dispName = "🗘"
|
|
case "promise":
|
|
dispName = "⋯"
|
|
}
|
|
if parentName := t.spanTracker.Get(parentSpanCtx.SpanID()); parentName != "" {
|
|
dispName = parentName + " ⇨ " + dispName
|
|
}
|
|
spanID := t.spanTracker.StartNew(dispName)
|
|
|
|
spanCtx := trace.NewSpanContext(trace.SpanContextConfig{
|
|
TraceID: fakeTraceIDForTesting,
|
|
SpanID: spanID,
|
|
})
|
|
span := &testLogTraceSpan{
|
|
name: spanName,
|
|
context: &spanCtx,
|
|
t: t.t,
|
|
spanTracker: t.spanTracker,
|
|
}
|
|
ctx = trace.ContextWithSpan(ctx, span)
|
|
|
|
cfg := trace.NewSpanStartConfig(opts...)
|
|
var attrsBuilder strings.Builder
|
|
if parentSpanCtx.HasSpanID() && !cfg.NewRoot() {
|
|
fmt.Fprintf(&attrsBuilder, "\nPARENT: %s", t.spanTracker.SpanDisplay(parentSpanCtx.SpanID()))
|
|
}
|
|
for _, link := range cfg.Links() {
|
|
fmt.Fprintf(&attrsBuilder, "\nLINK: %s", t.spanTracker.SpanDisplay(link.SpanContext.SpanID()))
|
|
}
|
|
for _, kv := range cfg.Attributes() {
|
|
fmt.Fprintf(&attrsBuilder, "\n%s = %s", kv.Key, t.spanTracker.SpanAttrDisplay(kv))
|
|
}
|
|
span.log("START%s", attrsBuilder.String())
|
|
return ctx, span
|
|
}
|
|
|
|
type testLogTraceSpan struct {
|
|
name string
|
|
context *trace.SpanContext
|
|
t *testing.T
|
|
spanTracker *spanTracker
|
|
|
|
embedded.Span
|
|
}
|
|
|
|
var _ trace.Span = (*testLogTraceSpan)(nil)
|
|
|
|
func (s testLogTraceSpan) log(f string, args ...any) {
|
|
s.t.Helper()
|
|
s.t.Logf(
|
|
"[trace:%s] %s\n%s",
|
|
testingSpanIDString(s.context.SpanID()),
|
|
s.spanTracker.Get(s.context.SpanID()),
|
|
fmt.Sprintf(f, args...),
|
|
)
|
|
}
|
|
|
|
func testingSpanIDString(id trace.SpanID) string {
|
|
// we only use the last 32bits of the ids in this fake tracer, because
|
|
// the others will always be zero. (see testLogTracer.generateSpanID)
|
|
return fmt.Sprintf("%x", id[4:])
|
|
}
|
|
|
|
// AddEvent implements trace.Span.
|
|
func (s testLogTraceSpan) AddEvent(name string, options ...trace.EventOption) {
|
|
s.t.Helper()
|
|
cfg := trace.NewEventConfig(options...)
|
|
var attrsBuilder strings.Builder
|
|
for _, kv := range cfg.Attributes() {
|
|
fmt.Fprintf(&attrsBuilder, "\n%s = %s", kv.Key, s.spanTracker.SpanAttrDisplay(kv))
|
|
}
|
|
s.log("EVENT %s%s", name, attrsBuilder.String())
|
|
}
|
|
|
|
// End implements trace.Span.
|
|
func (s testLogTraceSpan) End(options ...trace.SpanEndOption) {
|
|
s.t.Helper()
|
|
s.log("END")
|
|
}
|
|
|
|
// IsRecording implements trace.Span.
|
|
func (s testLogTraceSpan) IsRecording() bool {
|
|
s.t.Helper()
|
|
return true
|
|
}
|
|
|
|
// RecordError implements trace.Span.
|
|
func (s testLogTraceSpan) RecordError(err error, options ...trace.EventOption) {
|
|
s.t.Helper()
|
|
s.log("ERROR %s", err)
|
|
}
|
|
|
|
// SetAttributes implements trace.Span.
|
|
func (s testLogTraceSpan) SetAttributes(kv ...attribute.KeyValue) {
|
|
s.t.Helper()
|
|
}
|
|
|
|
// SetName implements trace.Span.
|
|
func (s *testLogTraceSpan) SetName(name string) {
|
|
s.t.Helper()
|
|
s.log("RENAMED to %s", name)
|
|
s.name = name
|
|
}
|
|
|
|
// SetStatus implements trace.Span.
|
|
func (s testLogTraceSpan) SetStatus(code codes.Code, description string) {
|
|
s.t.Helper()
|
|
s.log("STATUS %s: %s", code, description)
|
|
}
|
|
|
|
// SpanContext implements trace.Span.
|
|
func (s testLogTraceSpan) SpanContext() trace.SpanContext {
|
|
s.t.Helper()
|
|
return *s.context
|
|
}
|
|
|
|
// TracerProvider implements trace.Span.
|
|
func (s testLogTraceSpan) TracerProvider() trace.TracerProvider {
|
|
s.t.Helper()
|
|
return testLogTracerProvider{
|
|
t: s.t,
|
|
spanTracker: s.spanTracker,
|
|
}
|
|
}
|
|
|
|
// AddLink implements trace.Span.
|
|
func (s testLogTraceSpan) AddLink(link trace.Link) {
|
|
// Noop
|
|
}
|