diff --git a/data_for_test.go b/data_for_test.go index b81ec35..3600335 100644 --- a/data_for_test.go +++ b/data_for_test.go @@ -586,6 +586,17 @@ var suites = []FixtureSuite{ SpanCount: 1, EventCount: 1, }, + // this validates options sent to otel-cli span end + CheckFuncs: []CheckFunc{ + func(t *testing.T, f Fixture, r Results) { + if r.Span.StatusCode != 2 { + t.Errorf("expected 2 for span status code, but got %d", r.Span.StatusCode) + } + if r.Span.StatusDescription != "I can't do that Dave." { + t.Errorf("got wrong string for status description: %q", r.Span.StatusDescription) + } + }, + }, }, { Name: "otel-cli span event", @@ -597,7 +608,13 @@ var suites = []FixtureSuite{ { Name: "otel-cli span end", Config: FixtureConfig{ - CliArgs: []string{"span", "end", "--sockdir", "."}, + CliArgs: []string{ + "span", "end", + "--sockdir", ".", + // these are validated by checkfuncs defined above ^^ + "--status-code", "error", + "--status-description", "I can't do that Dave.", + }, }, Expect: Results{Config: otelcli.DefaultConfig()}, }, diff --git a/otelcli/protobuf_span.go b/otelcli/protobuf_span.go index c852c68..f3c6961 100644 --- a/otelcli/protobuf_span.go +++ b/otelcli/protobuf_span.go @@ -86,15 +86,21 @@ func NewProtobufSpanWithConfig(c Config) tracepb.Span { span.SpanId = emptySpanId } - // Only set status description when an error status. - // https://github.com/open-telemetry/opentelemetry-specification/blob/480a19d702470563d32a870932be5ddae798079c/specification/trace/api.md#set-status + SetSpanStatus(&span, c) + + return span +} + +// SetSpanStatus checks for status code error in the config and sets the +// span's 2 values as appropriate. +// Only set status description when an error status. +// https://github.com/open-telemetry/opentelemetry-specification/blob/480a19d702470563d32a870932be5ddae798079c/specification/trace/api.md#set-status +func SetSpanStatus(span *tracepb.Span, c Config) { statusCode := SpanStatusStringToInt(c.StatusCode) - if statusCode == tracepb.Status_STATUS_CODE_ERROR { + if statusCode != tracepb.Status_STATUS_CODE_UNSET { span.Status.Code = statusCode span.Status.Message = c.StatusDescription } - - return span } // generateTraceId generates a random 16 byte trace id diff --git a/otelcli/root.go b/otelcli/root.go index c459b99..b6aed0b 100644 --- a/otelcli/root.go +++ b/otelcli/root.go @@ -99,6 +99,13 @@ func addSpanParams(cmd *cobra.Command) { cmd.Flags().StringVarP(&config.ServiceName, "service", "s", defaults.ServiceName, "set the name of the application sent on the traces") // --kind / -k cmd.Flags().StringVarP(&config.Kind, "kind", "k", defaults.Kind, "set the trace kind, e.g. internal, server, client, producer, consumer") + + addSpanStatusParams(cmd) +} + +func addSpanStatusParams(cmd *cobra.Command) { + defaults := DefaultConfig() + // --status-code / -sc cmd.Flags().StringVar(&config.StatusCode, "status-code", defaults.StatusCode, "set the span status code, e.g. unset|ok|error") // --status-description / -sd diff --git a/otelcli/span_background.go b/otelcli/span_background.go index 025651a..de2d078 100644 --- a/otelcli/span_background.go +++ b/otelcli/span_background.go @@ -87,7 +87,7 @@ func doSpanBackground(cmd *cobra.Command, args []string) { // propagation before the server starts, instead of after propagateTraceparent(span, os.Stdout) - bgs := createBgServer(spanBgSockfile(), span) + bgs := createBgServer(spanBgSockfile(), &span) // set up signal handlers to cleanly exit on SIGINT/SIGTERM etc signals := make(chan os.Signal) diff --git a/otelcli/span_background_server.go b/otelcli/span_background_server.go index 96cb05f..d8622d1 100644 --- a/otelcli/span_background_server.go +++ b/otelcli/span_background_server.go @@ -31,7 +31,10 @@ type BgSpanEvent struct { } // BgEnd is an empty struct that can be sent to call End(). -type BgEnd struct{} +type BgEnd struct { + StatusCode string `json:"status_code"` + StatusDesc string `json:"status_description"` +} // AddEvent takes a BgSpanEvent from the client and attaches an event to the span. func (bs BgSpan) AddEvent(bse *BgSpanEvent, reply *BgSpan) error { @@ -63,8 +66,10 @@ func (bs BgSpan) Wait(in, reply *struct{}) error { // End takes a BgEnd (empty) struct, replies with the usual trace info, then // ends the span end exits the background process. func (bs BgSpan) End(in *BgEnd, reply *BgSpan) error { - // TODO: maybe accept an end timestamp? - //endSpan(bs.span) + // handle --status-code and --status-description args to span end + c := config.WithStatusCode(in.StatusCode).WithStatusDescription(in.StatusDesc) + SetSpanStatus(bs.span, c) + // running the shutdown as a goroutine prevents the client from getting an // error here when the server gets closed. defer didn't do the trick. go bs.shutdown() @@ -81,7 +86,7 @@ type bgServer struct { // createBgServer opens a new span background server on a unix socket and // returns with the server ready to go. Not expected to block. -func createBgServer(sockfile string, span tracepb.Span) *bgServer { +func createBgServer(sockfile string, span *tracepb.Span) *bgServer { var err error bgs := bgServer{ @@ -97,7 +102,7 @@ func createBgServer(sockfile string, span tracepb.Span) *bgServer { bgspan := BgSpan{ TraceID: hex.EncodeToString(span.TraceId), SpanID: hex.EncodeToString(span.SpanId), - span: &span, + span: span, shutdown: func() { bgs.Shutdown() }, } // makes methods on BgSpan available over RPC diff --git a/otelcli/span_end.go b/otelcli/span_end.go index 7370908..8dae818 100644 --- a/otelcli/span_end.go +++ b/otelcli/span_end.go @@ -27,12 +27,20 @@ func init() { //spanEndCmd.Flags().StringVar(&config.Timeout, "timeout", defaults.Timeout, "timeout for otel-cli operations, all timeouts in otel-cli use this value") spanEndCmd.Flags().StringVar(&config.BackgroundSockdir, "sockdir", defaults.BackgroundSockdir, "a directory where a socket can be placed safely") spanEndCmd.MarkFlagRequired("sockdir") + + spanEndCmd.Flags().StringVar(&config.SpanEndTime, "end", defaults.SpanEndTime, "an Unix epoch or RFC3339 timestamp for the end of the span") + + addSpanStatusParams(spanEndCmd) } func doSpanEnd(cmd *cobra.Command, args []string) { client, shutdown := createBgClient() - rpcArgs := BgEnd{} + rpcArgs := BgEnd{ + StatusCode: config.StatusCode, + StatusDesc: config.StatusDescription, + } + res := BgSpan{} err := client.Call("BgSpan.End", rpcArgs, &res) if err != nil { diff --git a/otlpserver/clievent.go b/otlpserver/clievent.go index 25ff1b0..1389592 100644 --- a/otlpserver/clievent.go +++ b/otlpserver/clievent.go @@ -25,6 +25,8 @@ type CliEvent struct { ElapsedMs int64 `json:"elapsed_ms"` Attributes map[string]string `json:"attributes"` ServiceAttributes map[string]string `json:"service_attributes"` + StatusCode int32 + StatusDescription string // for a span this is the start nanos, for an event it's just the timestamp // mostly here for sorting CliEventList but could be any uint64 Nanos uint64 `json:"nanos"` @@ -62,6 +64,8 @@ func (ce CliEvent) ToStringMap() map[string]string { "end": etime, "attributes": mapToKVString(ce.Attributes), "service_attributes": mapToKVString(ce.ServiceAttributes), + "status_code": strconv.FormatInt(int64(ce.StatusCode), 10), + "status_description": ce.StatusDescription, "is_populated": strconv.FormatBool(ce.IsPopulated), "server_meta": mapToKVString(ce.ServerMeta), } @@ -88,6 +92,8 @@ func NewCliEventFromSpan(span *v1.Span, scopeSpans *v1.ScopeSpans, rss *v1.Resou Attributes: make(map[string]string), ServiceAttributes: make(map[string]string), Nanos: span.GetStartTimeUnixNano(), + StatusCode: int32(span.GetStatus().Code), + StatusDescription: span.GetStatus().Message, IsPopulated: true, ServerMeta: make(map[string]string), }