From 69dc01273c1e6168b8d4a4b4fc00209eafcf4cf9 Mon Sep 17 00:00:00 2001 From: Rafael Roquetto Date: Tue, 23 Jul 2024 14:48:18 -0600 Subject: [PATCH] Review feedback --- pkg/beyla/config.go | 12 +- pkg/beyla/config_test.go | 71 +++++++++- pkg/export/debug/debug.go | 199 ++++++++++------------------ pkg/internal/pipe/instrumenter.go | 5 +- pkg/internal/request/span.go | 130 ++++++++----------- pkg/internal/request/span_test.go | 209 +++++++++++++++++++++--------- 6 files changed, 343 insertions(+), 283 deletions(-) diff --git a/pkg/beyla/config.go b/pkg/beyla/config.go index de4f3d70b..f3feaf134 100644 --- a/pkg/beyla/config.go +++ b/pkg/beyla/config.go @@ -95,8 +95,7 @@ var DefaultConfig = Config{ SpanMetricsServiceCacheSize: 10000, }, Printer: false, // Deprecated: use TracePrinter instead - TracePrinter: debug.TracePrinterNone, - Noop: false, + TracePrinter: debug.TracePrinterDisabled, InternalMetrics: imetrics.Config{ Prometheus: imetrics.PrometheusConfig{ Port: 0, // disabled by default @@ -164,10 +163,9 @@ type Config struct { // From this comment, the properties below will remain undocumented, as they // are useful for development purposes. They might be helpful for customer support. - ChannelBufferLen int `yaml:"channel_buffer_len" env:"BEYLA_CHANNEL_BUFFER_LEN"` - Noop debug.NoopEnabled `yaml:"noop" env:"BEYLA_NOOP_TRACES"` - ProfilePort int `yaml:"profile_port" env:"BEYLA_PROFILE_PORT"` - InternalMetrics imetrics.Config `yaml:"internal_metrics"` + ChannelBufferLen int `yaml:"channel_buffer_len" env:"BEYLA_CHANNEL_BUFFER_LEN"` + ProfilePort int `yaml:"profile_port" env:"BEYLA_PROFILE_PORT"` + InternalMetrics imetrics.Config `yaml:"internal_metrics"` // Processes metrics for application. They will be only enabled if there is a metrics exporter enabled, // and both the "application" and "application_process" features are enabled @@ -241,7 +239,7 @@ func (c *Config) Validate() error { c.TracePrinter = debug.TracePrinterText } - if c.Enabled(FeatureAppO11y) && !c.Noop.Enabled() && !c.Printer.Enabled() && + if c.Enabled(FeatureAppO11y) && !c.Printer.Enabled() && !c.Grafana.OTLP.MetricsEnabled() && !c.Grafana.OTLP.TracesEnabled() && !c.Metrics.Enabled() && !c.Traces.Enabled() && !c.Prometheus.Enabled() && !c.TracePrinter.Enabled() { diff --git a/pkg/beyla/config_test.go b/pkg/beyla/config_test.go index 21d263436..c35ed1be6 100644 --- a/pkg/beyla/config_test.go +++ b/pkg/beyla/config_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/beyla/pkg/export/attributes" + "github.com/grafana/beyla/pkg/export/debug" "github.com/grafana/beyla/pkg/export/instrumentations" "github.com/grafana/beyla/pkg/export/otel" "github.com/grafana/beyla/pkg/export/prom" @@ -24,8 +25,11 @@ import ( "github.com/grafana/beyla/pkg/transform" ) +type envMap map[string]string + func TestConfig_Overrides(t *testing.T) { userConfig := bytes.NewBufferString(` +trace_printer: json channel_buffer_len: 33 ebpf: functions: @@ -66,7 +70,7 @@ network: require.NoError(t, os.Setenv("GRAFANA_CLOUD_SUBMIT", "metrics,traces")) require.NoError(t, os.Setenv("KUBECONFIG", "/foo/bar")) require.NoError(t, os.Setenv("BEYLA_NAME_RESOLVER_SOURCES", "k8s,dns")) - defer unsetEnv(t, map[string]string{ + defer unsetEnv(t, envMap{ "KUBECONFIG": "", "BEYLA_OPEN_PORT": "", "BEYLA_EXECUTABLE_NAME": "", "OTEL_SERVICE_NAME": "", "BEYLA_NOOP_TRACES": "", "OTEL_EXPORTER_OTLP_ENDPOINT": "", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "", "GRAFANA_CLOUD_SUBMIT": "", @@ -97,7 +101,7 @@ network: ChannelBufferLen: 33, LogLevel: "INFO", Printer: false, - Noop: true, + TracePrinter: "json", EBPF: ebpfcommon.TracerConfig{ BatchLength: 100, BatchTimeout: time.Second, @@ -198,12 +202,18 @@ func TestConfig_ServiceName(t *testing.T) { } func TestConfigValidate(t *testing.T) { - testCases := []map[string]string{ + testCases := []envMap{ {"OTEL_EXPORTER_OTLP_ENDPOINT": "localhost:1234", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, {"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "localhost:1234", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, {"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": "localhost:1234", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, {"BEYLA_PRINT_TRACES": "true", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, - {"BEYLA_TRACE_PRINTER": "text", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, + {"BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "disabled", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_PRINT_TRACES": "false", "BEYLA_TRACE_PRINTER": "text", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_TRACE_PRINTER": "text", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_TRACE_PRINTER": "json", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_TRACE_PRINTER": "json_indent", "BEYLA_EXECUTABLE_NAME": "foo"}, + {"BEYLA_TRACE_PRINTER": "counter", "BEYLA_EXECUTABLE_NAME": "foo"}, {"BEYLA_PROMETHEUS_PORT": "8080", "BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar"}, } for n, tc := range testCases { @@ -215,10 +225,13 @@ func TestConfigValidate(t *testing.T) { } func TestConfigValidate_error(t *testing.T) { - testCases := []map[string]string{ + testCases := []envMap{ {"OTEL_EXPORTER_OTLP_ENDPOINT": "localhost:1234", "INSTRUMENT_FUNC_NAME": "bar"}, {"BEYLA_EXECUTABLE_NAME": "foo", "INSTRUMENT_FUNC_NAME": "bar", "BEYLA_PRINT_TRACES": "false"}, {"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "text"}, + {"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "json"}, + {"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "json_indent"}, + {"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_PRINT_TRACES": "true", "BEYLA_TRACE_PRINTER": "counter"}, } for n, tc := range testCases { t.Run(fmt.Sprint("case", n), func(t *testing.T) { @@ -283,6 +296,50 @@ network: require.NoError(t, cfg.Validate()) } +func TestConfigValidate_TracePrinter(t *testing.T) { + type test struct { + env envMap + errorMsg string + } + + testCases := []test{ + { + env: envMap{"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_TRACE_PRINTER": "invalid_printer"}, + errorMsg: "invalid value for trace_printer: 'invalid_printer'", + }, + { + env: envMap{"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_TRACE_PRINTER": "json", "BEYLA_PRINT_TRACES": "true"}, + errorMsg: "print_traces and trace_printer are mutually exclusive, use trace_printer instead", + }, + { + env: envMap{"BEYLA_EXECUTABLE_NAME": "foo"}, + errorMsg: "you need to define at least one exporter: trace_printer, grafana, otel_metrics_export, otel_traces_export or prometheus_export", + }, + } + + for i := range testCases { + cfg := loadConfig(t, testCases[i].env) + unsetEnv(t, testCases[i].env) + + err := cfg.Validate() + require.Error(t, err) + assert.Equal(t, err.Error(), testCases[i].errorMsg) + } +} + +func TestConfigValidate_TracePrinterFallback(t *testing.T) { + env := envMap{"BEYLA_EXECUTABLE_NAME": "foo", "BEYLA_PRINT_TRACES": "true"} + + cfg := loadConfig(t, env) + + unsetEnv(t, env) + + err := cfg.Validate() + require.NoError(t, err) + assert.True(t, cfg.Printer.Enabled()) + assert.Equal(t, cfg.TracePrinter, debug.TracePrinterText) +} + func TestConfig_OtelGoAutoEnv(t *testing.T) { // OTEL_GO_AUTO_TARGET_EXE is an alias to BEYLA_EXECUTABLE_NAME // (Compatibility with OpenTelemetry) @@ -312,7 +369,7 @@ func TestConfig_NetworkImplicitProm(t *testing.T) { assert.True(t, cfg.Enabled(FeatureNetO11y)) // Net o11y should be on } -func loadConfig(t *testing.T, env map[string]string) *Config { +func loadConfig(t *testing.T, env envMap) *Config { for k, v := range env { require.NoError(t, os.Setenv(k, v)) } @@ -321,7 +378,7 @@ func loadConfig(t *testing.T, env map[string]string) *Config { return cfg } -func unsetEnv(t *testing.T, env map[string]string) { +func unsetEnv(t *testing.T, env envMap) { for k := range env { require.NoError(t, os.Unsetenv(k)) } diff --git a/pkg/export/debug/debug.go b/pkg/export/debug/debug.go index 34e77ceef..35c4f9345 100644 --- a/pkg/export/debug/debug.go +++ b/pkg/export/debug/debug.go @@ -4,6 +4,7 @@ package debug import ( "encoding/json" "fmt" + "log/slog" "github.com/mariomac/pipes/pipe" "go.opentelemetry.io/otel/trace" @@ -11,39 +12,30 @@ import ( "github.com/grafana/beyla/pkg/internal/request" ) -// XXX deprecated (REMOVE) - use TracePrinter instead +// TODO deprecated (REMOVE) - use TracePrinter instead type PrintEnabled bool func (p PrintEnabled) Enabled() bool { return bool(p) } -type TracePrinter uint +type TracePrinter string const ( - TracePrinterNone = TracePrinter(iota) - TracePrinterText - TracePrinterJSON - TracePrinterJSONIndent - TracePrinterUnknown + TracePrinterDisabled = TracePrinter("disabled") + TracePrinterCounter = TracePrinter("counter") + TracePrinterText = TracePrinter("text") + TracePrinterJSON = TracePrinter("json") + TracePrinterJSONIndent = TracePrinter("json_indent") ) -const ( - kIndent = true - kNoIndent = false -) - -var kTracePrinterString = map[TracePrinter]string{ - TracePrinterNone: "none", - TracePrinterText: "text", - TracePrinterJSON: "json", - TracePrinterJSONIndent: "json_indent", - TracePrinterUnknown: "unknown", +func mlog() *slog.Logger { + return slog.With("component", "debug.TracePrinter") } func (t TracePrinter) Valid() bool { switch t { - case TracePrinterNone, TracePrinterText, TracePrinterJSON, TracePrinterJSONIndent: + case TracePrinterDisabled, TracePrinterText, TracePrinterJSON, TracePrinterJSONIndent, TracePrinterCounter: return true } @@ -51,78 +43,60 @@ func (t TracePrinter) Valid() bool { } func (t TracePrinter) Enabled() bool { - return t.Valid() && t != TracePrinterNone + return t.Valid() && t != TracePrinterDisabled } -func (t TracePrinter) String() string { - for v, str := range kTracePrinterString { - if t == v { - return str - } +func resolvePrinterFunc(p TracePrinter) pipe.FinalFunc[[]request.Span] { + const ( + jsonIndent = true + jsonNoIndent = false + ) + + switch p { + case TracePrinterText: + return textPrinter + case TracePrinterJSON: + return func(input <-chan []request.Span) { jsonPrinter(input, jsonNoIndent) } + case TracePrinterJSONIndent: + return func(input <-chan []request.Span) { jsonPrinter(input, jsonIndent) } + case TracePrinterCounter: + return makeCounterPrinter() } - return kTracePrinterString[TracePrinterUnknown] -} - -func (t *TracePrinter) UnmarshalText(data []byte) error { - text := string(data) - - for v, str := range kTracePrinterString { - if text == str { - *t = v - return nil - } - } - - *t = TracePrinterUnknown - - return fmt.Errorf("unknown TracePrinter value %q", text) -} - -func (t TracePrinter) MarshalText() ([]byte, error) { - return []byte(t.String()), nil + return pipe.IgnoreFinal[[]request.Span]() } func PrinterNode(p TracePrinter) pipe.FinalProvider[[]request.Span] { - return func() (pipe.FinalFunc[[]request.Span], error) { - switch p { - case TracePrinterText: - return printFunc() - case TracePrinterJSON: - return jsonPrintFunc(kNoIndent) - case TracePrinterJSONIndent: - return jsonPrintFunc(kIndent) - } + printerFunc := resolvePrinterFunc(p) - return pipe.IgnoreFinal[[]request.Span](), nil + return func() (pipe.FinalFunc[[]request.Span], error) { + return printerFunc, nil } } -func printFunc() (pipe.FinalFunc[[]request.Span], error) { - return func(input <-chan []request.Span) { - for spans := range input { - for i := range spans { - t := spans[i].Timings() - fmt.Printf("%s (%s[%s]) %s %v %s %s [%s:%d]->[%s:%d] size:%dB svc=[%s %s] traceparent=[%s]\n", - t.Start.Format("2006-01-02 15:04:05.12345"), - t.End.Sub(t.RequestStart), - t.End.Sub(t.Start), - spanType(&spans[i]), - spans[i].Status, - spans[i].Method, - spans[i].Path, - spans[i].Peer+" as "+spans[i].PeerName, - spans[i].PeerPort, - spans[i].Host+" as "+spans[i].HostName, - spans[i].HostPort, - spans[i].ContentLength, - &spans[i].ServiceID, - spans[i].ServiceID.SDKLanguage.String(), - traceparent(&spans[i]), - ) - } +func textPrinter(input <-chan []request.Span) { + for spans := range input { + for i := range spans { + t := spans[i].Timings() + fmt.Printf("%s (%s[%s]) %s %v %s %s [%s:%d]->[%s:%d] size:%dB svc=[%s %s] traceparent=[%s]\n", + t.Start.Format("2006-01-02 15:04:05.12345"), + t.End.Sub(t.RequestStart), + t.End.Sub(t.Start), + spans[i].Type, + spans[i].Status, + spans[i].Method, + spans[i].Path, + spans[i].Peer+" as "+spans[i].PeerName, + spans[i].PeerPort, + spans[i].Host+" as "+spans[i].HostName, + spans[i].HostPort, + spans[i].ContentLength, + &spans[i].ServiceID, + spans[i].ServiceID.SDKLanguage.String(), + traceparent(&spans[i]), + ) } - }, nil + } } func serializeSpansJSON(spans []request.Span, indent bool) ([]byte, error) { @@ -133,19 +107,17 @@ func serializeSpansJSON(spans []request.Span, indent bool) ([]byte, error) { return json.Marshal(spans) } -func jsonPrintFunc(indent bool) (pipe.FinalFunc[[]request.Span], error) { - return func(input <-chan []request.Span) { - for spans := range input { - data, err := serializeSpansJSON(spans, indent) - - if err != nil { - fmt.Printf("Error serializing json\n") - continue - } +func jsonPrinter(input <-chan []request.Span, indent bool) { + for spans := range input { + data, err := serializeSpansJSON(spans, indent) - fmt.Printf("%s\n", data) + if err != nil { + mlog().Error("Error serializing span to json") + continue } - }, nil + + fmt.Printf("%s\n", data) + } } func traceparent(span *request.Span) string { @@ -155,47 +127,14 @@ func traceparent(span *request.Span) string { return fmt.Sprintf("00-%s-%s-%02x", trace.TraceID(span.TraceID).String(), trace.SpanID(span.ParentSpanID).String(), span.Flags) } -func spanType(span *request.Span) string { - switch span.Type { - case request.EventTypeHTTP: - return "SRV" - case request.EventTypeHTTPClient: - return "CLNT" - case request.EventTypeGRPC: - return "GRPC_SRV" - case request.EventTypeGRPCClient: - return "GRPC_CLNT" - case request.EventTypeSQLClient: - return "SQL" - case request.EventTypeRedisClient: - return "REDIS" - case request.EventTypeKafkaClient: - return "KAFKA" - case request.EventTypeRedisServer: - return "REDIS_SRV" - case request.EventTypeKafkaServer: - return "KAFKA_SRV" - } - - return "" -} - -type NoopEnabled bool +func makeCounterPrinter() pipe.FinalFunc[[]request.Span] { + counter := 0 -func (n NoopEnabled) Enabled() bool { - return bool(n) -} -func NoopNode(n NoopEnabled) pipe.FinalProvider[[]request.Span] { - return func() (pipe.FinalFunc[[]request.Span], error) { - if !n { - return pipe.IgnoreFinal[[]request.Span](), nil + return func(input <-chan []request.Span) { + for spans := range input { + counter += len(spans) } - counter := 0 - return func(spans <-chan []request.Span) { - for range spans { - counter += len(spans) - } - fmt.Printf("Processed %d requests\n", counter) - }, nil + + fmt.Printf("Processed %d requests\n", counter) } } diff --git a/pkg/internal/pipe/instrumenter.go b/pkg/internal/pipe/instrumenter.go index 9bb3bdba8..5a2a75226 100644 --- a/pkg/internal/pipe/instrumenter.go +++ b/pkg/internal/pipe/instrumenter.go @@ -40,7 +40,6 @@ type nodesMap struct { Traces pipe.Final[[]request.Span] Prometheus pipe.Final[[]request.Span] Printer pipe.Final[[]request.Span] - Noop pipe.Final[[]request.Span] ProcessReport pipe.Final[[]request.Span] } @@ -53,7 +52,7 @@ func (n *nodesMap) Connect() { n.Routes.SendTo(n.Kubernetes) n.Kubernetes.SendTo(n.NameResolver) n.NameResolver.SendTo(n.AttributeFilter) - n.AttributeFilter.SendTo(n.AlloyTraces, n.Metrics, n.Traces, n.Prometheus, n.Printer, n.Noop, n.ProcessReport) + n.AttributeFilter.SendTo(n.AlloyTraces, n.Metrics, n.Traces, n.Prometheus, n.Printer, n.ProcessReport) } // accessor functions to each field. Grouped here for code brevity during the pipeline build @@ -67,7 +66,6 @@ func otelMetrics(n *nodesMap) *pipe.Final[[]request.Span] { re func otelTraces(n *nodesMap) *pipe.Final[[]request.Span] { return &n.Traces } func printer(n *nodesMap) *pipe.Final[[]request.Span] { return &n.Printer } func prometheus(n *nodesMap) *pipe.Final[[]request.Span] { return &n.Prometheus } -func noop(n *nodesMap) *pipe.Final[[]request.Span] { return &n.Noop } func processReport(n *nodesMap) *pipe.Final[[]request.Span] { return &n.ProcessReport } // builder with injectable instantiators for unit testing @@ -119,7 +117,6 @@ func newGraphBuilder(ctx context.Context, config *beyla.Config, ctxInfo *global. pipe.AddFinalProvider(gnb, prometheus, prom.PrometheusEndpoint(ctx, gb.ctxInfo, &config.Prometheus, config.Attributes.Select)) pipe.AddFinalProvider(gnb, alloyTraces, alloy.TracesReceiver(ctx, gb.ctxInfo, &config.TracesReceiver, config.Attributes.Select)) - pipe.AddFinalProvider(gnb, noop, debug.NoopNode(config.Noop)) pipe.AddFinalProvider(gnb, printer, debug.PrinterNode(config.TracePrinter)) // process subpipeline will start another pipeline only to collect and export data diff --git a/pkg/internal/request/span.go b/pkg/internal/request/span.go index 3633d86ac..9289c39de 100644 --- a/pkg/internal/request/span.go +++ b/pkg/internal/request/span.go @@ -121,7 +121,7 @@ type Span struct { PeerPort int `json:"peerPort,string"` Host string `json:"host"` HostPort int `json:"hostPort,string"` - Status int `json:"status,string"` + Status int `json:"-"` ContentLength int64 `json:"-"` RequestStart int64 `json:"-"` Start int64 `json:"-"` @@ -170,87 +170,69 @@ func kindString(span *Span) string { // helper attribute functions used by JSON serialization type SpanAttributes map[string]string -// nolint func spanAttributes(s *Span) SpanAttributes { - attr := SpanAttributes{} - - // method - switch s.Type { - case EventTypeHTTP, EventTypeHTTPClient: - attr["method"] = s.Method - case EventTypeGRPC, EventTypeGRPCClient: - attr["method"] = s.Path - } - - // status - switch s.Type { - case EventTypeHTTP, EventTypeHTTPClient, EventTypeGRPC, EventTypeGRPCClient: - attr["status"] = strconv.Itoa(s.Status) - } - - // url - switch s.Type { - case EventTypeHTTP, EventTypeHTTPClient: - attr["url"] = s.Path - } - - // contentLen - switch s.Type { - case EventTypeHTTP, EventTypeHTTPClient: - attr["contentLen"] = strconv.FormatInt(s.ContentLength, 10) - } - - // route switch s.Type { case EventTypeHTTP: - attr["route"] = s.Route - } - - // clientAddr - switch s.Type { - case EventTypeHTTP, EventTypeGRPC: - attr["clientAddr"] = SpanPeer(s) - } - - // serverAddr - // Applies to ALL kinds of event types - attr["serverAddr"] = SpanHost(s) - - // serverPort - // Applies to ALL kinds of event types - attr["serverPort"] = strconv.Itoa(s.HostPort) - - // operation - switch s.Type { - case EventTypeSQLClient, EventTypeRedisServer, EventTypeKafkaServer: - attr["operation"] = s.Method - } - - // table - switch s.Type { + return SpanAttributes{ + "method": s.Method, + "status": strconv.Itoa(s.Status), + "url": s.Path, + "contentLen": strconv.FormatInt(s.ContentLength, 10), + "route": s.Route, + "clientAddr": SpanPeer(s), + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + } + case EventTypeHTTPClient: + return SpanAttributes{ + "method": s.Method, + "status": strconv.Itoa(s.Status), + "url": s.Path, + "clientAddr": SpanPeer(s), + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + } + case EventTypeGRPC: + return SpanAttributes{ + "method": s.Path, + "status": strconv.Itoa(s.Status), + "clientAddr": SpanPeer(s), + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + } + case EventTypeGRPCClient: + return SpanAttributes{ + "method": s.Path, + "status": strconv.Itoa(s.Status), + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + } case EventTypeSQLClient: - attr["table"] = s.Path - } - - // statement - switch s.Type { - case EventTypeSQLClient, EventTypeRedisServer: - attr["statement"] = s.Statement - } - - // query - switch s.Type { + return SpanAttributes{ + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + "operation": s.Method, + "table": s.Path, + "statement": s.Statement, + } case EventTypeRedisServer: - attr["query"] = s.Path - } - - // clientId - switch s.Type { + return SpanAttributes{ + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + "operation": s.Method, + "statement": s.Statement, + "query": s.Path, + } case EventTypeKafkaServer: - attr["clientId"] = s.OtherNamespace + return SpanAttributes{ + "serverAddr": SpanHost(s), + "serverPort": strconv.Itoa(s.HostPort), + "operation": s.Method, + "clientId": s.OtherNamespace, + } } - return attr + return SpanAttributes{} } func (s Span) MarshalJSON() ([]byte, error) { diff --git a/pkg/internal/request/span_test.go b/pkg/internal/request/span_test.go index 6c0ad6436..5485d71d9 100644 --- a/pkg/internal/request/span_test.go +++ b/pkg/internal/request/span_test.go @@ -91,67 +91,154 @@ func getObjFromObj(obj jsonObject, key string) jsonObject { } func TestSerializeJSONSpans(t *testing.T) { - span := Span{ - Type: EventTypeHTTP, - IgnoreSpan: IgnoreMetrics, - Method: "method", - Path: "path", - Route: "route", - Peer: "peer", - PeerPort: 1234, - Host: "host", - HostPort: 5678, - Status: 200, - ContentLength: 1024, - RequestStart: 10000, - Start: 15000, - End: 35000, - TraceID: trace2.TraceID{0x1, 0x2, 0x3}, - SpanID: trace2.SpanID{0x1, 0x2, 0x3}, - ParentSpanID: trace2.SpanID{0x1, 0x2, 0x3}, - Flags: 1, - PeerName: "peername", - HostName: "hostname", - OtherNamespace: "otherns", - Statement: "statement", + type testData struct { + eventType EventType + attribs map[string]any } - data, err := json.MarshalIndent(span, "", " ") - - require.NoError(t, err) - - s, err := deserializeJSONObject(data) - - require.NoError(t, err) - - assert.Equal(t, s["type"], "HTTP") - assert.Equal(t, s["kind"], "SERVER") - assert.Equal(t, s["ignoreSpan"], "Metrics") - assert.Equal(t, s["peer"], "peer") - assert.Equal(t, s["peerPort"], "1234") - assert.Equal(t, s["host"], "host") - assert.Equal(t, s["hostPort"], "5678") - assert.Equal(t, s["peerName"], "peername") - assert.Equal(t, s["hostName"], "hostname") - assert.NotEmpty(t, s["start"]) - assert.NotEmpty(t, s["handlerStart"]) - assert.NotEmpty(t, s["end"]) - assert.Equal(t, s["duration"], "25µs") - assert.Equal(t, s["durationUSec"], "25") - assert.Equal(t, s["handlerDuration"], "20µs") - assert.Equal(t, s["handlerDurationUSec"], "20") - - assert.Equal(t, s["traceID"], "01020300000000000000000000000000") - assert.Equal(t, s["spanID"], "0102030000000000") - assert.Equal(t, s["parentSpanID"], "0102030000000000") - assert.Equal(t, s["flags"], "1") - - a := getObjFromObj(s, "attributes") - assert.Equal(t, a["method"], "method") - assert.Equal(t, a["status"], "200") - assert.Equal(t, a["url"], "path") - assert.Equal(t, a["clientAddr"], "peername") - assert.Equal(t, a["serverAddr"], "hostname") - assert.Equal(t, a["contentLen"], "1024") - assert.Equal(t, a["route"], "route") + tData := []testData{ + testData{ + eventType: EventTypeHTTP, + attribs: map[string]any{ + "method": "method", + "status": "200", + "url": "path", + "contentLen": "1024", + "route": "route", + "clientAddr": "peername", + "serverAddr": "hostname", + "serverPort": "5678", + }, + }, + testData{ + eventType: EventTypeHTTPClient, + attribs: map[string]any{ + "method": "method", + "status": "200", + "url": "path", + "clientAddr": "peername", + "serverAddr": "hostname", + "serverPort": "5678", + }, + }, + testData{ + eventType: EventTypeGRPC, + attribs: map[string]any{ + "method": "path", + "status": "200", + "clientAddr": "peername", + "serverAddr": "hostname", + "serverPort": "5678", + }, + }, + testData{ + eventType: EventTypeGRPCClient, + attribs: map[string]any{ + "method": "path", + "status": "200", + "serverAddr": "hostname", + "serverPort": "5678", + }, + }, + testData{ + eventType: EventTypeSQLClient, + attribs: map[string]any{ + "serverAddr": "hostname", + "serverPort": "5678", + "operation": "method", + "table": "path", + "statement": "statement", + }, + }, + testData{ + eventType: EventTypeRedisClient, + attribs: map[string]any{}, + }, + testData{ + eventType: EventTypeKafkaClient, + attribs: map[string]any{}, + }, + testData{ + eventType: EventTypeRedisServer, + attribs: map[string]any{ + "serverAddr": "hostname", + "serverPort": "5678", + "operation": "method", + "statement": "statement", + "query": "path", + }, + }, + testData{ + eventType: EventTypeKafkaServer, + attribs: map[string]any{ + "serverAddr": "hostname", + "serverPort": "5678", + "operation": "method", + "clientId": "otherns", + }, + }, + } + + test := func(t *testing.T, tData *testData) { + span := Span{ + Type: tData.eventType, + IgnoreSpan: IgnoreMetrics, + Method: "method", + Path: "path", + Route: "route", + Peer: "peer", + PeerPort: 1234, + Host: "host", + HostPort: 5678, + Status: 200, + ContentLength: 1024, + RequestStart: 10000, + Start: 15000, + End: 35000, + TraceID: trace2.TraceID{0x1, 0x2, 0x3}, + SpanID: trace2.SpanID{0x1, 0x2, 0x3}, + ParentSpanID: trace2.SpanID{0x1, 0x2, 0x3}, + Flags: 1, + PeerName: "peername", + HostName: "hostname", + OtherNamespace: "otherns", + Statement: "statement", + } + + data, err := json.MarshalIndent(span, "", " ") + + require.NoError(t, err) + + s, err := deserializeJSONObject(data) + + require.NoError(t, err) + + assert.Equal(t, map[string]any{ + "type": tData.eventType.String(), + "kind": kindString(&span), + "ignoreSpan": "Metrics", + "peer": "peer", + "peerPort": "1234", + "host": "host", + "hostPort": "5678", + "peerName": "peername", + "hostName": "hostname", + "start": s["start"], + "handlerStart": s["handlerStart"], + "end": s["end"], + "duration": "25µs", + "durationUSec": "25", + "handlerDuration": "20µs", + "handlerDurationUSec": "20", + "traceID": "01020300000000000000000000000000", + "spanID": "0102030000000000", + "parentSpanID": "0102030000000000", + "flags": "1", + "attributes": tData.attribs, + }, s) + } + + for i := range tData { + test(t, &tData[i]) + } }