Skip to content

Commit

Permalink
#81 template traceparent into exec command args (#308)
Browse files Browse the repository at this point in the history
Modifies the default behavior of `otel-cli exec`  so that command-line arguments to the child process are modified to inject the traceparent anywhere `{{traceparent}}` appears in them. This makes patterns like this work:

```shell
export TRACEPARENT=00-5b28ee2bf1f1796cca65bef26fb90c35-9d699b596c3dced3-01
./otel-cli exec --endpoint localhost:4317 -- curl --verbose -H 'traceparent: {{traceparent}}' https://github.com
```

This can be disabled with a new extra argument: `otel-cli exec --tp-disable-inject`.

## Squashed Commits

* add support to template traceparent into exec command args

```
$ go build && TRACEPARENT=00-5b28ee2bf1f1796cca65bef26fb90c35-9d699b596c3dced3-01 ./otel-cli exec --endpoint localhost:4317 -- curl --verbose -H 'traceparent: {{tr
aceparent}}' http://tobert.org
*   Trying 45.33.3.11:80...
* Connected to tobert.org (45.33.3.11) port 80 (#0)
> GET / HTTP/1.1
> Host: tobert.org
> User-Agent: curl/7.81.0
> Accept: */*
> traceparent: 00-5b28ee2bf1f1796cca65bef26fb90c35-433d7e8620e9ccea-01
```

* update CHANGELOG.md

* add an example of injecting traceparent into exec args

* add a test for exec tp injection

* remove unneeded regular expression on cmd output

* add --tp-disable-inject to otel-cli exec along with tests

* group injection tests in a suite

Not a big deal, but makes a touch more sense.
  • Loading branch information
tobert authored Mar 11, 2024
1 parent ec13a7c commit b23efba
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 24 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [0.4.3] - 2024-03-11

Add injection of `{{traceparent}}` to `otel-cli exec` as default behavior, along with
the `otel-cli exec --tp-disable-inject` to turn it off (old behavior).

### Added

- `otel-cli exec echo {{traceparent}}` is now supported to pass traceparent to child process
- `otel-cli exec --tp-disable-inject` will disable this new default behavior

## [0.4.2] - 2023-12-01

The Docker container now builds off `alpine:latest` instead of `scratch`. This
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ otel-cli exec --kind producer "otel-cli exec --kind consumer sleep 1"
# used by span and exec. use --tp-ignore-env to ignore it even when present
export TRACEPARENT=00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

# you can pass the traceparent to a child via arguments as well
# {{traceparent}} in any of the command's arguments will be replaced with the traceparent string
otel-cli exec --name "curl api" -- \
curl -H 'traceparent: {{traceparent}}' https://myapi.com/v1/coolstuff

# create a span with a custom start/end time using either RFC3339,
# same with the nanosecond extension, or Unix epoch, with/without nanos
otel-cli span --start 2021-03-24T07:28:05.12345Z --end 2021-03-24T07:30:08.0001Z
Expand Down
36 changes: 35 additions & 1 deletion data_for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,7 @@ var suites = []FixtureSuite{
// otel-cli exec runs otel-cli exec
{
{
Name: "otel-cli span exec (nested)",
Name: "otel-cli exec (nested)",
Config: FixtureConfig{
CliArgs: []string{
"exec", "--name", "outer", "--endpoint", "{{endpoint}}", "--fail", "--verbose", "--",
Expand All @@ -915,6 +915,40 @@ var suites = []FixtureSuite{
},
},
},
// otel-cli exec echo "{{traceparent}}" and otel-cli exec --tp-disable-inject
{
{
Name: "otel-cli exec with arg injection injects the traceparent",
Config: FixtureConfig{
CliArgs: []string{
"exec", "--endpoint", "{{endpoint}}",
"--force-trace-id", "e39280f2980af3a8600ae98c74f2dabf", "--force-span-id", "023eee2731392b4d",
"--",
"echo", "{{traceparent}}"},
},
Expect: Results{
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
CliOutput: "00-e39280f2980af3a8600ae98c74f2dabf-023eee2731392b4d-01\n",
SpanCount: 1,
},
},
{
Name: "otel-cli exec --tp-disable-inject returns the {{traceparent}} tag unmodified",
Config: FixtureConfig{
CliArgs: []string{
"exec", "--endpoint", "{{endpoint}}",
"--force-trace-id", "e39280f2980af3a8600ae98c74f2dabf", "--force-span-id", "023eee2731392b4d",
"--tp-disable-inject",
"--",
"echo", "{{traceparent}}"},
},
Expect: Results{
Config: otelcli.DefaultConfig().WithEndpoint("{{endpoint}}"),
CliOutput: "{{traceparent}}\n",
SpanCount: 1,
},
},
},
// validate OTEL_EXPORTER_OTLP_PROTOCOL / --protocol
{
// --protocol
Expand Down
5 changes: 4 additions & 1 deletion otelcli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func DefaultConfig() Config {
BackgroundWait: false,
BackgroundSkipParentPidCheck: false,
ExecCommandTimeout: "",
ExecTpDisableInject: false,
StatusCanaryCount: 1,
StatusCanaryInterval: "",
SpanStartTime: "now",
Expand Down Expand Up @@ -109,7 +110,8 @@ type Config struct {
BackgroundWait bool `json:"background_wait" env:""`
BackgroundSkipParentPidCheck bool `json:"background_skip_parent_pid_check"`

ExecCommandTimeout string `json:"exec_command_timeout" env:"OTEL_CLI_EXEC_CMD_TIMEOUT"`
ExecCommandTimeout string `json:"exec_command_timeout" env:"OTEL_CLI_EXEC_CMD_TIMEOUT"`
ExecTpDisableInject bool `json:"exec_tp_disable_inject" env:"OTEL_CLI_EXEC_TP_DISALBE_INJECT"`

StatusCanaryCount int `json:"status_canary_count"`
StatusCanaryInterval string `json:"status_canary_interval"`
Expand Down Expand Up @@ -232,6 +234,7 @@ func (c Config) ToStringMap() map[string]string {
"background_wait": strconv.FormatBool(c.BackgroundWait),
"background_skip_pid_check": strconv.FormatBool(c.BackgroundSkipParentPidCheck),
"exec_command_timeout": c.ExecCommandTimeout,
"exec_tp_disable_inject": strconv.FormatBool(c.ExecTpDisableInject),
"span_start_time": c.SpanStartTime,
"span_end_time": c.SpanEndTime,
"event_name": c.EventName,
Expand Down
66 changes: 44 additions & 22 deletions otelcli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/equinix-labs/otel-cli/otlpclient"
"github.com/equinix-labs/otel-cli/w3c/traceparent"
"github.com/spf13/cobra"
tracev1 "go.opentelemetry.io/proto/otlp/trace/v1"
)
Expand Down Expand Up @@ -47,33 +48,70 @@ otel-cli exec -s "outer span" 'otel-cli exec -s "inner span" sleep 1'`,
"timeout for the child process, when 0 otel-cli will wait forever",
)

cmd.Flags().BoolVar(
&config.ExecTpDisableInject,
"tp-disable-inject",
defaults.ExecTpDisableInject,
"disable automatically replacing {{traceparent}} with a traceparent",
)

return &cmd
}

func doExec(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
config := getConfig(ctx)
span := config.NewProtobufSpan()

// put the command in the attributes, before creating the span so it gets picked up
config.Attributes["command"] = args[0]
config.Attributes["arguments"] = ""

// no deadline if there is no command timeout set
cancelCtxDeadline := func() {}
// fork the context for the command so its deadline doesn't impact the otlpclient ctx
cmdCtx := ctx
cmdTimeout := config.ParseExecCommandTimeout()
if cmdTimeout > 0 {
cmdCtx, cancelCtxDeadline = context.WithDeadline(ctx, time.Now().Add(cmdTimeout))
}

// pass the existing env but add the latest TRACEPARENT carrier so e.g.
// otel-cli exec 'otel-cli exec sleep 1' will relate the spans automatically
childEnv := []string{}

// set the traceparent to the current span to be available to the child process
var tp traceparent.Traceparent
if config.GetIsRecording() {
tp = otlpclient.TraceparentFromProtobufSpan(span, config.GetIsRecording())
childEnv = append(childEnv, fmt.Sprintf("TRACEPARENT=%s", tp.Encode()))
// when not recording, and a traceparent is available, pass it through
} else if !config.TraceparentIgnoreEnv {
tp := config.LoadTraceparent()
if tp.Initialized {
childEnv = append(childEnv, fmt.Sprintf("TRACEPARENT=%s", tp.Encode()))
}
}

var child *exec.Cmd
if len(args) > 1 {
// CSV-join the arguments to send as an attribute
buf := bytes.NewBuffer([]byte{})
csv.NewWriter(buf).WriteAll([][]string{args[1:]})
tpArgs := make([]string, len(args)-1)

if config.ExecTpDisableInject {
copy(tpArgs, args[1:])
} else {
// loop over the args replacing {{traceparent}} with the current tp
for i, arg := range args[1:] {
tpArgs[i] = strings.Replace(arg, "{{traceparent}}", tp.Encode(), -1)
}
}

// CSV-join the arguments to send as an attribute
csv.NewWriter(buf).WriteAll([][]string{tpArgs})
config.Attributes["arguments"] = buf.String()

child = exec.CommandContext(cmdCtx, args[0], args[1:]...)
child = exec.CommandContext(cmdCtx, args[0], tpArgs...)
} else {
child = exec.CommandContext(cmdCtx, args[0])
}
Expand All @@ -83,30 +121,13 @@ func doExec(cmd *cobra.Command, args []string) {
child.Stdout = os.Stdout
child.Stderr = os.Stderr

// pass the existing env but add the latest TRACEPARENT carrier so e.g.
// otel-cli exec 'otel-cli exec sleep 1' will relate the spans automatically
child.Env = []string{}

// grab everything BUT the TRACEPARENT envvar
for _, env := range os.Environ() {
if !strings.HasPrefix(env, "TRACEPARENT=") {
child.Env = append(child.Env, env)
}
}

span := config.NewProtobufSpan()

// set the traceparent to the current span to be available to the child process
if config.GetIsRecording() {
tp := otlpclient.TraceparentFromProtobufSpan(span, config.GetIsRecording())
child.Env = append(child.Env, fmt.Sprintf("TRACEPARENT=%s", tp.Encode()))
// when not recording, and a traceparent is available, pass it through
} else if !config.TraceparentIgnoreEnv {
tp := config.LoadTraceparent()
if tp.Initialized {
child.Env = append(child.Env, fmt.Sprintf("TRACEPARENT=%s", tp.Encode()))
childEnv = append(childEnv, env)
}
}
child.Env = childEnv

// ctrl-c (sigint) is forwarded to the child process
signals := make(chan os.Signal, 10)
Expand All @@ -119,6 +140,7 @@ func doExec(cmd *cobra.Command, args []string) {
close(signalsDone)
}()

span.StartTimeUnixNano = uint64(time.Now().UnixNano())
if err := child.Run(); err != nil {
span.Status = &tracev1.Status{
Message: fmt.Sprintf("exec command failed: %s", err),
Expand Down

0 comments on commit b23efba

Please sign in to comment.