diff --git a/.gitignore b/.gitignore
index 7411f51fd4a..82fc31613e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,4 +45,3 @@ go.work.sum
# merged from dagger/examples repository
**/node_modules
-**/env
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000000..a6d49dfbae0
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "telemetry/opentelemetry-proto"]
+ path = telemetry/opentelemetry-proto
+ url = https://github.com/open-telemetry/opentelemetry-proto
diff --git a/analytics/analytics.go b/analytics/analytics.go
index 3f4d94404f2..2a7d8247f67 100644
--- a/analytics/analytics.go
+++ b/analytics/analytics.go
@@ -8,14 +8,14 @@ import (
"encoding/json"
"fmt"
"io"
+ "log/slog"
"net/http"
"os"
"sync"
"time"
- "github.com/dagger/dagger/core/pipeline"
"github.com/dagger/dagger/engine"
- "github.com/vito/progrock"
+ "github.com/dagger/dagger/telemetry"
)
const (
@@ -78,30 +78,20 @@ func DoNotTrack() bool {
type Config struct {
DoNotTrack bool
- Labels pipeline.Labels
+ Labels telemetry.Labels
CloudToken string
}
-func DefaultConfig() Config {
+func DefaultConfig(labels telemetry.Labels) Config {
cfg := Config{
DoNotTrack: DoNotTrack(),
CloudToken: os.Getenv("DAGGER_CLOUD_TOKEN"),
+ Labels: labels,
}
// Backward compatibility with the old environment variable.
if cfg.CloudToken == "" {
cfg.CloudToken = os.Getenv("_EXPERIMENTAL_DAGGER_CLOUD_TOKEN")
}
-
- workdir, err := os.Getwd()
- if err != nil {
- fmt.Fprintf(os.Stderr, "failed to get cwd: %v\n", err)
- return cfg
- }
-
- cfg.Labels.AppendCILabel()
- cfg.Labels = append(cfg.Labels, pipeline.LoadVCSLabels(workdir)...)
- cfg.Labels = append(cfg.Labels, pipeline.LoadClientLabels(engine.Version)...)
-
return cfg
}
@@ -111,8 +101,7 @@ type queuedEvent struct {
}
type CloudTracker struct {
- cfg Config
- labels map[string]string
+ cfg Config
closed bool
mu sync.Mutex
@@ -128,15 +117,10 @@ func New(cfg Config) Tracker {
t := &CloudTracker{
cfg: cfg,
- labels: make(map[string]string),
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
- for _, l := range cfg.Labels {
- t.labels[l.Name] = l.Value
- }
-
go t.start()
return t
@@ -155,19 +139,19 @@ func (t *CloudTracker) Capture(ctx context.Context, event string, properties map
Type: event,
Properties: properties,
- DeviceID: t.labels["dagger.io/client.machine_id"],
+ DeviceID: t.cfg.Labels["dagger.io/client.machine_id"],
- ClientVersion: t.labels["dagger.io/client.version"],
- ClientOS: t.labels["dagger.io/client.os"],
- ClientArch: t.labels["dagger.io/client.arch"],
+ ClientVersion: t.cfg.Labels["dagger.io/client.version"],
+ ClientOS: t.cfg.Labels["dagger.io/client.os"],
+ ClientArch: t.cfg.Labels["dagger.io/client.arch"],
- CI: t.labels["dagger.io/ci"] == "true",
- CIVendor: t.labels["dagger.io/ci.vendor"],
+ CI: t.cfg.Labels["dagger.io/ci"] == "true",
+ CIVendor: t.cfg.Labels["dagger.io/ci.vendor"],
}
- if remote := t.labels["dagger.io/git.remote"]; remote != "" {
+ if remote := t.cfg.Labels["dagger.io/git.remote"]; remote != "" {
ev.GitRemoteEncoded = fmt.Sprintf("%x", base64.StdEncoding.EncodeToString([]byte(remote)))
}
- if author := t.labels["dagger.io/git.author.email"]; author != "" {
+ if author := t.cfg.Labels["dagger.io/git.author.email"]; author != "" {
ev.GitAuthorHashed = fmt.Sprintf("%x", sha256.Sum256([]byte(author)))
}
if clientMetadata, err := engine.ClientMetadataFromContext(ctx); err == nil {
@@ -203,21 +187,19 @@ func (t *CloudTracker) send() {
}
// grab the progrock recorder from the last event in the queue
- rec := progrock.FromContext(queue[len(queue)-1].ctx)
-
payload := bytes.NewBuffer([]byte{})
enc := json.NewEncoder(payload)
for _, q := range queue {
err := enc.Encode(q.event)
if err != nil {
- rec.Debug("analytics: encode failed", progrock.ErrorLabel(err))
+ slog.Debug("analytics: encode failed", "error", err)
continue
}
}
req, err := http.NewRequest(http.MethodPost, trackURL, bytes.NewReader(payload.Bytes()))
if err != nil {
- rec.Debug("analytics: new request failed", progrock.ErrorLabel(err))
+ slog.Debug("analytics: new request failed", "error", err)
return
}
if t.cfg.CloudToken != "" {
@@ -225,12 +207,12 @@ func (t *CloudTracker) send() {
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
- rec.Debug("analytics: do request failed", progrock.ErrorLabel(err))
+ slog.Debug("analytics: do request failed", "error", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
- rec.Debug("analytics: unexpected response", progrock.Labelf("status", resp.Status))
+ slog.Debug("analytics: unexpected response", "status", resp.Status)
}
}
diff --git a/cmd/codegen/codegen.go b/cmd/codegen/codegen.go
index 5b87c9f8a3b..144b1106155 100644
--- a/cmd/codegen/codegen.go
+++ b/cmd/codegen/codegen.go
@@ -4,10 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
+ "os"
"strings"
- "time"
-
- "github.com/vito/progrock"
"dagger.io/dagger"
"github.com/dagger/dagger/cmd/codegen/generator"
@@ -17,18 +15,14 @@ import (
)
func Generate(ctx context.Context, cfg generator.Config, dag *dagger.Client) (err error) {
- var vtxName string
+ logsW := os.Stdout
+
if cfg.ModuleName != "" {
- vtxName = fmt.Sprintf("generating %s module: %s", cfg.Lang, cfg.ModuleName)
+ fmt.Fprintf(logsW, "generating %s module: %s\n", cfg.Lang, cfg.ModuleName)
} else {
- vtxName = fmt.Sprintf("generating %s SDK client", cfg.Lang)
+ fmt.Fprintf(logsW, "generating %s SDK client\n", cfg.Lang)
}
- ctx, vtx := progrock.Span(ctx, time.Now().String(), vtxName)
- defer func() { vtx.Done(err) }()
-
- logsW := vtx.Stdout()
-
var introspectionSchema *introspection.Schema
if cfg.IntrospectionJSON != "" {
var resp introspection.Response
@@ -55,12 +49,12 @@ func Generate(ctx context.Context, cfg generator.Config, dag *dagger.Client) (er
for _, cmd := range generated.PostCommands {
cmd.Dir = cfg.OutputDir
- cmd.Stdout = vtx.Stdout()
- cmd.Stderr = vtx.Stderr()
- task := vtx.Task(strings.Join(cmd.Args, " "))
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ fmt.Fprintln(logsW, "running post-command:", strings.Join(cmd.Args, " "))
err := cmd.Run()
- task.Done(err)
if err != nil {
+ fmt.Fprintln(logsW, "post-command failed:", err)
return err
}
}
diff --git a/cmd/codegen/generator/go/generator.go b/cmd/codegen/generator/go/generator.go
index 0e43460821b..d215a286289 100644
--- a/cmd/codegen/generator/go/generator.go
+++ b/cmd/codegen/generator/go/generator.go
@@ -56,7 +56,11 @@ func (g *GoGenerator) Generate(ctx context.Context, schema *introspection.Schema
var overlay fs.FS = mfs
if g.Config.ModuleName != "" {
- overlay = layerfs.New(mfs, &MountedFS{FS: dagger.QueryBuilder, Name: "internal"})
+ overlay = layerfs.New(
+ mfs,
+ &MountedFS{FS: dagger.QueryBuilder, Name: "internal"},
+ &MountedFS{FS: dagger.Telemetry, Name: "internal"},
+ )
}
genSt := &generator.GeneratedState{
diff --git a/cmd/codegen/generator/go/templates/module_interfaces.go b/cmd/codegen/generator/go/templates/module_interfaces.go
index 5807d1d63d5..f283fb16b7d 100644
--- a/cmd/codegen/generator/go/templates/module_interfaces.go
+++ b/cmd/codegen/generator/go/templates/module_interfaces.go
@@ -346,7 +346,7 @@ func (spec *parsedIfaceType) marshalJSONMethodCode() *Statement {
BlockFunc(func(g *Group) {
g.If(Id("r").Op("==").Nil()).Block(Return(Index().Byte().Parens(Lit(`""`)), Nil()))
- g.List(Id("id"), Id("err")).Op(":=").Id("r").Dot("ID").Call(Qual("context", "Background").Call())
+ g.List(Id("id"), Id("err")).Op(":=").Id("r").Dot("ID").Call(Id("marshalCtx"))
g.If(Id("err").Op("!=").Nil()).Block(Return(Nil(), Id("err")))
g.Return(Id("json").Dot("Marshal").Call(Id("id")))
})
diff --git a/cmd/codegen/generator/go/templates/modules.go b/cmd/codegen/generator/go/templates/modules.go
index 555fefb13aa..0b8ad90df1b 100644
--- a/cmd/codegen/generator/go/templates/modules.go
+++ b/cmd/codegen/generator/go/templates/modules.go
@@ -44,7 +44,7 @@ from the Engine, calls the relevant function and returns the result. The generat
on the object+function name, with each case doing json deserialization of the input arguments and calling the actual
Go function.
*/
-func (funcs goTemplateFuncs) moduleMainSrc() (string, error) {
+func (funcs goTemplateFuncs) moduleMainSrc() (string, error) { //nolint: gocyclo
// HACK: the code in this func can be pretty flaky and tricky to debug -
// it's much easier to debug when we actually have stack traces, so we grab
// those on a panic
@@ -93,6 +93,12 @@ func (funcs goTemplateFuncs) moduleMainSrc() (string, error) {
tps := []types.Type{}
for _, obj := range objs {
+ // ignore any private definitions, they may be part of the runtime itself
+ // e.g. marshalCtx
+ if !obj.Exported() {
+ continue
+ }
+
// check if this is the constructor func, save it for later if so
if ok := ps.checkConstructor(obj); ok {
continue
@@ -230,58 +236,86 @@ const (
mainSrc = `func main() {
ctx := context.Background()
+ // Direct slog to the new stderr. This is only for dev time debugging, and
+ // runtime errors/warnings.
+ slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+ Level: slog.LevelWarn,
+ })))
+
+ if err := dispatch(ctx); err != nil {
+ fmt.Println(err.Error())
+ os.Exit(2)
+ }
+}
+
+func dispatch(ctx context.Context) error {
+ ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes(
+ semconv.SchemaURL,
+ semconv.ServiceNameKey.String("dagger-go-sdk"),
+ // TODO version?
+ ))
+ defer telemetry.Close()
+
+ ctx, span := Tracer().Start(ctx, "Go runtime",
+ trace.WithAttributes(
+ // In effect, the following two attributes hide the exec /runtime span.
+ //
+ // Replace the parent span,
+ attribute.Bool("dagger.io/ui.mask", true),
+ // and only show our children.
+ attribute.Bool("dagger.io/ui.passthrough", true),
+ ))
+ defer span.End()
+
+ // A lot of the "work" actually happens when we're marshalling the return
+ // value, which entails getting object IDs, which happens in MarshalJSON,
+ // which has no ctx argument, so we use this lovely global variable.
+ setMarshalContext(ctx)
+
fnCall := dag.CurrentFunctionCall()
parentName, err := fnCall.ParentName(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get parent name: %w", err)
}
fnName, err := fnCall.Name(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get fn name: %w", err)
}
parentJson, err := fnCall.Parent(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get fn parent: %w", err)
}
fnArgs, err := fnCall.InputArgs(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get fn args: %w", err)
}
inputArgs := map[string][]byte{}
for _, fnArg := range fnArgs {
argName, err := fnArg.Name(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get fn arg name: %w", err)
}
argValue, err := fnArg.Value(ctx)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("get fn arg value: %w", err)
}
inputArgs[argName] = []byte(argValue)
}
result, err := invoke(ctx, []byte(parentJson), parentName, fnName, inputArgs)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("invoke: %w", err)
}
resultBytes, err := json.Marshal(result)
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("marshal: %w", err)
}
_, err = fnCall.ReturnValue(ctx, JSON(resultBytes))
if err != nil {
- fmt.Println(err.Error())
- os.Exit(2)
+ return fmt.Errorf("store return value: %w", err)
}
+ return nil
}
`
parentJSONVar = "parentJSON"
diff --git a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/alias.go.tmpl b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/alias.go.tmpl
index e6acec2b70d..dd16e6b6520 100644
--- a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/alias.go.tmpl
+++ b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/alias.go.tmpl
@@ -1,15 +1,33 @@
import (
+ "context"
+ "log/slog"
+
"{{.PackageImport}}/internal/dagger"
+
+ "go.opentelemetry.io/otel/trace"
)
var dag = dagger.Connect()
+func Tracer() trace.Tracer {
+ return otel.Tracer("dagger.io/sdk.go")
+}
+
+// used for local MarshalJSON implementations
+var marshalCtx = context.Background()
+
+// called by main()
+func setMarshalContext(ctx context.Context) {
+ marshalCtx = ctx
+ dagger.SetMarshalContext(ctx)
+}
+
type DaggerObject = dagger.DaggerObject
type ExecError = dagger.ExecError
{{ range .Types }}
-{{ $name := .Name | FormatName }}
+{{ $name := .Name | FormatName }}
{{ .Description | Comment }}
type {{ $name }} = dagger.{{ $name }}
diff --git a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl
index 832f862dec9..9309d777fb4 100644
--- a/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl
+++ b/cmd/codegen/generator/go/templates/src/_dagger.gen.go/defs.go.tmpl
@@ -1,6 +1,7 @@
import (
"context"
"encoding/json"
+ "log/slog"
"errors"
"fmt"
"net"
@@ -13,14 +14,34 @@ import (
"github.com/Khan/genqlient/graphql"
"github.com/vektah/gqlparser/v2/gqlerror"
+ "go.opentelemetry.io/otel/propagation"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/trace"
{{ if IsModuleCode }}
"{{.PackageImport}}/internal/querybuilder"
+ "{{.PackageImport}}/internal/telemetry"
{{ else }}
"{{.PackageImport}}/querybuilder"
+ "{{.PackageImport}}/telemetry"
{{ end }}
)
+func Tracer() trace.Tracer {
+ return otel.Tracer("dagger.io/sdk.go")
+}
+
+// reassigned at runtime after the span is initialized
+var marshalCtx = context.Background()
+
+{{ if IsModuleCode }}
+// SetMarshalContext is a hack that lets us set the ctx to use for
+// MarshalJSON implementations that get an object's ID.
+func SetMarshalContext(ctx context.Context) {
+ marshalCtx = ctx
+}
+{{ end }}
+
// assertNotNil panic if the given value is nil.
// This function is used to validate that input with pointer type are not nil.
// See https://github.com/dagger/dagger/issues/5696 for more context.
@@ -163,6 +184,13 @@ func getClientParams() (graphql.Client, *querybuilder.Selection) {
httpClient := &http.Client{
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
r.SetBasicAuth(sessionToken, "")
+
+ // detect $TRACEPARENT set by 'dagger run'
+ r = r.WithContext(fallbackSpanContext(r.Context()))
+
+ // propagate span context via headers (i.e. for Dagger-in-Dagger)
+ propagation.TraceContext{}.Inject(r.Context(), propagation.HeaderCarrier(r.Header))
+
return dialTransport.RoundTrip(r)
}),
}
@@ -171,6 +199,17 @@ func getClientParams() (graphql.Client, *querybuilder.Selection) {
return gqlClient, querybuilder.Query()
}
+func fallbackSpanContext(ctx context.Context) context.Context {
+ if trace.SpanContextFromContext(ctx).IsValid() {
+ return ctx
+ }
+ if p, ok := os.LookupEnv("TRACEPARENT"); ok {
+ slog.Debug("falling back to $TRACEPARENT", "value", p)
+ return propagation.TraceContext{}.Extract(ctx, propagation.MapCarrier{"traceparent": p})
+ }
+ return ctx
+}
+
// TODO: pollutes namespace, move to non internal package in dagger.io/dagger
type roundTripperFunc func(*http.Request) (*http.Response, error)
diff --git a/cmd/codegen/generator/go/templates/src/_types/object.go.tmpl b/cmd/codegen/generator/go/templates/src/_types/object.go.tmpl
index f5b0d759c82..aa599b50af9 100644
--- a/cmd/codegen/generator/go/templates/src/_types/object.go.tmpl
+++ b/cmd/codegen/generator/go/templates/src/_types/object.go.tmpl
@@ -175,7 +175,7 @@ func (r *{{ $.Name | FormatName }}) XXX_GraphQLID(ctx context.Context) (string,
}
func (r *{{ $.Name | FormatName }}) MarshalJSON() ([]byte, error) {
- id, err := r.ID(context.Background())
+ id, err := r.ID(marshalCtx)
if err != nil {
return nil, err
}
diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go
index 5b33ab08013..039950034d4 100644
--- a/cmd/codegen/main.go
+++ b/cmd/codegen/main.go
@@ -5,8 +5,6 @@ import (
"os"
"github.com/spf13/cobra"
- "github.com/vito/progrock"
- "github.com/vito/progrock/console"
"dagger.io/dagger"
"github.com/dagger/dagger/cmd/codegen/generator"
@@ -15,7 +13,6 @@ import (
var (
outputDir string
lang string
- propagateLogs bool
introspectionJSONPath string
moduleContextPath string
@@ -35,15 +32,12 @@ var rootCmd = &cobra.Command{
func init() {
rootCmd.Flags().StringVar(&lang, "lang", "go", "language to generate")
rootCmd.Flags().StringVarP(&outputDir, "output", "o", ".", "output directory")
- rootCmd.Flags().BoolVar(&propagateLogs, "propagate-logs", false, "propagate logs directly to progrock.sock")
rootCmd.Flags().StringVar(&introspectionJSONPath, "introspection-json-path", "", "optional path to file containing pre-computed graphql introspection JSON")
rootCmd.Flags().StringVar(&moduleContextPath, "module-context", "", "path to context directory of the module")
rootCmd.Flags().StringVar(&moduleName, "module-name", "", "name of module to generate code for")
}
-const nestedSock = "/.progrock.sock"
-
func ClientGen(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
dag, err := dagger.Connect(ctx, dagger.WithSkipCompatibilityCheck())
@@ -51,28 +45,6 @@ func ClientGen(cmd *cobra.Command, args []string) error {
return err
}
- var progW progrock.Writer
- var dialErr error
- if propagateLogs {
- progW, dialErr = progrock.DialRPC(ctx, "unix://"+nestedSock)
- if dialErr != nil {
- return fmt.Errorf("error connecting to progrock: %w; falling back to console output", dialErr)
- }
- } else {
- progW = console.NewWriter(os.Stderr, console.WithMessageLevel(progrock.MessageLevel_DEBUG))
- }
-
- var rec *progrock.Recorder
- if parent := os.Getenv("_DAGGER_PROGROCK_PARENT"); parent != "" {
- rec = progrock.NewSubRecorder(progW, parent)
- } else {
- rec = progrock.NewRecorder(progW)
- }
- defer rec.Complete()
- defer rec.Close()
-
- ctx = progrock.ToContext(ctx, rec)
-
cfg := generator.Config{
Lang: generator.SDKLang(lang),
diff --git a/cmd/dagger-graph/README.md b/cmd/dagger-graph/README.md
deleted file mode 100644
index bdbfd0954f0..00000000000
--- a/cmd/dagger-graph/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# dagger-graph
-
-**Experimental** tool to generate graphs from a dagger journal file. Built using [D2](https://d2lang.com/tour/intro/).
-
-## Usage
-
-```console
-_EXPERIMENTAL_DAGGER_JOURNAL="./journal.log" go run mycode.go
-dagger-graph ./journal.log ./graph.svg
-```
-
-## Example
-
-
\ No newline at end of file
diff --git a/cmd/dagger-graph/example.svg b/cmd/dagger-graph/example.svg
deleted file mode 100644
index 0f0fb8d27c9..00000000000
--- a/cmd/dagger-graph/example.svg
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
\ No newline at end of file
diff --git a/cmd/dagger-graph/main.go b/cmd/dagger-graph/main.go
deleted file mode 100644
index b02b5e9f683..00000000000
--- a/cmd/dagger-graph/main.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package main
-
-import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- "time"
-
- "github.com/dagger/dagger/telemetry"
- "github.com/vito/progrock"
-
- "oss.terrastruct.com/d2/d2lib"
- "oss.terrastruct.com/d2/d2renderers/d2svg"
- "oss.terrastruct.com/d2/lib/textmeasure"
- "oss.terrastruct.com/util-go/go2"
-)
-
-func main() {
- if len(os.Args) < 3 {
- fmt.Fprintf(os.Stderr, "Usage: %s