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:
 | 
					    env:
 | 
				
			||||||
      SKIP_INTEGRATION_TESTS: 1
 | 
					      SKIP_INTEGRATION_TESTS: 1
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
 | 
					      -
 | 
				
			||||||
 | 
					        name: Setup Git config
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          git config --global core.autocrlf false
 | 
				
			||||||
 | 
					          git config --global core.eol lf
 | 
				
			||||||
      -
 | 
					      -
 | 
				
			||||||
        name: Checkout
 | 
					        name: Checkout
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,7 @@ func RootCmd(rootcmd *cobra.Command, dockerCli command.Cli, opts RootOptions) *c
 | 
				
			|||||||
		logsCmd(dockerCli, opts),
 | 
							logsCmd(dockerCli, opts),
 | 
				
			||||||
		inspectCmd(dockerCli, opts),
 | 
							inspectCmd(dockerCli, opts),
 | 
				
			||||||
		openCmd(dockerCli, opts),
 | 
							openCmd(dockerCli, opts),
 | 
				
			||||||
 | 
							traceCmd(dockerCli, opts),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return cmd
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,13 +5,14 @@ Commands to work on build records
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Subcommands
 | 
					### Subcommands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Name                                   | Description                    |
 | 
					| Name                                   | Description                                    |
 | 
				
			||||||
|:---------------------------------------|:-------------------------------|
 | 
					|:---------------------------------------|:-----------------------------------------------|
 | 
				
			||||||
| [`inspect`](buildx_history_inspect.md) | Inspect a build                |
 | 
					| [`inspect`](buildx_history_inspect.md) | Inspect a build                                |
 | 
				
			||||||
| [`logs`](buildx_history_logs.md)       | Print the logs of a build      |
 | 
					| [`logs`](buildx_history_logs.md)       | Print the logs of a build                      |
 | 
				
			||||||
| [`ls`](buildx_history_ls.md)           | List build records             |
 | 
					| [`ls`](buildx_history_ls.md)           | List build records                             |
 | 
				
			||||||
| [`open`](buildx_history_open.md)       | Open a build in Docker Desktop |
 | 
					| [`open`](buildx_history_open.md)       | Open a build in Docker Desktop                 |
 | 
				
			||||||
| [`rm`](buildx_history_rm.md)           | Remove build records           |
 | 
					| [`rm`](buildx_history_rm.md)           | Remove build records                           |
 | 
				
			||||||
 | 
					| [`trace`](buildx_history_trace.md)     | Show the OpenTelemetry trace of a build record |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Options
 | 
					### 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/stretchr/testify v1.10.0
 | 
				
			||||||
	github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
 | 
						github.com/tonistiigi/fsutil v0.0.0-20250113203817-b14e27f4135a
 | 
				
			||||||
	github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
						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
 | 
						github.com/zclconf/go-cty v1.16.0
 | 
				
			||||||
	go.opentelemetry.io/otel v1.31.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/metric v1.31.0
 | 
				
			||||||
	go.opentelemetry.io/otel/sdk v1.31.0
 | 
						go.opentelemetry.io/otel/sdk v1.31.0
 | 
				
			||||||
	go.opentelemetry.io/otel/trace 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/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 h1:7I5c2Ig/5FgqkYOh/N87NzoyI9U15qUPXhDD8uCupv8=
 | 
				
			||||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
 | 
					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 h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
 | 
				
			||||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
 | 
					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=
 | 
					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/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 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
 | 
				
			||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
 | 
					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 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
 | 
				
			||||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
 | 
					go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
 | 
				
			||||||
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
 | 
					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
 | 
					# github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4
 | 
				
			||||||
## explicit; go 1.16
 | 
					## explicit; go 1.16
 | 
				
			||||||
github.com/tonistiigi/go-csvvalue
 | 
					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
 | 
					# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
 | 
				
			||||||
## explicit
 | 
					## explicit
 | 
				
			||||||
github.com/tonistiigi/units
 | 
					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/envconfig
 | 
				
			||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlpconfig
 | 
					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/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
 | 
					# go.opentelemetry.io/otel/metric v1.31.0
 | 
				
			||||||
## explicit; go 1.22
 | 
					## explicit; go 1.22
 | 
				
			||||||
go.opentelemetry.io/otel/metric
 | 
					go.opentelemetry.io/otel/metric
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user