@@ -5,7 +5,6 @@ package ebpfcommon
55
66import (
77 "bytes"
8- "encoding/json"
98 "errors"
109 "fmt"
1110 "io"
@@ -20,19 +19,34 @@ import (
2019type elasticsearchOperation struct {
2120 NodeName string
2221 DBQueryText string
23- DBOperationName string
2422 DBCollectionName string
2523}
2624
27- const (
28- pathSearch string = "_search"
29- )
25+ var elasticsearchOperationMethods = map [string ]map [string ]struct {}{
26+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search
27+ "search" : {http .MethodPost : {}, http .MethodGet : {}},
28+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch
29+ "msearch" : {http .MethodPost : {}, http .MethodGet : {}},
30+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk
31+ "bulk" : {http .MethodPost : {}, http .MethodPut : {}},
32+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-get
33+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-index
34+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-delete
35+ // https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-exists
36+ "doc" : {http .MethodGet : {}, http .MethodPost : {}, http .MethodPut : {}, http .MethodHead : {}, http .MethodDelete : {}},
37+ }
3038
3139func ElasticsearchSpan (baseSpan * request.Span , req * http.Request , resp * http.Response ) (request.Span , bool ) {
3240 if ! isElasticsearchResponse (resp ) {
3341 return * baseSpan , false
3442 }
35- if err := isSearchRequest (req ); err != nil {
43+
44+ operationName := extractElasticsearchOperationName (req )
45+ if operationName == "" {
46+ return * baseSpan , false
47+ }
48+
49+ if err := isElasticsearchSupportedRequest (operationName , req .Method ); err != nil {
3650 slog .Debug (err .Error ())
3751 return * baseSpan , false
3852 }
@@ -42,19 +56,14 @@ func ElasticsearchSpan(baseSpan *request.Span, req *http.Request, resp *http.Res
4256 slog .Debug ("parse Elasticsearch request" , "error" , err )
4357 return * baseSpan , false
4458 }
45-
46- if resp != nil {
47- if v := resp .Header .Get ("X-Found-Handling-Instance" ); v != "" {
48- op .NodeName = v
49- }
50- } else {
51- op .NodeName = req .URL .Host
59+ if v := resp .Header .Get ("X-Found-Handling-Instance" ); v != "" {
60+ op .NodeName = v
5261 }
5362
5463 baseSpan .SubType = request .HTTPSubtypeElasticsearch
5564 baseSpan .Elasticsearch = & request.Elasticsearch {
5665 NodeName : op .NodeName ,
57- DBOperationName : op . DBOperationName ,
66+ DBOperationName : operationName ,
5867 DBCollectionName : op .DBCollectionName ,
5968 DBQueryText : op .DBQueryText ,
6069 }
@@ -67,42 +76,23 @@ func parseElasticsearchRequest(req *http.Request) (elasticsearchOperation, error
6776 if err != nil {
6877 return op , fmt .Errorf ("failed to read Elasticsearch request body %w" , err )
6978 }
70-
7179 req .Body = io .NopCloser (bytes .NewBuffer (reqB ))
72- if len (reqB ) == 0 {
73- op .DBQueryText = ""
74- } else {
75- dbQueryText , err := extractDBQueryText (reqB )
76- if err != nil {
77- return op , err
78- }
79- op .DBQueryText = dbQueryText
80- }
81- op .DBOperationName = extractOperationName (req )
82- op .DBCollectionName = extractDBCollectionName (req )
80+ op .DBQueryText = string (reqB )
81+ op .DBCollectionName = extractElasticsearchDBCollectionName (req )
8382 return op , nil
8483}
8584
86- func extractDBQueryText (body []byte ) (string , error ) {
87- var buf bytes.Buffer
88-
89- if err := json .Compact (& buf , body ); err != nil {
90- return "" , fmt .Errorf ("invalid Elasticsearch JSON body: %w" , err )
85+ func isElasticsearchSupportedRequest (operationName , methodName string ) error {
86+ methods , exists := elasticsearchOperationMethods [operationName ]
87+ if ! exists {
88+ return errors .New ("parse Elasticsearch request: unsupported endpoint" )
9189 }
9290
93- return buf .String (), nil
94- }
95-
96- func isSearchRequest (req * http.Request ) error {
97- // let's focus only on _search operation that has only GET and POST http methods
98- if ! strings .Contains (req .URL .Path , pathSearch ) {
99- return errors .New ("parse Elasticsearch search request: unsupported endpoint" )
100- }
101-
102- if req .Method != http .MethodGet && req .Method != http .MethodPost {
103- return errors .New ("parse Elasticsearch search request: unsupported method" )
91+ _ , supported := methods [methodName ]
92+ if supported {
93+ return nil
10494 }
105- return nil
95+ return fmt . Errorf ( "parse Elasticsearch %s request: unsupported method %s" , operationName , methodName )
10696}
10797
10898// isElasticsearchResponse checks if X-Elastic-Product HTTP header is present.
@@ -114,26 +104,43 @@ func isElasticsearchResponse(resp *http.Response) bool {
114104 return headerValue == expectedValue
115105}
116106
117- // extractOperationName is a generic function used to extract the operation name
107+ // extractElasticsearchOperationName is a generic function used to extract the operation name
118108// that is the endpoint identifier provided in the request
119- func extractOperationName (req * http.Request ) string {
109+ // we can have different operations where the name of the operation is found in
110+ // the last or second to last part of the url
111+ func extractElasticsearchOperationName (req * http.Request ) string {
120112 path := strings .Trim (req .URL .Path , "/" )
121113 if path == "" {
122114 return ""
123115 }
116+
124117 parts := strings .Split (path , "/" )
125118 if len (parts ) == 0 {
126119 return ""
127120 }
128- name := parts [len (parts )- 1 ]
129- return strings .TrimPrefix (name , "_" )
121+
122+ lastPart := parts [len (parts )- 1 ]
123+ possibleOperationName := strings .TrimPrefix (lastPart , "_" )
124+
125+ if _ , found := elasticsearchOperationMethods [possibleOperationName ]; found {
126+ return possibleOperationName
127+ }
128+
129+ if len (parts ) >= 2 {
130+ secondLastPart := parts [len (parts )- 2 ]
131+ possibleOperationName = strings .TrimPrefix (secondLastPart , "_" )
132+ if _ , found := elasticsearchOperationMethods [possibleOperationName ]; found {
133+ return possibleOperationName
134+ }
135+ }
136+ return ""
130137}
131138
132- // extractDBCollectionName takes into account this rule from semconv
139+ // extractElasticsearchDBCollectionName takes into account this rule from semconv
133140// The query may target multiple indices or data streams,
134141// in which case it SHOULD be a comma separated list of those.
135142// If the query doesn’t target a specific index, this field MUST NOT be set.
136- func extractDBCollectionName (req * http.Request ) string {
143+ func extractElasticsearchDBCollectionName (req * http.Request ) string {
137144 path := strings .Trim (req .URL .Path , "/" )
138145 if path == "" {
139146 return ""
0 commit comments