Skip to content

Commit ad2c25c

Browse files
Add OpenTelemetry traces (#110)
1 parent 95ef9f3 commit ad2c25c

File tree

8 files changed

+144
-1
lines changed

8 files changed

+144
-1
lines changed

client.go

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"time"
66

77
"github.com/osquery/osquery-go/gen/osquery"
8+
"github.com/osquery/osquery-go/traces"
89
"github.com/osquery/osquery-go/transport"
910

1011
"github.com/apache/thrift/lib/go/thrift"
@@ -107,6 +108,9 @@ func (c *ExtensionManagerClient) Call(registry, item string, request osquery.Ext
107108

108109
// CallContext requests a call to an extension (or core) registry plugin.
109110
func (c *ExtensionManagerClient) CallContext(ctx context.Context, registry, item string, request osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) {
111+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.CallContext")
112+
defer span.End()
113+
110114
if err := c.lock.Lock(ctx); err != nil {
111115
return nil, err
112116
}
@@ -121,6 +125,9 @@ func (c *ExtensionManagerClient) Extensions() (osquery.InternalExtensionList, er
121125

122126
// ExtensionsContext requests the list of active registered extensions.
123127
func (c *ExtensionManagerClient) ExtensionsContext(ctx context.Context) (osquery.InternalExtensionList, error) {
128+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.ExtensionsContext")
129+
defer span.End()
130+
124131
if err := c.lock.Lock(ctx); err != nil {
125132
return nil, err
126133
}
@@ -135,6 +142,9 @@ func (c *ExtensionManagerClient) RegisterExtension(info *osquery.InternalExtensi
135142

136143
// RegisterExtensionContext registers the extension plugins with the osquery process.
137144
func (c *ExtensionManagerClient) RegisterExtensionContext(ctx context.Context, info *osquery.InternalExtensionInfo, registry osquery.ExtensionRegistry) (*osquery.ExtensionStatus, error) {
145+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.RegisterExtensionContext")
146+
defer span.End()
147+
138148
if err := c.lock.Lock(ctx); err != nil {
139149
return nil, err
140150
}
@@ -149,6 +159,9 @@ func (c *ExtensionManagerClient) DeregisterExtension(uuid osquery.ExtensionRoute
149159

150160
// DeregisterExtensionContext de-registers the extension plugins with the osquery process.
151161
func (c *ExtensionManagerClient) DeregisterExtensionContext(ctx context.Context, uuid osquery.ExtensionRouteUUID) (*osquery.ExtensionStatus, error) {
162+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.DeregisterExtensionContext")
163+
defer span.End()
164+
152165
if err := c.lock.Lock(ctx); err != nil {
153166
return nil, err
154167
}
@@ -163,6 +176,9 @@ func (c *ExtensionManagerClient) Options() (osquery.InternalOptionList, error) {
163176

164177
// OptionsContext requests the list of bootstrap or configuration options.
165178
func (c *ExtensionManagerClient) OptionsContext(ctx context.Context) (osquery.InternalOptionList, error) {
179+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.OptionsContext")
180+
defer span.End()
181+
166182
if err := c.lock.Lock(ctx); err != nil {
167183
return nil, err
168184
}
@@ -181,6 +197,9 @@ func (c *ExtensionManagerClient) Query(sql string) (*osquery.ExtensionResponse,
181197
// Consider using the QueryRow or QueryRows helpers for a more friendly
182198
// interface.
183199
func (c *ExtensionManagerClient) QueryContext(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) {
200+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryContext")
201+
defer span.End()
202+
184203
if err := c.lock.Lock(ctx); err != nil {
185204
return nil, err
186205
}
@@ -199,6 +218,9 @@ func (c *ExtensionManagerClient) QueryRows(sql string) ([]map[string]string, err
199218
// results. It handles checking both the transport level errors and the osquery
200219
// internal errors by returning a normal Go error type.
201220
func (c *ExtensionManagerClient) QueryRowsContext(ctx context.Context, sql string) ([]map[string]string, error) {
221+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryRowsContext")
222+
defer span.End()
223+
202224
res, err := c.QueryContext(ctx, sql)
203225
if err != nil {
204226
return nil, errors.Wrap(err, "transport error in query")
@@ -222,6 +244,9 @@ func (c *ExtensionManagerClient) QueryRow(sql string) (map[string]string, error)
222244
// QueryRowContext behaves similarly to QueryRows, but it returns an error if the
223245
// query does not return exactly one row.
224246
func (c *ExtensionManagerClient) QueryRowContext(ctx context.Context, sql string) (map[string]string, error) {
247+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.QueryRowContext")
248+
defer span.End()
249+
225250
res, err := c.QueryRowsContext(ctx, sql)
226251
if err != nil {
227252
return nil, err
@@ -239,6 +264,9 @@ func (c *ExtensionManagerClient) GetQueryColumns(sql string) (*osquery.Extension
239264

240265
// GetQueryColumnsContext requests the columns returned by the parsed query.
241266
func (c *ExtensionManagerClient) GetQueryColumnsContext(ctx context.Context, sql string) (*osquery.ExtensionResponse, error) {
267+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerClient.GetQueryColumnsContext")
268+
defer span.End()
269+
242270
if err := c.lock.Lock(ctx); err != nil {
243271
return nil, err
244272
}

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ require (
55
github.com/apache/thrift v0.16.0
66
github.com/pkg/errors v0.8.0
77
github.com/stretchr/testify v1.8.3
8+
go.opentelemetry.io/otel v1.16.0
9+
go.opentelemetry.io/otel/trace v1.16.0
810
)
911

1012
require (
1113
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/go-logr/logr v1.2.4 // indirect
15+
github.com/go-logr/stdr v1.2.2 // indirect
1216
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
go.opentelemetry.io/otel/metric v1.16.0 // indirect
1318
golang.org/x/sys v0.1.0 // indirect
1419
gopkg.in/yaml.v3 v3.0.1 // indirect
1520
)

go.sum

+12
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@ github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY=
44
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
55
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
66
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
8+
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
9+
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
10+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
11+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
712
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
13+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
814
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
915
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
1016
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1117
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1218
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
1319
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
20+
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
21+
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
22+
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
23+
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
24+
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
25+
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
1426
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
1527
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
1628
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=

plugin/config/config.go

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88

99
"github.com/osquery/osquery-go/gen/osquery"
10+
"github.com/osquery/osquery-go/traces"
1011
)
1112

1213
// GenerateConfigsFunc returns the configurations generated by this plugin.
@@ -55,6 +56,9 @@ const requestActionKey = "action"
5556
const genConfigAction = "genConfig"
5657

5758
func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse {
59+
ctx, span := traces.StartSpan(ctx, "Config.Call", "action", request[requestActionKey])
60+
defer span.End()
61+
5862
switch request[requestActionKey] {
5963
case genConfigAction:
6064
configs, err := t.generate(ctx)

plugin/distributed/distributed.go

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strings"
1212

1313
"github.com/osquery/osquery-go/gen/osquery"
14+
"github.com/osquery/osquery-go/traces"
1415
)
1516

1617
// GetQueriesResult contains the information about which queries the
@@ -234,6 +235,9 @@ func convertRows(rows []interface{}) ([]map[string]string, error) {
234235
}
235236

236237
func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse {
238+
ctx, span := traces.StartSpan(ctx, "Distributed.Call", "action", request[requestActionKey])
239+
defer span.End()
240+
237241
switch request[requestActionKey] {
238242
case getQueriesAction:
239243
queries, err := t.getQueries(ctx)

plugin/table/table.go

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strconv"
88

99
"github.com/osquery/osquery-go/gen/osquery"
10+
"github.com/osquery/osquery-go/traces"
1011
"github.com/pkg/errors"
1112
)
1213

@@ -52,6 +53,9 @@ func (t *Plugin) Routes() osquery.ExtensionPluginResponse {
5253
}
5354

5455
func (t *Plugin) Call(ctx context.Context, request osquery.ExtensionPluginRequest) osquery.ExtensionResponse {
56+
ctx, span := traces.StartSpan(ctx, "Table.Call", "action", request["action"])
57+
defer span.End()
58+
5559
ok := osquery.ExtensionStatus{Code: 0, Message: "OK"}
5660
switch request["action"] {
5761
case "generate":

server.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/apache/thrift/lib/go/thrift"
1010

1111
"github.com/osquery/osquery-go/gen/osquery"
12+
"github.com/osquery/osquery-go/traces"
1213
"github.com/osquery/osquery-go/transport"
1314
"github.com/pkg/errors"
1415
)
@@ -287,6 +288,12 @@ func (s *ExtensionManagerServer) Ping(ctx context.Context) (*osquery.ExtensionSt
287288
// Call routes a call from the osquery process to the appropriate registered
288289
// plugin.
289290
func (s *ExtensionManagerServer) Call(ctx context.Context, registry string, item string, request osquery.ExtensionPluginRequest) (*osquery.ExtensionResponse, error) {
291+
ctx, span := traces.StartSpan(ctx, "ExtensionManagerServer.Call",
292+
"registry", registry,
293+
"item", item,
294+
)
295+
defer span.End()
296+
290297
subreg, ok := s.registry[registry]
291298
if !ok {
292299
return &osquery.ExtensionResponse{
@@ -307,7 +314,7 @@ func (s *ExtensionManagerServer) Call(ctx context.Context, registry string, item
307314
}, nil
308315
}
309316

310-
response := plugin.Call(context.Background(), request)
317+
response := plugin.Call(ctx, request)
311318
return &response, nil
312319
}
313320

traces/traces.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Package traces allows for instrumenting osquery-go with OpenTelemetry traces.
2+
// Unless the consuming application specifically configures a trace exporter, all tracing is a no-op.
3+
package traces
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"runtime/debug"
9+
10+
"go.opentelemetry.io/otel"
11+
"go.opentelemetry.io/otel/attribute"
12+
"go.opentelemetry.io/otel/trace"
13+
)
14+
15+
const instrumentationPkg = "github.com/osquery/osquery-go"
16+
17+
var internalVersion string
18+
19+
// osqueryGoVersion returns the version of osquery-go, to be used to set the instrumentation
20+
// version `otel.scope.version`. It looks through build info to determine the current version
21+
// of the osquery-go package. Once determined, it saves the version to avoid performing this
22+
// work again. If the version cannot be determined, the version defaults to the current version,
23+
// which is hardcoded.
24+
func osqueryGoVersion() string {
25+
if internalVersion != "" {
26+
return internalVersion
27+
}
28+
if info, ok := debug.ReadBuildInfo(); ok {
29+
for _, dep := range info.Deps {
30+
if dep == nil {
31+
continue
32+
}
33+
if dep.Path == instrumentationPkg {
34+
internalVersion = dep.Version
35+
return internalVersion
36+
}
37+
}
38+
}
39+
40+
// Couldn't get the version from runtime build info -- save and return 0.0.0,
41+
// which is the current osquery-go version.
42+
internalVersion = "0.0.0"
43+
return internalVersion
44+
}
45+
46+
// By default, use the global tracer provider, which is a no-op provider.
47+
var tracerProvider = otel.GetTracerProvider()
48+
49+
// SetTracerProvider allows consuming libraries to set a custom/non-global tracer provider.
50+
func SetTracerProvider(tp trace.TracerProvider) {
51+
tracerProvider = tp
52+
}
53+
54+
// OsqueryGoTracer provides a tracer with a standardized name and version.
55+
// It should be used to start a span that requires `SpanStartOption`s that are
56+
// not supported by `StartSpan` below -- i.e., any `SpanStartOption` besides
57+
// `WithAttributes`.
58+
func OsqueryGoTracer() trace.Tracer {
59+
return tracerProvider.Tracer(instrumentationPkg, trace.WithInstrumentationVersion(osqueryGoVersion()))
60+
}
61+
62+
// StartSpan is a wrapper around trace.Tracer.Start that simplifies passing in span attributes.
63+
// `keyVals` should be a list of pairs, where the first in the pair is a string representing
64+
// the attribute key and the second in the pair is the attribute value.
65+
// The caller is always responsible for ending the returned span.
66+
// Any spans requiring more specific configuration can be created manually via OsqueryGoTracer().Start.
67+
func StartSpan(ctx context.Context, spanName string, keyVals ...string) (context.Context, trace.Span) {
68+
attrs := make([]attribute.KeyValue, 0)
69+
70+
for i := 0; i < len(keyVals); i += 2 {
71+
// Ensure all attributes are appropriately namespaced
72+
key := fmt.Sprintf("osquery-go.%s", keyVals[i])
73+
attrs = append(attrs, attribute.String(key, keyVals[i+1]))
74+
}
75+
76+
opts := []trace.SpanStartOption{trace.WithAttributes(attrs...)}
77+
78+
return OsqueryGoTracer().Start(ctx, spanName, opts...)
79+
}

0 commit comments

Comments
 (0)