mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	Merge pull request #2904 from tonistiigi/history-command-trace
Add history trace command
This commit is contained in:
		
							
								
								
									
										5
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							@@ -174,6 +174,11 @@ jobs:
 | 
			
		||||
    env:
 | 
			
		||||
      SKIP_INTEGRATION_TESTS: 1
 | 
			
		||||
    steps:
 | 
			
		||||
      -
 | 
			
		||||
        name: Setup Git config
 | 
			
		||||
        run: |
 | 
			
		||||
          git config --global core.autocrlf false
 | 
			
		||||
          git config --global core.eol lf
 | 
			
		||||
      -
 | 
			
		||||
        name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c
 | 
			
		||||
		logsCmd(dockerCli, opts),
 | 
			
		||||
		inspectCmd(dockerCli, opts),
 | 
			
		||||
		openCmd(dockerCli, opts),
 | 
			
		||||
		traceCmd(dockerCli, opts),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										260
									
								
								commands/history/trace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								commands/history/trace.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
package history
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/console"
 | 
			
		||||
	"github.com/containerd/containerd/v2/core/content/proxy"
 | 
			
		||||
	"github.com/docker/buildx/builder"
 | 
			
		||||
	"github.com/docker/buildx/util/cobrautil/completion"
 | 
			
		||||
	"github.com/docker/buildx/util/otelutil"
 | 
			
		||||
	"github.com/docker/buildx/util/otelutil/jaeger"
 | 
			
		||||
	"github.com/docker/cli/cli/command"
 | 
			
		||||
	controlapi "github.com/moby/buildkit/api/services/control"
 | 
			
		||||
	"github.com/opencontainers/go-digest"
 | 
			
		||||
	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
 | 
			
		||||
	"github.com/pkg/browser"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	jaegerui "github.com/tonistiigi/jaeger-ui-rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type traceOptions struct {
 | 
			
		||||
	builder string
 | 
			
		||||
	ref     string
 | 
			
		||||
	addr    string
 | 
			
		||||
	compare string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadTrace(ctx context.Context, ref string, nodes []builder.Node) (string, []byte, error) {
 | 
			
		||||
	var offset *int
 | 
			
		||||
	if strings.HasPrefix(ref, "^") {
 | 
			
		||||
		off, err := strconv.Atoi(ref[1:])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", nil, errors.Wrapf(err, "invalid offset %q", ref)
 | 
			
		||||
		}
 | 
			
		||||
		offset = &off
 | 
			
		||||
		ref = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recs, err := queryRecords(ctx, ref, nodes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var rec *historyRecord
 | 
			
		||||
 | 
			
		||||
	if ref == "" {
 | 
			
		||||
		slices.SortFunc(recs, func(a, b historyRecord) int {
 | 
			
		||||
			return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime())
 | 
			
		||||
		})
 | 
			
		||||
		for _, r := range recs {
 | 
			
		||||
			if r.CompletedAt != nil {
 | 
			
		||||
				if offset != nil {
 | 
			
		||||
					if *offset > 0 {
 | 
			
		||||
						*offset--
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				rec = &r
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if offset != nil && *offset > 0 {
 | 
			
		||||
			return "", nil, errors.Errorf("no completed build found with offset %d", *offset)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		rec = &recs[0]
 | 
			
		||||
	}
 | 
			
		||||
	if rec == nil {
 | 
			
		||||
		if ref == "" {
 | 
			
		||||
			return "", nil, errors.New("no records found")
 | 
			
		||||
		}
 | 
			
		||||
		return "", nil, errors.Errorf("no record found for ref %q", ref)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if rec.CompletedAt == nil {
 | 
			
		||||
		return "", nil, errors.Errorf("build %q is not completed, only completed builds can be traced", rec.Ref)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if rec.Trace == nil {
 | 
			
		||||
		// build is complete but no trace yet. try to finalize the trace
 | 
			
		||||
		time.Sleep(1 * time.Second) // give some extra time for last parts of trace to be written
 | 
			
		||||
 | 
			
		||||
		c, err := rec.node.Driver.Client(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", nil, err
 | 
			
		||||
		}
 | 
			
		||||
		_, err = c.ControlClient().UpdateBuildHistory(ctx, &controlapi.UpdateBuildHistoryRequest{
 | 
			
		||||
			Ref:      rec.Ref,
 | 
			
		||||
			Finalize: true,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recs, err := queryRecords(ctx, rec.Ref, []builder.Node{*rec.node})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(recs) == 0 {
 | 
			
		||||
			return "", nil, errors.Errorf("build record %q was deleted", rec.Ref)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		rec = &recs[0]
 | 
			
		||||
		if rec.Trace == nil {
 | 
			
		||||
			return "", nil, errors.Errorf("build record %q is missing a trace", rec.Ref)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c, err := rec.node.Driver.Client(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store := proxy.NewContentStore(c.ContentClient())
 | 
			
		||||
 | 
			
		||||
	ra, err := store.ReaderAt(ctx, ocispecs.Descriptor{
 | 
			
		||||
		Digest:    digest.Digest(rec.Trace.Digest),
 | 
			
		||||
		MediaType: rec.Trace.MediaType,
 | 
			
		||||
		Size:      rec.Trace.Size,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spans, err := otelutil.ParseSpanStubs(io.NewSectionReader(ra, 0, ra.Size()))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wrapper := struct {
 | 
			
		||||
		Data []jaeger.Trace `json:"data"`
 | 
			
		||||
	}{
 | 
			
		||||
		Data: spans.JaegerData().Data,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(wrapper.Data) == 0 {
 | 
			
		||||
		return "", nil, errors.New("no trace data")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	enc := json.NewEncoder(buf)
 | 
			
		||||
	enc.SetIndent("", "  ")
 | 
			
		||||
	if err := enc.Encode(wrapper); err != nil {
 | 
			
		||||
		return "", nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(wrapper.Data[0].TraceID), buf.Bytes(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runTrace(ctx context.Context, dockerCli command.Cli, opts traceOptions) error {
 | 
			
		||||
	b, err := builder.New(dockerCli, builder.WithName(opts.builder))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nodes, err := b.LoadNodes(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, node := range nodes {
 | 
			
		||||
		if node.Err != nil {
 | 
			
		||||
			return node.Err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	traceID, data, err := loadTrace(ctx, opts.ref, nodes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	srv := jaegerui.NewServer(jaegerui.Config{})
 | 
			
		||||
	if err := srv.AddTrace(traceID, bytes.NewReader(data)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	url := "/trace/" + traceID
 | 
			
		||||
 | 
			
		||||
	if opts.compare != "" {
 | 
			
		||||
		traceIDcomp, data, err := loadTrace(ctx, opts.compare, nodes)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "failed to load trace for %s", opts.compare)
 | 
			
		||||
		}
 | 
			
		||||
		if err := srv.AddTrace(traceIDcomp, bytes.NewReader(data)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		url = "/trace/" + traceIDcomp + "..." + traceID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var term bool
 | 
			
		||||
	if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
 | 
			
		||||
		term = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !term && opts.compare == "" {
 | 
			
		||||
		fmt.Fprintln(dockerCli.Out(), string(data))
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ln, err := net.Listen("tcp", opts.addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		time.Sleep(100 * time.Millisecond)
 | 
			
		||||
		browser.OpenURL(url)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	url = "http://" + ln.Addr().String() + url
 | 
			
		||||
	fmt.Fprintf(dockerCli.Err(), "Trace available at %s\n", url)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		<-ctx.Done()
 | 
			
		||||
		ln.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = srv.Serve(ln)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			return nil
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func traceCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
 | 
			
		||||
	var options traceOptions
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:   "trace [OPTIONS] [REF]",
 | 
			
		||||
		Short: "Show the OpenTelemetry trace of a build record",
 | 
			
		||||
		Args:  cobra.MaximumNArgs(1),
 | 
			
		||||
		RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
			if len(args) > 0 {
 | 
			
		||||
				options.ref = args[0]
 | 
			
		||||
			}
 | 
			
		||||
			options.builder = *rootOpts.Builder
 | 
			
		||||
			return runTrace(cmd.Context(), dockerCli, options)
 | 
			
		||||
		},
 | 
			
		||||
		ValidArgsFunction: completion.Disable,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flags := cmd.Flags()
 | 
			
		||||
	flags.StringVar(&options.addr, "addr", "127.0.0.1:0", "Address to bind the UI server")
 | 
			
		||||
	flags.StringVar(&options.compare, "compare", "", "Compare with another build reference")
 | 
			
		||||
 | 
			
		||||
	return cmd
 | 
			
		||||
}
 | 
			
		||||
@@ -6,12 +6,13 @@ Commands to work on build records
 | 
			
		||||
### Subcommands
 | 
			
		||||
 | 
			
		||||
| Name                                   | Description                                    |
 | 
			
		||||
|:---------------------------------------|:-------------------------------|
 | 
			
		||||
|:---------------------------------------|:-----------------------------------------------|
 | 
			
		||||
| [`inspect`](buildx_history_inspect.md) | Inspect a build                                |
 | 
			
		||||
| [`logs`](buildx_history_logs.md)       | Print the logs of a build                      |
 | 
			
		||||
| [`ls`](buildx_history_ls.md)           | List build records                             |
 | 
			
		||||
| [`open`](buildx_history_open.md)       | Open a build in Docker Desktop                 |
 | 
			
		||||
| [`rm`](buildx_history_rm.md)           | Remove build records                           |
 | 
			
		||||
| [`trace`](buildx_history_trace.md)     | Show the OpenTelemetry trace of a build record |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								docs/reference/buildx_history_trace.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docs/reference/buildx_history_trace.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
# docker buildx history trace
 | 
			
		||||
 | 
			
		||||
<!---MARKER_GEN_START-->
 | 
			
		||||
Show the OpenTelemetry trace of a build record
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 | 
			
		||||
| Name            | Type     | Default       | Description                              |
 | 
			
		||||
|:----------------|:---------|:--------------|:-----------------------------------------|
 | 
			
		||||
| `--addr`        | `string` | `127.0.0.1:0` | Address to bind the UI server            |
 | 
			
		||||
| `--builder`     | `string` |               | Override the configured builder instance |
 | 
			
		||||
| `--compare`     | `string` |               | Compare with another build reference     |
 | 
			
		||||
| `-D`, `--debug` | `bool`   |               | Enable debug logging                     |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!---MARKER_GEN_END-->
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -46,8 +46,10 @@ require (
 | 
			
		||||
	github.com/stretchr/testify v1.10.0
 | 
			
		||||
	github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
 | 
			
		||||
	github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
			
		||||
	github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6
 | 
			
		||||
	github.com/zclconf/go-cty v1.16.0
 | 
			
		||||
	go.opentelemetry.io/otel v1.31.0
 | 
			
		||||
	go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
 | 
			
		||||
	go.opentelemetry.io/otel/metric v1.31.0
 | 
			
		||||
	go.opentelemetry.io/otel/sdk v1.31.0
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.31.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@@ -447,6 +447,8 @@ github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a h1:EfGw4G0x/8qXW
 | 
			
		||||
github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a/go.mod h1:Dl/9oEjK7IqnjAm21Okx/XIxUCFJzvh+XdVHUlBwXTw=
 | 
			
		||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
 | 
			
		||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
 | 
			
		||||
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6 h1:RT/a0RvdX84iwtOrUK45+wjcNpaG+hS7n7XFYqj4axg=
 | 
			
		||||
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6/go.mod h1:3Ez1Paeg+0Ghu3KwpEGC1HgZ4CHDlg+Ez/5Baeomk54=
 | 
			
		||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
 | 
			
		||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
 | 
			
		||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw=
 | 
			
		||||
@@ -490,6 +492,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
 | 
			
		||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0 h1:UGZ1QwZWY67Z6BmckTU+9Rxn04m2bD3gD6Mk0OIOCPk=
 | 
			
		||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0/go.mod h1:fcwWuDuaObkkChiDlhEpSq9+X1C0omv+s5mBtToAQ64=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
 | 
			
		||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11125
									
								
								util/otelutil/fixtures/bktraces.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11125
									
								
								util/otelutil/fixtures/bktraces.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										9542
									
								
								util/otelutil/fixtures/jaeger.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9542
									
								
								util/otelutil/fixtures/jaeger.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11127
									
								
								util/otelutil/fixtures/otlp.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11127
									
								
								util/otelutil/fixtures/otlp.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										45
									
								
								util/otelutil/jaeger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								util/otelutil/jaeger.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
package otelutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/buildx/util/otelutil/jaeger"
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/resource"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type JaegerData struct {
 | 
			
		||||
	Data []jaeger.Trace `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JaegerData return Jaeger data compatible with ui import feature.
 | 
			
		||||
// https://github.com/jaegertracing/jaeger-ui/issues/381#issuecomment-494150826
 | 
			
		||||
func (s Spans) JaegerData() JaegerData {
 | 
			
		||||
	roSpans := s.ReadOnlySpans()
 | 
			
		||||
 | 
			
		||||
	// fetch default service.name from default resource for backup
 | 
			
		||||
	var defaultServiceName string
 | 
			
		||||
	defaultResource := resource.Default()
 | 
			
		||||
	if value, exists := defaultResource.Set().Value(attribute.Key("service.name")); exists {
 | 
			
		||||
		defaultServiceName = value.AsString()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := jaeger.Trace{
 | 
			
		||||
		TraceID:   jaeger.TraceID(roSpans[0].SpanContext().TraceID().String()),
 | 
			
		||||
		Processes: make(map[jaeger.ProcessID]jaeger.Process),
 | 
			
		||||
		Spans:     []jaeger.Span{},
 | 
			
		||||
	}
 | 
			
		||||
	for i := range roSpans {
 | 
			
		||||
		ss := roSpans[i]
 | 
			
		||||
		pid := jaeger.ProcessID(fmt.Sprintf("p%d", i))
 | 
			
		||||
		data.Processes[pid] = jaeger.ResourceToProcess(ss.Resource(), defaultServiceName)
 | 
			
		||||
		span := jaeger.ConvertSpan(ss)
 | 
			
		||||
		span.Process = nil
 | 
			
		||||
		span.ProcessID = pid
 | 
			
		||||
		data.Spans = append(data.Spans, span)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return JaegerData{
 | 
			
		||||
		Data: []jaeger.Trace{data},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										224
									
								
								util/otelutil/jaeger/convert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								util/otelutil/jaeger/convert.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,224 @@
 | 
			
		||||
package jaeger
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
	"go.opentelemetry.io/otel/codes"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/resource"
 | 
			
		||||
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 | 
			
		||||
	"go.opentelemetry.io/otel/trace"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	keyInstrumentationLibraryName    = "otel.library.name"
 | 
			
		||||
	keyInstrumentationLibraryVersion = "otel.library.version"
 | 
			
		||||
	keyError                         = "error"
 | 
			
		||||
	keySpanKind                      = "span.kind"
 | 
			
		||||
	keyStatusCode                    = "otel.status_code"
 | 
			
		||||
	keyStatusMessage                 = "otel.status_description"
 | 
			
		||||
	keyDroppedAttributeCount         = "otel.event.dropped_attributes_count"
 | 
			
		||||
	keyEventName                     = "event"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ResourceToProcess(res *resource.Resource, defaultServiceName string) Process {
 | 
			
		||||
	var process Process
 | 
			
		||||
	var serviceName attribute.KeyValue
 | 
			
		||||
	if res != nil {
 | 
			
		||||
		for iter := res.Iter(); iter.Next(); {
 | 
			
		||||
			if iter.Attribute().Key == attribute.Key("service.name") {
 | 
			
		||||
				serviceName = iter.Attribute()
 | 
			
		||||
				// Don't convert service.name into tag.
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if tag := keyValueToJaegerTag(iter.Attribute()); tag != nil {
 | 
			
		||||
				process.Tags = append(process.Tags, *tag)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If no service.name is contained in a Span's Resource,
 | 
			
		||||
	// that field MUST be populated from the default Resource.
 | 
			
		||||
	if serviceName.Value.AsString() == "" {
 | 
			
		||||
		serviceName = attribute.Key("service.version").String(defaultServiceName)
 | 
			
		||||
	}
 | 
			
		||||
	process.ServiceName = serviceName.Value.AsString()
 | 
			
		||||
 | 
			
		||||
	return process
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ConvertSpan(ss tracesdk.ReadOnlySpan) Span {
 | 
			
		||||
	attr := ss.Attributes()
 | 
			
		||||
	tags := make([]KeyValue, 0, len(attr))
 | 
			
		||||
	for _, kv := range attr {
 | 
			
		||||
		tag := keyValueToJaegerTag(kv)
 | 
			
		||||
		if tag != nil {
 | 
			
		||||
			tags = append(tags, *tag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if is := ss.InstrumentationScope(); is.Name != "" {
 | 
			
		||||
		tags = append(tags, getStringTag(keyInstrumentationLibraryName, is.Name))
 | 
			
		||||
		if is.Version != "" {
 | 
			
		||||
			tags = append(tags, getStringTag(keyInstrumentationLibraryVersion, is.Version))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ss.SpanKind() != trace.SpanKindInternal {
 | 
			
		||||
		tags = append(tags,
 | 
			
		||||
			getStringTag(keySpanKind, ss.SpanKind().String()),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ss.Status().Code != codes.Unset {
 | 
			
		||||
		switch ss.Status().Code {
 | 
			
		||||
		case codes.Ok:
 | 
			
		||||
			tags = append(tags, getStringTag(keyStatusCode, "OK"))
 | 
			
		||||
		case codes.Error:
 | 
			
		||||
			tags = append(tags, getBoolTag(keyError, true))
 | 
			
		||||
			tags = append(tags, getStringTag(keyStatusCode, "ERROR"))
 | 
			
		||||
		}
 | 
			
		||||
		if ss.Status().Description != "" {
 | 
			
		||||
			tags = append(tags, getStringTag(keyStatusMessage, ss.Status().Description))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var logs []Log
 | 
			
		||||
	for _, a := range ss.Events() {
 | 
			
		||||
		nTags := len(a.Attributes)
 | 
			
		||||
		if a.Name != "" {
 | 
			
		||||
			nTags++
 | 
			
		||||
		}
 | 
			
		||||
		if a.DroppedAttributeCount != 0 {
 | 
			
		||||
			nTags++
 | 
			
		||||
		}
 | 
			
		||||
		fields := make([]KeyValue, 0, nTags)
 | 
			
		||||
		if a.Name != "" {
 | 
			
		||||
			// If an event contains an attribute with the same key, it needs
 | 
			
		||||
			// to be given precedence and overwrite this.
 | 
			
		||||
			fields = append(fields, getStringTag(keyEventName, a.Name))
 | 
			
		||||
		}
 | 
			
		||||
		for _, kv := range a.Attributes {
 | 
			
		||||
			tag := keyValueToJaegerTag(kv)
 | 
			
		||||
			if tag != nil {
 | 
			
		||||
				fields = append(fields, *tag)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if a.DroppedAttributeCount != 0 {
 | 
			
		||||
			fields = append(fields, getInt64Tag(keyDroppedAttributeCount, int64(a.DroppedAttributeCount)))
 | 
			
		||||
		}
 | 
			
		||||
		logs = append(logs, Log{
 | 
			
		||||
			Timestamp: timeAsEpochMicroseconds(a.Time),
 | 
			
		||||
			Fields:    fields,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []Reference
 | 
			
		||||
	for _, link := range ss.Links() {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			RefType: FollowsFrom,
 | 
			
		||||
			TraceID: TraceID(link.SpanContext.TraceID().String()),
 | 
			
		||||
			SpanID:  SpanID(link.SpanContext.SpanID().String()),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	refs = append(refs, Reference{
 | 
			
		||||
		RefType: ChildOf,
 | 
			
		||||
		TraceID: TraceID(ss.Parent().TraceID().String()),
 | 
			
		||||
		SpanID:  SpanID(ss.Parent().SpanID().String()),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return Span{
 | 
			
		||||
		TraceID:       TraceID(ss.SpanContext().TraceID().String()),
 | 
			
		||||
		SpanID:        SpanID(ss.SpanContext().SpanID().String()),
 | 
			
		||||
		Flags:         uint32(ss.SpanContext().TraceFlags()),
 | 
			
		||||
		OperationName: ss.Name(),
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		StartTime:     timeAsEpochMicroseconds(ss.StartTime()),
 | 
			
		||||
		Duration:      durationAsMicroseconds(ss.EndTime().Sub(ss.StartTime())),
 | 
			
		||||
		Tags:          tags,
 | 
			
		||||
		Logs:          logs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func keyValueToJaegerTag(keyValue attribute.KeyValue) *KeyValue {
 | 
			
		||||
	var tag *KeyValue
 | 
			
		||||
	switch keyValue.Value.Type() {
 | 
			
		||||
	case attribute.STRING:
 | 
			
		||||
		s := keyValue.Value.AsString()
 | 
			
		||||
		tag = &KeyValue{
 | 
			
		||||
			Key:   string(keyValue.Key),
 | 
			
		||||
			Type:  StringType,
 | 
			
		||||
			Value: s,
 | 
			
		||||
		}
 | 
			
		||||
	case attribute.BOOL:
 | 
			
		||||
		b := keyValue.Value.AsBool()
 | 
			
		||||
		tag = &KeyValue{
 | 
			
		||||
			Key:   string(keyValue.Key),
 | 
			
		||||
			Type:  BoolType,
 | 
			
		||||
			Value: b,
 | 
			
		||||
		}
 | 
			
		||||
	case attribute.INT64:
 | 
			
		||||
		i := keyValue.Value.AsInt64()
 | 
			
		||||
		tag = &KeyValue{
 | 
			
		||||
			Key:   string(keyValue.Key),
 | 
			
		||||
			Type:  Int64Type,
 | 
			
		||||
			Value: i,
 | 
			
		||||
		}
 | 
			
		||||
	case attribute.FLOAT64:
 | 
			
		||||
		f := keyValue.Value.AsFloat64()
 | 
			
		||||
		tag = &KeyValue{
 | 
			
		||||
			Key:   string(keyValue.Key),
 | 
			
		||||
			Type:  Float64Type,
 | 
			
		||||
			Value: f,
 | 
			
		||||
		}
 | 
			
		||||
	case attribute.BOOLSLICE,
 | 
			
		||||
		attribute.INT64SLICE,
 | 
			
		||||
		attribute.FLOAT64SLICE,
 | 
			
		||||
		attribute.STRINGSLICE:
 | 
			
		||||
		data, _ := json.Marshal(keyValue.Value.AsInterface())
 | 
			
		||||
		a := (string)(data)
 | 
			
		||||
		tag = &KeyValue{
 | 
			
		||||
			Key:   string(keyValue.Key),
 | 
			
		||||
			Type:  StringType,
 | 
			
		||||
			Value: a,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getInt64Tag(k string, i int64) KeyValue {
 | 
			
		||||
	return KeyValue{
 | 
			
		||||
		Key:   k,
 | 
			
		||||
		Type:  Int64Type,
 | 
			
		||||
		Value: i,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getStringTag(k, s string) KeyValue {
 | 
			
		||||
	return KeyValue{
 | 
			
		||||
		Key:   k,
 | 
			
		||||
		Type:  StringType,
 | 
			
		||||
		Value: s,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBoolTag(k string, b bool) KeyValue {
 | 
			
		||||
	return KeyValue{
 | 
			
		||||
		Key:   k,
 | 
			
		||||
		Type:  BoolType,
 | 
			
		||||
		Value: b,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// timeAsEpochMicroseconds converts time.Time to microseconds since epoch,
 | 
			
		||||
// which is the format the StartTime field is stored in the Span.
 | 
			
		||||
func timeAsEpochMicroseconds(t time.Time) uint64 {
 | 
			
		||||
	return uint64(t.UnixNano() / 1000)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// durationAsMicroseconds converts time.Duration to microseconds,
 | 
			
		||||
// which is the format the Duration field is stored in the Span.
 | 
			
		||||
func durationAsMicroseconds(d time.Duration) uint64 {
 | 
			
		||||
	return uint64(d.Nanoseconds() / 1000)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								util/otelutil/jaeger/model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								util/otelutil/jaeger/model.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
package jaeger
 | 
			
		||||
 | 
			
		||||
// ReferenceType is the reference type of one span to another
 | 
			
		||||
type ReferenceType string
 | 
			
		||||
 | 
			
		||||
// TraceID is the shared trace ID of all spans in the trace.
 | 
			
		||||
type TraceID string
 | 
			
		||||
 | 
			
		||||
// SpanID is the id of a span
 | 
			
		||||
type SpanID string
 | 
			
		||||
 | 
			
		||||
// ProcessID is a hashed value of the Process struct that is unique within the trace.
 | 
			
		||||
type ProcessID string
 | 
			
		||||
 | 
			
		||||
// ValueType is the type of a value stored in KeyValue struct.
 | 
			
		||||
type ValueType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// ChildOf means a span is the child of another span
 | 
			
		||||
	ChildOf ReferenceType = "CHILD_OF"
 | 
			
		||||
	// FollowsFrom means a span follows from another span
 | 
			
		||||
	FollowsFrom ReferenceType = "FOLLOWS_FROM"
 | 
			
		||||
 | 
			
		||||
	// StringType indicates a string value stored in KeyValue
 | 
			
		||||
	StringType ValueType = "string"
 | 
			
		||||
	// BoolType indicates a Boolean value stored in KeyValue
 | 
			
		||||
	BoolType ValueType = "bool"
 | 
			
		||||
	// Int64Type indicates a 64bit signed integer value stored in KeyValue
 | 
			
		||||
	Int64Type ValueType = "int64"
 | 
			
		||||
	// Float64Type indicates a 64bit float value stored in KeyValue
 | 
			
		||||
	Float64Type ValueType = "float64"
 | 
			
		||||
	// BinaryType indicates an arbitrary byte array stored in KeyValue
 | 
			
		||||
	BinaryType ValueType = "binary"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Trace is a list of spans
 | 
			
		||||
type Trace struct {
 | 
			
		||||
	TraceID   TraceID               `json:"traceID"`
 | 
			
		||||
	Spans     []Span                `json:"spans"`
 | 
			
		||||
	Processes map[ProcessID]Process `json:"processes"`
 | 
			
		||||
	Warnings  []string              `json:"warnings"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Span is a span denoting a piece of work in some infrastructure
 | 
			
		||||
// When converting to UI model, ParentSpanID and Process should be dereferenced into
 | 
			
		||||
// References and ProcessID, respectively.
 | 
			
		||||
// When converting to ES model, ProcessID and Warnings should be omitted. Even if
 | 
			
		||||
// included, ES with dynamic settings off will automatically ignore unneeded fields.
 | 
			
		||||
type Span struct {
 | 
			
		||||
	TraceID       TraceID     `json:"traceID"`
 | 
			
		||||
	SpanID        SpanID      `json:"spanID"`
 | 
			
		||||
	ParentSpanID  SpanID      `json:"parentSpanID,omitempty"` // deprecated
 | 
			
		||||
	Flags         uint32      `json:"flags,omitempty"`
 | 
			
		||||
	OperationName string      `json:"operationName"`
 | 
			
		||||
	References    []Reference `json:"references"`
 | 
			
		||||
	StartTime     uint64      `json:"startTime"` // microseconds since Unix epoch
 | 
			
		||||
	Duration      uint64      `json:"duration"`  // microseconds
 | 
			
		||||
	Tags          []KeyValue  `json:"tags"`
 | 
			
		||||
	Logs          []Log       `json:"logs"`
 | 
			
		||||
	ProcessID     ProcessID   `json:"processID,omitempty"`
 | 
			
		||||
	Process       *Process    `json:"process,omitempty"`
 | 
			
		||||
	Warnings      []string    `json:"warnings"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reference is a reference from one span to another
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	RefType ReferenceType `json:"refType"`
 | 
			
		||||
	TraceID TraceID       `json:"traceID"`
 | 
			
		||||
	SpanID  SpanID        `json:"spanID"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Process is the process emitting a set of spans
 | 
			
		||||
type Process struct {
 | 
			
		||||
	ServiceName string     `json:"serviceName"`
 | 
			
		||||
	Tags        []KeyValue `json:"tags"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Log is a log emitted in a span
 | 
			
		||||
type Log struct {
 | 
			
		||||
	Timestamp uint64     `json:"timestamp"`
 | 
			
		||||
	Fields    []KeyValue `json:"fields"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KeyValue is a key-value pair with typed value.
 | 
			
		||||
type KeyValue struct {
 | 
			
		||||
	Key   string      `json:"key"`
 | 
			
		||||
	Type  ValueType   `json:"type,omitempty"`
 | 
			
		||||
	Value interface{} `json:"value"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DependencyLink shows dependencies between services
 | 
			
		||||
type DependencyLink struct {
 | 
			
		||||
	Parent    string `json:"parent"`
 | 
			
		||||
	Child     string `json:"child"`
 | 
			
		||||
	CallCount uint64 `json:"callCount"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Operation defines the data in the operation response when query operation by service and span kind
 | 
			
		||||
type Operation struct {
 | 
			
		||||
	Name     string `json:"name"`
 | 
			
		||||
	SpanKind string `json:"spanKind"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								util/otelutil/jaeger_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								util/otelutil/jaeger_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package otelutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const jaegerFixture = "./fixtures/jaeger.json"
 | 
			
		||||
 | 
			
		||||
func TestJaegerData(t *testing.T) {
 | 
			
		||||
	dt, err := os.ReadFile(bktracesFixture)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	spanStubs, err := ParseSpanStubs(bytes.NewReader(dt))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	trace := spanStubs.JaegerData()
 | 
			
		||||
	dtJaegerTrace, err := json.MarshalIndent(trace, "", "  ")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	dtJaeger, err := os.ReadFile(jaegerFixture)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Equal(t, string(dtJaeger), string(dtJaegerTrace))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										491
									
								
								util/otelutil/span.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								util/otelutil/span.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,491 @@
 | 
			
		||||
package otelutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/instrumentation"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/resource"
 | 
			
		||||
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
 | 
			
		||||
	"go.opentelemetry.io/otel/trace"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Span is a type similar to otel's SpanStub, but with the correct types needed
 | 
			
		||||
// for handle marshaling and unmarshalling.
 | 
			
		||||
type Span struct {
 | 
			
		||||
	// Name is the name of a specific span
 | 
			
		||||
	Name string
 | 
			
		||||
	// SpanContext is the unique SpanContext that identifies the span
 | 
			
		||||
	SpanContext trace.SpanContext
 | 
			
		||||
	// Parten is the unique SpanContext that identifies the parent of the span.
 | 
			
		||||
	// If the span has no parent, this span context will be invalid.
 | 
			
		||||
	Parent trace.SpanContext
 | 
			
		||||
	// SpanKind is the role the span plays in a Trace
 | 
			
		||||
	SpanKind trace.SpanKind
 | 
			
		||||
	// StartTime is the time the span started recording
 | 
			
		||||
	StartTime time.Time
 | 
			
		||||
	// EndTime returns the time the span stopped recording
 | 
			
		||||
	EndTime time.Time
 | 
			
		||||
	// Attributes are the defining attributes of a span
 | 
			
		||||
	Attributes []attribute.KeyValue
 | 
			
		||||
	// Events are all the events that occurred within the span
 | 
			
		||||
	Events []tracesdk.Event
 | 
			
		||||
	// Links are all the links the span has to other spans
 | 
			
		||||
	Links []tracesdk.Link
 | 
			
		||||
	// Status is that span status
 | 
			
		||||
	Status tracesdk.Status
 | 
			
		||||
	// DroppedAttributes is the number of attributes dropped by the span due to
 | 
			
		||||
	// a limit being reached
 | 
			
		||||
	DroppedAttributes int
 | 
			
		||||
	// DroppedEvents is the number of attributes dropped by the span due to a
 | 
			
		||||
	// limit being reached
 | 
			
		||||
	DroppedEvents int
 | 
			
		||||
	// DroppedLinks is the number of links dropped by the span due to a limit
 | 
			
		||||
	// being reached
 | 
			
		||||
	DroppedLinks int
 | 
			
		||||
	// ChildSpanCount is the count of spans that consider the span a direct
 | 
			
		||||
	// parent
 | 
			
		||||
	ChildSpanCount int
 | 
			
		||||
	// Resource is the information about the entity that produced the span
 | 
			
		||||
	// We have to change this type from the otel type to make this struct
 | 
			
		||||
	// marshallable
 | 
			
		||||
	Resource []attribute.KeyValue
 | 
			
		||||
	// InstrumentationLibrary is information about the library that produced
 | 
			
		||||
	// the span
 | 
			
		||||
	//nolint:staticcheck
 | 
			
		||||
	InstrumentationLibrary instrumentation.Library
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Spans []Span
 | 
			
		||||
 | 
			
		||||
// Len return the length of the Spans.
 | 
			
		||||
func (s Spans) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadOnlySpans return a list of tracesdk.ReadOnlySpan from span stubs.
 | 
			
		||||
func (s Spans) ReadOnlySpans() []tracesdk.ReadOnlySpan {
 | 
			
		||||
	roSpans := make([]tracesdk.ReadOnlySpan, len(s))
 | 
			
		||||
	for i := range s {
 | 
			
		||||
		roSpans[i] = s[i].Snapshot()
 | 
			
		||||
	}
 | 
			
		||||
	return roSpans
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseSpanStubs parses BuildKit trace data into a list of SpanStubs.
 | 
			
		||||
func ParseSpanStubs(rdr io.Reader) (Spans, error) {
 | 
			
		||||
	var spanStubs []Span
 | 
			
		||||
	decoder := json.NewDecoder(rdr)
 | 
			
		||||
	for {
 | 
			
		||||
		var span Span
 | 
			
		||||
		if err := decoder.Decode(&span); err == io.EOF {
 | 
			
		||||
			break
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			return nil, errors.Wrapf(err, "error decoding JSON")
 | 
			
		||||
		}
 | 
			
		||||
		spanStubs = append(spanStubs, span)
 | 
			
		||||
	}
 | 
			
		||||
	return spanStubs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// spanData is data that we need to unmarshal in custom ways.
 | 
			
		||||
type spanData struct {
 | 
			
		||||
	Name              string
 | 
			
		||||
	SpanContext       spanContext
 | 
			
		||||
	Parent            spanContext
 | 
			
		||||
	SpanKind          trace.SpanKind
 | 
			
		||||
	StartTime         time.Time
 | 
			
		||||
	EndTime           time.Time
 | 
			
		||||
	Attributes        []keyValue
 | 
			
		||||
	Events            []event
 | 
			
		||||
	Links             []link
 | 
			
		||||
	Status            tracesdk.Status
 | 
			
		||||
	DroppedAttributes int
 | 
			
		||||
	DroppedEvents     int
 | 
			
		||||
	DroppedLinks      int
 | 
			
		||||
	ChildSpanCount    int
 | 
			
		||||
	Resource          []keyValue // change this type from the otel type to make this struct marshallable
 | 
			
		||||
	//nolint:staticcheck
 | 
			
		||||
	InstrumentationLibrary instrumentation.Library
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// spanContext is a custom type used to unmarshal otel SpanContext correctly.
 | 
			
		||||
type spanContext struct {
 | 
			
		||||
	TraceID    string
 | 
			
		||||
	SpanID     string
 | 
			
		||||
	TraceFlags string
 | 
			
		||||
	TraceState string // TODO: implement, currently dropped
 | 
			
		||||
	Remote     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// event is a custom type used to unmarshal otel Event correctly.
 | 
			
		||||
type event struct {
 | 
			
		||||
	Name                  string
 | 
			
		||||
	Attributes            []keyValue
 | 
			
		||||
	DroppedAttributeCount int
 | 
			
		||||
	Time                  time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// link is a custom type used to unmarshal otel Link correctly.
 | 
			
		||||
type link struct {
 | 
			
		||||
	SpanContext           spanContext
 | 
			
		||||
	Attributes            []keyValue
 | 
			
		||||
	DroppedAttributeCount int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// keyValue is a custom type used to unmarshal otel KeyValue correctly.
 | 
			
		||||
type keyValue struct {
 | 
			
		||||
	Key   string
 | 
			
		||||
	Value value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// value is a custom type used to unmarshal otel Value correctly.
 | 
			
		||||
type value struct {
 | 
			
		||||
	Type  string
 | 
			
		||||
	Value interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnmarshalJSON implements json.Unmarshaler for Span which allows correctly
 | 
			
		||||
// retrieving attribute.KeyValue values.
 | 
			
		||||
func (s *Span) UnmarshalJSON(data []byte) error {
 | 
			
		||||
	var sd spanData
 | 
			
		||||
	if err := json.NewDecoder(bytes.NewReader(data)).Decode(&sd); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unable to decode to spanData")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.Name = sd.Name
 | 
			
		||||
	s.SpanKind = sd.SpanKind
 | 
			
		||||
	s.StartTime = sd.StartTime
 | 
			
		||||
	s.EndTime = sd.EndTime
 | 
			
		||||
	s.Status = sd.Status
 | 
			
		||||
	s.DroppedAttributes = sd.DroppedAttributes
 | 
			
		||||
	s.DroppedEvents = sd.DroppedEvents
 | 
			
		||||
	s.DroppedLinks = sd.DroppedLinks
 | 
			
		||||
	s.ChildSpanCount = sd.ChildSpanCount
 | 
			
		||||
	s.InstrumentationLibrary = sd.InstrumentationLibrary
 | 
			
		||||
 | 
			
		||||
	spanCtx, err := sd.SpanContext.asTraceSpanContext()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unable to decode spanCtx")
 | 
			
		||||
	}
 | 
			
		||||
	s.SpanContext = spanCtx
 | 
			
		||||
 | 
			
		||||
	parent, err := sd.Parent.asTraceSpanContext()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "unable to decode parent")
 | 
			
		||||
	}
 | 
			
		||||
	s.Parent = parent
 | 
			
		||||
 | 
			
		||||
	var attributes []attribute.KeyValue
 | 
			
		||||
	for _, a := range sd.Attributes {
 | 
			
		||||
		kv, err := a.asAttributeKeyValue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unable to decode attribute (%s)", a.Key)
 | 
			
		||||
		}
 | 
			
		||||
		attributes = append(attributes, kv)
 | 
			
		||||
	}
 | 
			
		||||
	s.Attributes = attributes
 | 
			
		||||
 | 
			
		||||
	var events []tracesdk.Event
 | 
			
		||||
	for _, e := range sd.Events {
 | 
			
		||||
		var eventAttributes []attribute.KeyValue
 | 
			
		||||
		for _, a := range e.Attributes {
 | 
			
		||||
			kv, err := a.asAttributeKeyValue()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unable to decode event attribute (%s)", a.Key)
 | 
			
		||||
			}
 | 
			
		||||
			eventAttributes = append(eventAttributes, kv)
 | 
			
		||||
		}
 | 
			
		||||
		events = append(events, tracesdk.Event{
 | 
			
		||||
			Name:                  e.Name,
 | 
			
		||||
			Attributes:            eventAttributes,
 | 
			
		||||
			DroppedAttributeCount: e.DroppedAttributeCount,
 | 
			
		||||
			Time:                  e.Time,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	s.Events = events
 | 
			
		||||
 | 
			
		||||
	var links []tracesdk.Link
 | 
			
		||||
	for _, l := range sd.Links {
 | 
			
		||||
		linkSpanCtx, err := l.SpanContext.asTraceSpanContext()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "unable to decode linkSpanCtx")
 | 
			
		||||
		}
 | 
			
		||||
		var linkAttributes []attribute.KeyValue
 | 
			
		||||
		for _, a := range l.Attributes {
 | 
			
		||||
			kv, err := a.asAttributeKeyValue()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "unable to decode link attribute (%s)", a.Key)
 | 
			
		||||
			}
 | 
			
		||||
			linkAttributes = append(linkAttributes, kv)
 | 
			
		||||
		}
 | 
			
		||||
		links = append(links, tracesdk.Link{
 | 
			
		||||
			SpanContext:           linkSpanCtx,
 | 
			
		||||
			Attributes:            linkAttributes,
 | 
			
		||||
			DroppedAttributeCount: l.DroppedAttributeCount,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	s.Links = links
 | 
			
		||||
 | 
			
		||||
	var resources []attribute.KeyValue
 | 
			
		||||
	for _, r := range sd.Resource {
 | 
			
		||||
		kv, err := r.asAttributeKeyValue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "unable to decode resource (%s)", r.Key)
 | 
			
		||||
		}
 | 
			
		||||
		resources = append(resources, kv)
 | 
			
		||||
	}
 | 
			
		||||
	s.Resource = resources
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// asTraceSpanContext converts the internal spanContext representation to an
 | 
			
		||||
// otel one.
 | 
			
		||||
func (sc *spanContext) asTraceSpanContext() (trace.SpanContext, error) {
 | 
			
		||||
	traceID, err := traceIDFromHex(sc.TraceID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return trace.SpanContext{}, errors.Wrap(err, "unable to parse trace id")
 | 
			
		||||
	}
 | 
			
		||||
	spanID, err := spanIDFromHex(sc.SpanID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return trace.SpanContext{}, errors.Wrap(err, "unable to parse span id")
 | 
			
		||||
	}
 | 
			
		||||
	traceFlags := trace.TraceFlags(0x00)
 | 
			
		||||
	if sc.TraceFlags == "01" {
 | 
			
		||||
		traceFlags = trace.TraceFlags(0x01)
 | 
			
		||||
	}
 | 
			
		||||
	config := trace.SpanContextConfig{
 | 
			
		||||
		TraceID:    traceID,
 | 
			
		||||
		SpanID:     spanID,
 | 
			
		||||
		TraceFlags: traceFlags,
 | 
			
		||||
		Remote:     sc.Remote,
 | 
			
		||||
	}
 | 
			
		||||
	return trace.NewSpanContext(config), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// asAttributeKeyValue converts the internal keyValue representation to an
 | 
			
		||||
// otel one.
 | 
			
		||||
func (kv *keyValue) asAttributeKeyValue() (attribute.KeyValue, error) {
 | 
			
		||||
	// value types get encoded as string
 | 
			
		||||
	switch kv.Value.Type {
 | 
			
		||||
	case attribute.INVALID.String():
 | 
			
		||||
		return attribute.KeyValue{}, errors.New("invalid value type")
 | 
			
		||||
	case attribute.BOOL.String():
 | 
			
		||||
		return attribute.Bool(kv.Key, kv.Value.Value.(bool)), nil
 | 
			
		||||
	case attribute.INT64.String():
 | 
			
		||||
		// value could be int64 or float64, so handle both cases (float64 comes
 | 
			
		||||
		// from json unmarshal)
 | 
			
		||||
		var v int64
 | 
			
		||||
		switch i := kv.Value.Value.(type) {
 | 
			
		||||
		case int64:
 | 
			
		||||
			v = i
 | 
			
		||||
		case float64:
 | 
			
		||||
			v = int64(i)
 | 
			
		||||
		}
 | 
			
		||||
		return attribute.Int64(kv.Key, v), nil
 | 
			
		||||
	case attribute.FLOAT64.String():
 | 
			
		||||
		return attribute.Float64(kv.Key, kv.Value.Value.(float64)), nil
 | 
			
		||||
	case attribute.STRING.String():
 | 
			
		||||
		return attribute.String(kv.Key, kv.Value.Value.(string)), nil
 | 
			
		||||
	case attribute.BOOLSLICE.String():
 | 
			
		||||
		return attribute.BoolSlice(kv.Key, kv.Value.Value.([]bool)), nil
 | 
			
		||||
	case attribute.INT64SLICE.String():
 | 
			
		||||
		// handle both float64 and int64 (float64 comes from json unmarshal)
 | 
			
		||||
		var v []int64
 | 
			
		||||
		switch sli := kv.Value.Value.(type) {
 | 
			
		||||
		case []int64:
 | 
			
		||||
			v = sli
 | 
			
		||||
		case []float64:
 | 
			
		||||
			for i := range sli {
 | 
			
		||||
				v = append(v, int64(sli[i]))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return attribute.Int64Slice(kv.Key, v), nil
 | 
			
		||||
	case attribute.FLOAT64SLICE.String():
 | 
			
		||||
		return attribute.Float64Slice(kv.Key, kv.Value.Value.([]float64)), nil
 | 
			
		||||
	case attribute.STRINGSLICE.String():
 | 
			
		||||
		var strSli []string
 | 
			
		||||
		// sometimes we can get an []interface{} instead of a []string, so
 | 
			
		||||
		// always cast to []string if that happens.
 | 
			
		||||
		switch sli := kv.Value.Value.(type) {
 | 
			
		||||
		case []string:
 | 
			
		||||
			strSli = sli
 | 
			
		||||
		case []interface{}:
 | 
			
		||||
			for i := range sli {
 | 
			
		||||
				var v string
 | 
			
		||||
				// best case we have a string, otherwise, cast it using
 | 
			
		||||
				// fmt.Sprintf
 | 
			
		||||
				if str, ok := sli[i].(string); ok {
 | 
			
		||||
					v = str
 | 
			
		||||
				} else {
 | 
			
		||||
					v = fmt.Sprintf("%v", sli[i])
 | 
			
		||||
				}
 | 
			
		||||
				// add the string to the slice
 | 
			
		||||
				strSli = append(strSli, v)
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return attribute.KeyValue{}, errors.Errorf("got unsupported type %q for %s", reflect.ValueOf(kv.Value.Value).Kind(), attribute.STRINGSLICE.String())
 | 
			
		||||
		}
 | 
			
		||||
		return attribute.StringSlice(kv.Key, strSli), nil
 | 
			
		||||
	default:
 | 
			
		||||
		return attribute.KeyValue{}, errors.Errorf("unknown value type %s", kv.Value.Type)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// traceIDFromHex returns a TraceID from a hex string if it is compliant with
 | 
			
		||||
// the W3C trace-context specification and removes the validity check.
 | 
			
		||||
// https://www.w3.org/TR/trace-context/#trace-id
 | 
			
		||||
func traceIDFromHex(h string) (trace.TraceID, error) {
 | 
			
		||||
	t := trace.TraceID{}
 | 
			
		||||
	if len(h) != 32 {
 | 
			
		||||
		return t, errors.New("unable to parse trace id")
 | 
			
		||||
	}
 | 
			
		||||
	if err := decodeHex(h, t[:]); err != nil {
 | 
			
		||||
		return t, err
 | 
			
		||||
	}
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// spanIDFromHex returns a SpanID from a hex string if it is compliant with the
 | 
			
		||||
// W3C trace-context specification and removes the validity check.
 | 
			
		||||
// https://www.w3.org/TR/trace-context/#parent-id
 | 
			
		||||
func spanIDFromHex(h string) (trace.SpanID, error) {
 | 
			
		||||
	s := trace.SpanID{}
 | 
			
		||||
	if len(h) != 16 {
 | 
			
		||||
		return s, errors.New("unable to parse span id of length: %d")
 | 
			
		||||
	}
 | 
			
		||||
	if err := decodeHex(h, s[:]); err != nil {
 | 
			
		||||
		return s, err
 | 
			
		||||
	}
 | 
			
		||||
	return s, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// decodeHex decodes hex in a manner compliant with otel.
 | 
			
		||||
func decodeHex(h string, b []byte) error {
 | 
			
		||||
	for _, r := range h {
 | 
			
		||||
		switch {
 | 
			
		||||
		case 'a' <= r && r <= 'f':
 | 
			
		||||
			continue
 | 
			
		||||
		case '0' <= r && r <= '9':
 | 
			
		||||
			continue
 | 
			
		||||
		default:
 | 
			
		||||
			return errors.New("unable to parse hex id")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	decoded, err := hex.DecodeString(h)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	copy(b, decoded)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Snapshot turns a Span into a ReadOnlySpan which is exportable by otel.
 | 
			
		||||
func (s *Span) Snapshot() tracesdk.ReadOnlySpan {
 | 
			
		||||
	return spanSnapshot{
 | 
			
		||||
		name:                 s.Name,
 | 
			
		||||
		spanContext:          s.SpanContext,
 | 
			
		||||
		parent:               s.Parent,
 | 
			
		||||
		spanKind:             s.SpanKind,
 | 
			
		||||
		startTime:            s.StartTime,
 | 
			
		||||
		endTime:              s.EndTime,
 | 
			
		||||
		attributes:           s.Attributes,
 | 
			
		||||
		events:               s.Events,
 | 
			
		||||
		links:                s.Links,
 | 
			
		||||
		status:               s.Status,
 | 
			
		||||
		droppedAttributes:    s.DroppedAttributes,
 | 
			
		||||
		droppedEvents:        s.DroppedEvents,
 | 
			
		||||
		droppedLinks:         s.DroppedLinks,
 | 
			
		||||
		childSpanCount:       s.ChildSpanCount,
 | 
			
		||||
		resource:             resource.NewSchemaless(s.Resource...),
 | 
			
		||||
		instrumentationScope: s.InstrumentationLibrary,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// spanSnapshot is a helper type for transforming a Span into a ReadOnlySpan.
 | 
			
		||||
type spanSnapshot struct {
 | 
			
		||||
	// Embed the interface to implement the private method.
 | 
			
		||||
	tracesdk.ReadOnlySpan
 | 
			
		||||
 | 
			
		||||
	name                 string
 | 
			
		||||
	spanContext          trace.SpanContext
 | 
			
		||||
	parent               trace.SpanContext
 | 
			
		||||
	spanKind             trace.SpanKind
 | 
			
		||||
	startTime            time.Time
 | 
			
		||||
	endTime              time.Time
 | 
			
		||||
	attributes           []attribute.KeyValue
 | 
			
		||||
	events               []tracesdk.Event
 | 
			
		||||
	links                []tracesdk.Link
 | 
			
		||||
	status               tracesdk.Status
 | 
			
		||||
	droppedAttributes    int
 | 
			
		||||
	droppedEvents        int
 | 
			
		||||
	droppedLinks         int
 | 
			
		||||
	childSpanCount       int
 | 
			
		||||
	resource             *resource.Resource
 | 
			
		||||
	instrumentationScope instrumentation.Scope
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name returns the Name of the snapshot
 | 
			
		||||
func (s spanSnapshot) Name() string { return s.name }
 | 
			
		||||
 | 
			
		||||
// SpanContext returns the SpanContext of the snapshot
 | 
			
		||||
func (s spanSnapshot) SpanContext() trace.SpanContext { return s.spanContext }
 | 
			
		||||
 | 
			
		||||
// Parent returns the Parent of the snapshot
 | 
			
		||||
func (s spanSnapshot) Parent() trace.SpanContext { return s.parent }
 | 
			
		||||
 | 
			
		||||
// SpanKind returns the SpanKind of the snapshot
 | 
			
		||||
func (s spanSnapshot) SpanKind() trace.SpanKind { return s.spanKind }
 | 
			
		||||
 | 
			
		||||
// StartTime returns the StartTime of the snapshot
 | 
			
		||||
func (s spanSnapshot) StartTime() time.Time { return s.startTime }
 | 
			
		||||
 | 
			
		||||
// EndTime returns the EndTime of the snapshot
 | 
			
		||||
func (s spanSnapshot) EndTime() time.Time { return s.endTime }
 | 
			
		||||
 | 
			
		||||
// Attributes returns the Attributes of the snapshot
 | 
			
		||||
func (s spanSnapshot) Attributes() []attribute.KeyValue { return s.attributes }
 | 
			
		||||
 | 
			
		||||
// Links returns the Links of the snapshot
 | 
			
		||||
func (s spanSnapshot) Links() []tracesdk.Link { return s.links }
 | 
			
		||||
 | 
			
		||||
// Events return the Events of the snapshot
 | 
			
		||||
func (s spanSnapshot) Events() []tracesdk.Event { return s.events }
 | 
			
		||||
 | 
			
		||||
// Status returns the Status of the snapshot
 | 
			
		||||
func (s spanSnapshot) Status() tracesdk.Status { return s.status }
 | 
			
		||||
 | 
			
		||||
// DroppedAttributes returns the DroppedAttributes of the snapshot
 | 
			
		||||
func (s spanSnapshot) DroppedAttributes() int { return s.droppedAttributes }
 | 
			
		||||
 | 
			
		||||
// DroppedLinks returns the DroppedLinks of the snapshot
 | 
			
		||||
func (s spanSnapshot) DroppedLinks() int { return s.droppedLinks }
 | 
			
		||||
 | 
			
		||||
// DroppedEvents returns the DroppedEvents of the snapshot
 | 
			
		||||
func (s spanSnapshot) DroppedEvents() int { return s.droppedEvents }
 | 
			
		||||
 | 
			
		||||
// ChildSpanCount returns the ChildSpanCount of the snapshot
 | 
			
		||||
func (s spanSnapshot) ChildSpanCount() int { return s.childSpanCount }
 | 
			
		||||
 | 
			
		||||
// Resource returns the Resource of the snapshot
 | 
			
		||||
func (s spanSnapshot) Resource() *resource.Resource { return s.resource }
 | 
			
		||||
 | 
			
		||||
// InstrumentationScope returns the InstrumentationScope of the snapshot
 | 
			
		||||
func (s spanSnapshot) InstrumentationScope() instrumentation.Scope {
 | 
			
		||||
	return s.instrumentationScope
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InstrumentationLibrary returns the InstrumentationLibrary of the snapshot
 | 
			
		||||
//
 | 
			
		||||
//nolint:staticcheck
 | 
			
		||||
func (s spanSnapshot) InstrumentationLibrary() instrumentation.Library {
 | 
			
		||||
	return s.instrumentationScope
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										159
									
								
								util/otelutil/span_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								util/otelutil/span_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
package otelutil
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// curl -s --unix-socket /tmp/docker-desktop-build-dev.sock http://localhost/blobs/default/default?digest=sha256:3103104e9fa908087bd47572da6ad9a5a7bf973608f736536d18d635a7da0140 -X GET > ./fixtures/bktraces.json
 | 
			
		||||
const bktracesFixture = "./fixtures/bktraces.json"
 | 
			
		||||
 | 
			
		||||
const otlpFixture = "./fixtures/otlp.json"
 | 
			
		||||
 | 
			
		||||
func TestParseSpanStubs(t *testing.T) {
 | 
			
		||||
	dt, err := os.ReadFile(bktracesFixture)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	spanStubs, err := ParseSpanStubs(bytes.NewReader(dt))
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Equal(t, 73, len(spanStubs))
 | 
			
		||||
 | 
			
		||||
	dtSpanStubs, err := json.MarshalIndent(spanStubs, "", "  ")
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	dtotel, err := os.ReadFile(otlpFixture)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Equal(t, string(dtotel), string(dtSpanStubs))
 | 
			
		||||
 | 
			
		||||
	exp, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.NoError(t, exp.ExportSpans(context.Background(), spanStubs.ReadOnlySpans()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAsAttributeKeyValue(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		Type  string
 | 
			
		||||
		value any
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want attribute.KeyValue
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "string",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.STRING.String(),
 | 
			
		||||
				value: "value",
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.String("key", "value"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "int64 (int64)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.INT64.String(),
 | 
			
		||||
				value: int64(1),
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Int64("key", 1),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "int64 (float64)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.INT64.String(),
 | 
			
		||||
				value: float64(1.0),
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Int64("key", 1),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "bool",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.BOOL.String(),
 | 
			
		||||
				value: true,
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Bool("key", true),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "float64",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.FLOAT64.String(),
 | 
			
		||||
				value: float64(1.0),
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Float64("key", 1.0),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "float64slice",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.FLOAT64SLICE.String(),
 | 
			
		||||
				value: []float64{1.0, 2.0},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Float64Slice("key", []float64{1.0, 2.0}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "int64slice (int64)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.INT64SLICE.String(),
 | 
			
		||||
				value: []int64{1, 2},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Int64Slice("key", []int64{1, 2}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "int64slice (float64)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.INT64SLICE.String(),
 | 
			
		||||
				value: []float64{1.0, 2.0},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.Int64Slice("key", []int64{1, 2}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "boolslice",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.BOOLSLICE.String(),
 | 
			
		||||
				value: []bool{true, false},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.BoolSlice("key", []bool{true, false}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "stringslice (strings)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.STRINGSLICE.String(),
 | 
			
		||||
				value: []string{"value1", "value2"},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.StringSlice("key", []string{"value1", "value2"}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "stringslice (interface of string)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.STRINGSLICE.String(),
 | 
			
		||||
				value: []interface{}{"value1", "value2"},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.StringSlice("key", []string{"value1", "value2"}),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "stringslice (interface mixed)",
 | 
			
		||||
			args: args{
 | 
			
		||||
				Type:  attribute.STRINGSLICE.String(),
 | 
			
		||||
				value: []interface{}{"value1", 2},
 | 
			
		||||
			},
 | 
			
		||||
			want: attribute.StringSlice("key", []string{"value1", "2"}),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt := tt
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			kv := keyValue{
 | 
			
		||||
				Key:   "key",
 | 
			
		||||
				Value: value{Type: tt.args.Type, Value: tt.args.value},
 | 
			
		||||
			}
 | 
			
		||||
			attr, err := kv.asAttributeKeyValue()
 | 
			
		||||
			require.NoError(t, err, "failed to convert key value to attribute key value")
 | 
			
		||||
			assert.Equal(t, tt.want, attr, "attribute key value mismatch")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/Dockerfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/Dockerfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
ARG NODE_VERSION=23.6
 | 
			
		||||
ARG ALPINE_VERSION=3.21
 | 
			
		||||
ARG GOLANG_VERSION=1.23
 | 
			
		||||
ARG JAEGERUI_VERSION=v1.66.0
 | 
			
		||||
 | 
			
		||||
FROM scratch AS jaegerui-src
 | 
			
		||||
ARG JAEGERUI_REPO=https://github.com/jaegertracing/jaeger-ui.git
 | 
			
		||||
ARG JAEGERUI_VERSION
 | 
			
		||||
ADD ${JAEGERUI_REPO}#${JAEGERUI_VERSION} /
 | 
			
		||||
 | 
			
		||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS builder
 | 
			
		||||
WORKDIR /work/jaeger-ui
 | 
			
		||||
COPY --from=jaegerui-src / .
 | 
			
		||||
RUN npm install
 | 
			
		||||
WORKDIR /work/jaeger-ui/packages/jaeger-ui
 | 
			
		||||
RUN NODE_ENVIRONMENT=production npm run build
 | 
			
		||||
# failed to find a way to avoid legacy build
 | 
			
		||||
RUN rm build/static/*-legacy* && rm build/static/*.png
 | 
			
		||||
 | 
			
		||||
FROM scratch AS jaegerui
 | 
			
		||||
COPY --from=builder /work/jaeger-ui/packages/jaeger-ui/build /
 | 
			
		||||
 | 
			
		||||
FROM alpine AS compressor
 | 
			
		||||
RUN --mount=target=/in,from=jaegerui <<EOT
 | 
			
		||||
    set -ex
 | 
			
		||||
    mkdir -p /out
 | 
			
		||||
    cp -a /in/. /out
 | 
			
		||||
    cd /out
 | 
			
		||||
    find . -type f -exec sh -c 'gzip -9 -c "$1" > "$1.tmp" && mv "$1.tmp" "$1"' _ {} \;
 | 
			
		||||
    # stop
 | 
			
		||||
EOT
 | 
			
		||||
 | 
			
		||||
FROM scratch AS jaegerui-compressed
 | 
			
		||||
COPY --from=compressor /out /
 | 
			
		||||
							
								
								
									
										42
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package jaegerui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"slices"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Menu struct {
 | 
			
		||||
	Label string     `json:"label"`
 | 
			
		||||
	Items []MenuItem `json:"items"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MenuItem struct {
 | 
			
		||||
	Label string `json:"label"`
 | 
			
		||||
	URL   string `json:"url"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Dependencies struct {
 | 
			
		||||
		MenuEnabled bool `json:"menuEnabled"`
 | 
			
		||||
	} `json:"dependencies"`
 | 
			
		||||
	Monitor struct {
 | 
			
		||||
		MenuEnabled bool `json:"menuEnabled"`
 | 
			
		||||
	} `json:"monitor"`
 | 
			
		||||
	ArchiveEnabled bool   `json:"archiveEnabled"`
 | 
			
		||||
	Menu           []Menu `json:"menu"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cfg Config) Inject(name string, dt []byte) ([]byte, bool) {
 | 
			
		||||
	if name != "index.html" {
 | 
			
		||||
		return dt, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfgData, err := json.Marshal(cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return dt, false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dt = bytes.Replace(dt, []byte("const JAEGER_CONFIG = DEFAULT_CONFIG;"), slices.Concat([]byte(`const JAEGER_CONFIG = `), cfgData, []byte(`;`)), 1)
 | 
			
		||||
	return dt, true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/decompress/decompress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/decompress/decompress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
package decompress
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type decompressFS struct {
 | 
			
		||||
	fs.FS
 | 
			
		||||
	mu     sync.Mutex
 | 
			
		||||
	data   map[string][]byte
 | 
			
		||||
	inject Injector
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Injector interface {
 | 
			
		||||
	Inject(name string, dt []byte) ([]byte, bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFS(fsys fs.FS, injector Injector) fs.FS {
 | 
			
		||||
	return &decompressFS{
 | 
			
		||||
		FS:     fsys,
 | 
			
		||||
		data:   make(map[string][]byte),
 | 
			
		||||
		inject: injector,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *decompressFS) Open(name string) (fs.File, error) {
 | 
			
		||||
	name = filepath.Clean(name)
 | 
			
		||||
 | 
			
		||||
	f, err := d.FS.Open(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.mu.Lock()
 | 
			
		||||
	defer d.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	dt, ok := d.data[name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		return &staticFile{
 | 
			
		||||
			Reader: bytes.NewReader(dt),
 | 
			
		||||
			f:      f,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fi, err := f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		f.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fi.IsDir() {
 | 
			
		||||
		return f, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gzReader, err := gzip.NewReader(f)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		f.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	if _, err := io.Copy(buf, gzReader); err != nil {
 | 
			
		||||
		f.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dt = buf.Bytes()
 | 
			
		||||
	if d.inject != nil {
 | 
			
		||||
		newdt, ok := d.inject.Inject(name, dt)
 | 
			
		||||
		if ok {
 | 
			
		||||
			dt = newdt
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.data[name] = dt
 | 
			
		||||
 | 
			
		||||
	return &staticFile{
 | 
			
		||||
		Reader: bytes.NewReader(dt),
 | 
			
		||||
		f:      f,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type staticFile struct {
 | 
			
		||||
	*bytes.Reader
 | 
			
		||||
	f fs.File
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *staticFile) Stat() (fs.FileInfo, error) {
 | 
			
		||||
	fi, err := s.f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &fileInfo{
 | 
			
		||||
		FileInfo: fi,
 | 
			
		||||
		size:     int64(s.Len()),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *staticFile) Close() error {
 | 
			
		||||
	return s.f.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type fileInfo struct {
 | 
			
		||||
	fs.FileInfo
 | 
			
		||||
	size int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fileInfo) Size() int64 {
 | 
			
		||||
	return f.size
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ fs.File = &staticFile{}
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/docker-bake.hcl
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/docker-bake.hcl
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
target "public" {
 | 
			
		||||
    output = ["./public"]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/fs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/fs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package jaegerui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/tonistiigi/jaeger-ui-rest/decompress"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//go:embed public
 | 
			
		||||
var staticFiles embed.FS
 | 
			
		||||
 | 
			
		||||
func FS(cfg Config) http.FileSystem {
 | 
			
		||||
	files, _ := fs.Sub(staticFiles, "public")
 | 
			
		||||
	return http.FS(decompress.NewFS(files, cfg))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/index.html
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/index.html
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/favicon-BxcVf0am.ico
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/favicon-BxcVf0am.ico
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-BBZlPGVK.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-BBZlPGVK.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-C4KU8NTf.css
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/index-C4KU8NTf.css
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/jaeger-logo-CNZsoUdk.svg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/public/static/jaeger-logo-CNZsoUdk.svg
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										3
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/readme.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/readme.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
### jaeger-ui-rest
 | 
			
		||||
 | 
			
		||||
[Jaeger UI](https://github.com/jaegertracing/jaeger-ui) server with only the UI component and simple REST API for loading pregenerated JSON traces.
 | 
			
		||||
							
								
								
									
										172
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								vendor/github.com/tonistiigi/jaeger-ui-rest/server.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
			
		||||
package jaegerui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewServer(cfg Config) *Server {
 | 
			
		||||
	mux := &http.ServeMux{}
 | 
			
		||||
	s := &Server{
 | 
			
		||||
		config: cfg,
 | 
			
		||||
		server: &http.Server{
 | 
			
		||||
			Handler: mux,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fsHandler := http.FileServer(FS(cfg))
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("GET /api/services", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		w.Write([]byte(`{"data": [], "total": 0}`))
 | 
			
		||||
	})
 | 
			
		||||
	mux.HandleFunc("GET /trace/", redirectRoot(fsHandler))
 | 
			
		||||
	mux.HandleFunc("GET /search", redirectRoot(fsHandler))
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("POST /api/traces/", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		traceID := strings.TrimPrefix(r.URL.Path, "/api/traces/")
 | 
			
		||||
		if traceID == "" || strings.Contains(traceID, "/") {
 | 
			
		||||
			http.Error(w, "Invalid trace ID", http.StatusBadRequest)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		handleHTTPError(s.AddTrace(traceID, r.Body), w)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.HandleFunc("GET /api/traces/", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		traceID := strings.TrimPrefix(r.URL.Path, "/api/traces/")
 | 
			
		||||
		if traceID == "" {
 | 
			
		||||
			qry := r.URL.Query()
 | 
			
		||||
			ids := qry["traceID"]
 | 
			
		||||
			if len(ids) > 0 {
 | 
			
		||||
				dt, err := s.GetTraces(ids...)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					handleHTTPError(err, w)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				w.Write(dt)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if traceID == "" || strings.Contains(traceID, "/") {
 | 
			
		||||
			http.Error(w, "Invalid trace ID", http.StatusBadRequest)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		dt, err := s.GetTraces(traceID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			handleHTTPError(err, w)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		w.Write(dt)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	mux.Handle("/", fsHandler)
 | 
			
		||||
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Server struct {
 | 
			
		||||
	config Config
 | 
			
		||||
	server *http.Server
 | 
			
		||||
 | 
			
		||||
	mu     sync.Mutex
 | 
			
		||||
	traces map[string][]byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) AddTrace(traceID string, rdr io.Reader) error {
 | 
			
		||||
	var payload struct {
 | 
			
		||||
		Data []struct {
 | 
			
		||||
			TraceID string `json:"traceID"`
 | 
			
		||||
		} `json:"data"`
 | 
			
		||||
	}
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	if _, err := io.Copy(buf, rdr); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to read trace data")
 | 
			
		||||
	}
 | 
			
		||||
	dt := buf.Bytes()
 | 
			
		||||
 | 
			
		||||
	if err := json.Unmarshal(dt, &payload); err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "failed to unmarshal trace data")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(payload.Data) != 1 {
 | 
			
		||||
		return errors.Errorf("expected 1 trace, got %d", len(payload.Data))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if payload.Data[0].TraceID != traceID {
 | 
			
		||||
		return errors.Errorf("trace ID mismatch: %s != %s", payload.Data[0].TraceID, traceID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	if s.traces == nil {
 | 
			
		||||
		s.traces = make(map[string][]byte)
 | 
			
		||||
	}
 | 
			
		||||
	s.traces[traceID] = dt
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) GetTraces(traceIDs ...string) ([]byte, error) {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if len(traceIDs) == 0 {
 | 
			
		||||
		return nil, errors.Errorf("trace ID is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(traceIDs) == 1 {
 | 
			
		||||
		dt, ok := s.traces[traceIDs[0]]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, errors.Wrapf(os.ErrNotExist, "trace %s not found", traceIDs[0])
 | 
			
		||||
		}
 | 
			
		||||
		return dt, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type payloadT struct {
 | 
			
		||||
		Data []interface{} `json:"data"`
 | 
			
		||||
	}
 | 
			
		||||
	var payload payloadT
 | 
			
		||||
 | 
			
		||||
	for _, traceID := range traceIDs {
 | 
			
		||||
		dt, ok := s.traces[traceID]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, errors.Wrapf(os.ErrNotExist, "trace %s not found", traceID)
 | 
			
		||||
		}
 | 
			
		||||
		var p payloadT
 | 
			
		||||
		if err := json.Unmarshal(dt, &p); err != nil {
 | 
			
		||||
			return nil, errors.Wrapf(err, "failed to unmarshal trace data")
 | 
			
		||||
		}
 | 
			
		||||
		payload.Data = append(payload.Data, p.Data...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return json.MarshalIndent(payload, "", "  ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *Server) Serve(l net.Listener) error {
 | 
			
		||||
	return s.server.Serve(l)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redirectRoot(h http.Handler) func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		r.URL.Path = "/"
 | 
			
		||||
		h.ServeHTTP(w, r)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleHTTPError(err error, w http.ResponseWriter) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case err == nil:
 | 
			
		||||
		return
 | 
			
		||||
	case errors.Is(err, os.ErrNotExist):
 | 
			
		||||
		http.Error(w, err.Error(), http.StatusNotFound)
 | 
			
		||||
	default:
 | 
			
		||||
		http.Error(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										201
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
                                 Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
   1. Definitions.
 | 
			
		||||
 | 
			
		||||
      "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
      and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
      "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
      the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
      "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
      other entities that control, are controlled by, or are under common
 | 
			
		||||
      control with that entity. For the purposes of this definition,
 | 
			
		||||
      "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
      direction or management of such entity, whether by contract or
 | 
			
		||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
      "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
      exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
      "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
      including but not limited to software source code, documentation
 | 
			
		||||
      source, and configuration files.
 | 
			
		||||
 | 
			
		||||
      "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
      transformation or translation of a Source form, including but
 | 
			
		||||
      not limited to compiled object code, generated documentation,
 | 
			
		||||
      and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
      "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
      Object form, made available under the License, as indicated by a
 | 
			
		||||
      copyright notice that is included in or attached to the work
 | 
			
		||||
      (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
      "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
      form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
      editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
      represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
      of this License, Derivative Works shall not include works that remain
 | 
			
		||||
      separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
      the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
      "Contribution" shall mean any work of authorship, including
 | 
			
		||||
      the original version of the Work and any modifications or additions
 | 
			
		||||
      to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
      or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
      the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
      means any form of electronic, verbal, or written communication sent
 | 
			
		||||
      to the Licensor or its representatives, including but not limited to
 | 
			
		||||
      communication on electronic mailing lists, source code control systems,
 | 
			
		||||
      and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
      Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
      excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
      designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
      on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
      subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
      publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
      Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
   3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      (except as stated in this section) patent license to make, have made,
 | 
			
		||||
      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
      where such license applies only to those patent claims licensable
 | 
			
		||||
      by such Contributor that are necessarily infringed by their
 | 
			
		||||
      Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
      with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
      institute patent litigation against any entity (including a
 | 
			
		||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
      or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
      or contributory patent infringement, then any patent licenses
 | 
			
		||||
      granted to You under this License for that Work shall terminate
 | 
			
		||||
      as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
   4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
      Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
      modifications, and in Source or Object form, provided that You
 | 
			
		||||
      meet the following conditions:
 | 
			
		||||
 | 
			
		||||
      (a) You must give any other recipients of the Work or
 | 
			
		||||
          Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
      (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
          stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
      (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
          that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
          attribution notices from the Source form of the Work,
 | 
			
		||||
          excluding those notices that do not pertain to any part of
 | 
			
		||||
          the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
      (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
          distribution, then any Derivative Works that You distribute must
 | 
			
		||||
          include a readable copy of the attribution notices contained
 | 
			
		||||
          within such NOTICE file, excluding those notices that do not
 | 
			
		||||
          pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
          of the following places: within a NOTICE text file distributed
 | 
			
		||||
          as part of the Derivative Works; within the Source form or
 | 
			
		||||
          documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
          within a display generated by the Derivative Works, if and
 | 
			
		||||
          wherever such third-party notices normally appear. The contents
 | 
			
		||||
          of the NOTICE file are for informational purposes only and
 | 
			
		||||
          do not modify the License. You may add Your own attribution
 | 
			
		||||
          notices within Derivative Works that You distribute, alongside
 | 
			
		||||
          or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
          that such additional attribution notices cannot be construed
 | 
			
		||||
          as modifying the License.
 | 
			
		||||
 | 
			
		||||
      You may add Your own copyright statement to Your modifications and
 | 
			
		||||
      may provide additional or different license terms and conditions
 | 
			
		||||
      for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
      for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
      reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
      the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
      any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
      by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
      this License, without any additional terms or conditions.
 | 
			
		||||
      Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
      the terms of any separate license agreement you may have executed
 | 
			
		||||
      with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
   6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
      names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
      except as required for reasonable and customary use in describing the
 | 
			
		||||
      origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
      agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
      implied, including, without limitation, any warranties or conditions
 | 
			
		||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
      appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
      risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
   8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
      whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
      unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
      negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
      liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
      incidental, or consequential damages of any character arising as a
 | 
			
		||||
      result of this License or out of the use or inability to use the
 | 
			
		||||
      Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
      work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
      other commercial damages or losses), even if such Contributor
 | 
			
		||||
      has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
   9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
      the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
      or other liability obligations and/or rights consistent with this
 | 
			
		||||
      License. However, in accepting such obligations, You may act only
 | 
			
		||||
      on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
      of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
      defend, and hold each Contributor harmless for any liability
 | 
			
		||||
      incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
      of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
   END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
   APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
      To apply the Apache License to your work, attach the following
 | 
			
		||||
      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
      replaced with your own identifying information. (Don't include
 | 
			
		||||
      the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
      comment syntax for the file format. We also recommend that a
 | 
			
		||||
      file or class name and description of purpose be included on the
 | 
			
		||||
      same "printed page" as the copyright notice for easier
 | 
			
		||||
      identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
   Copyright [yyyy] [name of copyright owner]
 | 
			
		||||
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# STDOUT Trace Exporter
 | 
			
		||||
 | 
			
		||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/stdout/stdouttrace)
 | 
			
		||||
							
								
								
									
										85
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
// Copyright The OpenTelemetry Authors
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	defaultWriter      = os.Stdout
 | 
			
		||||
	defaultPrettyPrint = false
 | 
			
		||||
	defaultTimestamps  = true
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// config contains options for the STDOUT exporter.
 | 
			
		||||
type config struct {
 | 
			
		||||
	// Writer is the destination.  If not set, os.Stdout is used.
 | 
			
		||||
	Writer io.Writer
 | 
			
		||||
 | 
			
		||||
	// PrettyPrint will encode the output into readable JSON. Default is
 | 
			
		||||
	// false.
 | 
			
		||||
	PrettyPrint bool
 | 
			
		||||
 | 
			
		||||
	// Timestamps specifies if timestamps should be printed. Default is
 | 
			
		||||
	// true.
 | 
			
		||||
	Timestamps bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newConfig creates a validated Config configured with options.
 | 
			
		||||
func newConfig(options ...Option) config {
 | 
			
		||||
	cfg := config{
 | 
			
		||||
		Writer:      defaultWriter,
 | 
			
		||||
		PrettyPrint: defaultPrettyPrint,
 | 
			
		||||
		Timestamps:  defaultTimestamps,
 | 
			
		||||
	}
 | 
			
		||||
	for _, opt := range options {
 | 
			
		||||
		cfg = opt.apply(cfg)
 | 
			
		||||
	}
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Option sets the value of an option for a Config.
 | 
			
		||||
type Option interface {
 | 
			
		||||
	apply(config) config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithWriter sets the export stream destination.
 | 
			
		||||
func WithWriter(w io.Writer) Option {
 | 
			
		||||
	return writerOption{w}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type writerOption struct {
 | 
			
		||||
	W io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o writerOption) apply(cfg config) config {
 | 
			
		||||
	cfg.Writer = o.W
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithPrettyPrint prettifies the emitted output.
 | 
			
		||||
func WithPrettyPrint() Option {
 | 
			
		||||
	return prettyPrintOption(true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type prettyPrintOption bool
 | 
			
		||||
 | 
			
		||||
func (o prettyPrintOption) apply(cfg config) config {
 | 
			
		||||
	cfg.PrettyPrint = bool(o)
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithoutTimestamps sets the export stream to not include timestamps.
 | 
			
		||||
func WithoutTimestamps() Option {
 | 
			
		||||
	return timestampsOption(false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type timestampsOption bool
 | 
			
		||||
 | 
			
		||||
func (o timestampsOption) apply(cfg config) config {
 | 
			
		||||
	cfg.Timestamps = bool(o)
 | 
			
		||||
	return cfg
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
// Copyright The OpenTelemetry Authors
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
// Package stdouttrace contains an OpenTelemetry exporter for tracing
 | 
			
		||||
// telemetry to be written to an output destination as JSON.
 | 
			
		||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
 | 
			
		||||
							
								
								
									
										103
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/trace.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/go.opentelemetry.io/otel/exporters/stdout/stdouttrace/trace.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
// Copyright The OpenTelemetry Authors
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/trace"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/trace/tracetest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var zeroTime time.Time
 | 
			
		||||
 | 
			
		||||
var _ trace.SpanExporter = &Exporter{}
 | 
			
		||||
 | 
			
		||||
// New creates an Exporter with the passed options.
 | 
			
		||||
func New(options ...Option) (*Exporter, error) {
 | 
			
		||||
	cfg := newConfig(options...)
 | 
			
		||||
 | 
			
		||||
	enc := json.NewEncoder(cfg.Writer)
 | 
			
		||||
	if cfg.PrettyPrint {
 | 
			
		||||
		enc.SetIndent("", "\t")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Exporter{
 | 
			
		||||
		encoder:    enc,
 | 
			
		||||
		timestamps: cfg.Timestamps,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
 | 
			
		||||
type Exporter struct {
 | 
			
		||||
	encoder    *json.Encoder
 | 
			
		||||
	encoderMu  sync.Mutex
 | 
			
		||||
	timestamps bool
 | 
			
		||||
 | 
			
		||||
	stoppedMu sync.RWMutex
 | 
			
		||||
	stopped   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExportSpans writes spans in json format to stdout.
 | 
			
		||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
 | 
			
		||||
	if err := ctx.Err(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	e.stoppedMu.RLock()
 | 
			
		||||
	stopped := e.stopped
 | 
			
		||||
	e.stoppedMu.RUnlock()
 | 
			
		||||
	if stopped {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(spans) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
 | 
			
		||||
 | 
			
		||||
	e.encoderMu.Lock()
 | 
			
		||||
	defer e.encoderMu.Unlock()
 | 
			
		||||
	for i := range stubs {
 | 
			
		||||
		stub := &stubs[i]
 | 
			
		||||
		// Remove timestamps
 | 
			
		||||
		if !e.timestamps {
 | 
			
		||||
			stub.StartTime = zeroTime
 | 
			
		||||
			stub.EndTime = zeroTime
 | 
			
		||||
			for j := range stub.Events {
 | 
			
		||||
				ev := &stub.Events[j]
 | 
			
		||||
				ev.Time = zeroTime
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Encode span stubs, one by one
 | 
			
		||||
		if err := e.encoder.Encode(stub); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Shutdown is called to stop the exporter, it performs no action.
 | 
			
		||||
func (e *Exporter) Shutdown(ctx context.Context) error {
 | 
			
		||||
	e.stoppedMu.Lock()
 | 
			
		||||
	e.stopped = true
 | 
			
		||||
	e.stoppedMu.Unlock()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
 | 
			
		||||
func (e *Exporter) MarshalLog() interface{} {
 | 
			
		||||
	return struct {
 | 
			
		||||
		Type           string
 | 
			
		||||
		WithTimestamps bool
 | 
			
		||||
	}{
 | 
			
		||||
		Type:           "stdout",
 | 
			
		||||
		WithTimestamps: e.timestamps,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -737,6 +737,10 @@ github.com/tonistiigi/fsutil/types
 | 
			
		||||
# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
			
		||||
## explicit; go 1.16
 | 
			
		||||
github.com/tonistiigi/go-csvvalue
 | 
			
		||||
# github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250211190051-7d4944a45bb6
 | 
			
		||||
## explicit; go 1.22.0
 | 
			
		||||
github.com/tonistiigi/jaeger-ui-rest
 | 
			
		||||
github.com/tonistiigi/jaeger-ui-rest/decompress
 | 
			
		||||
# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
 | 
			
		||||
## explicit
 | 
			
		||||
github.com/tonistiigi/units
 | 
			
		||||
@@ -828,6 +832,9 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig
 | 
			
		||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/retry
 | 
			
		||||
# go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.31.0
 | 
			
		||||
## explicit; go 1.22
 | 
			
		||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace
 | 
			
		||||
# go.opentelemetry.io/otel/metric v1.31.0
 | 
			
		||||
## explicit; go 1.22
 | 
			
		||||
go.opentelemetry.io/otel/metric
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user