Skip to content

Commit e814306

Browse files
Merge pull request #240 from RonFed/database/sql_instrumentation
Database/sql instrumentation
2 parents e717861 + 7ba795a commit e814306

File tree

31 files changed

+1303
-15
lines changed

31 files changed

+1303
-15
lines changed

.github/workflows/e2e/k8s/sample-job.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ spec:
3535
value: "sample-app"
3636
- name: OTEL_PROPAGATORS
3737
value: "tracecontext,baggage"
38+
- name: OTEL_GO_AUTO_INCLUDE_DB_STATEMENT
39+
value: "true"
3840
resources: {}
3941
securityContext:
4042
runAsUser: 0

.github/workflows/kind.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
strategy:
1414
matrix:
1515
k8s-version: ["v1.26.0"]
16-
library: ["gorillamux", "nethttp", "gin"]
16+
library: ["gorillamux", "nethttp", "gin", "databasesql"]
1717
runs-on: ubuntu-latest
1818
steps:
1919
- name: Checkout Repo

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,7 @@ launcher/
2525
opentelemetry-helm-charts/
2626

2727
# don't track temp e2e trace json files
28-
test/**/traces-orig.json
28+
test/**/traces-orig.json
29+
30+
# ignore db files created by the example or tests
31+
*.db

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
88

99
## [Unreleased]
1010

11+
### Added
12+
- Add database/sql instrumentation ([#240](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/240))
13+
1114
### Changed
1215

1316
- The function signature of `"go.opentelemetry.io/auto/offsets-tracker/downloader".DownloadBinary` has changed.

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,11 @@ license-header-check:
118118
exit 1; \
119119
fi
120120

121-
.PHONY: fixture-nethttp fixture-gorillamux fixture-gin
121+
.PHONY: fixture-nethttp fixture-gorillamux fixture-gin fixture-databasesql
122122
fixture-nethttp: fixtures/nethttp
123123
fixture-gorillamux: fixtures/gorillamux
124124
fixture-gin: fixtures/gin
125+
fixture-databasesql: fixtures/databasesql
125126
fixtures/%: LIBRARY=$*
126127
fixtures/%:
127128
$(MAKE) docker-build

examples/httpPlusdb/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM golang:1.20
2+
WORKDIR /app
3+
COPY . .
4+
RUN go build -o main
5+
ENTRYPOINT ["/app/main"]

examples/httpPlusdb/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Example of Auto instrumentation of HTTP server + SQL database
2+
3+
This example shows a trace being generated which is composed of a http request and sql db handling -
4+
both visible in the trace.
5+
6+
For testing auto instrumentation, we can use the docker compose.
7+
8+
To run the example, bring up the services using the command.
9+
10+
```
11+
docker compose up
12+
```
13+
14+
Now, you can hit the server using the below command
15+
```
16+
curl localhost:8080/query_db
17+
Which will query the dummy sqlite database named test.db
18+
```
19+
Every hit to the server should generate a trace that we can observe in [Jaeger UI](http://localhost:16686/)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
version: "3.9"
2+
3+
networks:
4+
default:
5+
name: http-db
6+
driver: bridge
7+
8+
services:
9+
http-plus-db:
10+
depends_on:
11+
- jaeger
12+
build:
13+
context: .
14+
dockerfile: ./Dockerfile
15+
pid: "host"
16+
ports:
17+
- "8080:8080"
18+
volumes:
19+
- shared-data:/app
20+
- /proc:/host/proc
21+
go-auto:
22+
depends_on:
23+
- http-plus-db
24+
build:
25+
context: ../..
26+
dockerfile: Dockerfile
27+
privileged: true
28+
pid: "host"
29+
environment:
30+
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
31+
- OTEL_GO_AUTO_TARGET_EXE=/app/main
32+
- OTEL_GO_AUTO_INCLUDE_DB_STATEMENT=true
33+
- OTEL_SERVICE_NAME=httpPlusdb
34+
- OTEL_PROPAGATORS=tracecontext,baggage
35+
- CGO_ENABLED=1
36+
volumes:
37+
- shared-data:/app
38+
- /proc:/host/proc
39+
40+
jaeger:
41+
image: jaegertracing/all-in-one:latest
42+
ports:
43+
- "16686:16686"
44+
- "14268:14268"
45+
environment:
46+
- COLLECTOR_OTLP_ENABLED=true
47+
- LOG_LEVEL=debug
48+
deploy:
49+
resources:
50+
limits:
51+
memory: 300M
52+
restart: unless-stopped
53+
54+
55+
volumes:
56+
shared-data:

examples/httpPlusdb/go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module go.opentelemetry.io/auto/examples/httpPlusdb
2+
3+
go 1.20
4+
5+
require go.uber.org/zap v1.24.0
6+
7+
require (
8+
go.uber.org/atomic v1.7.0 // indirect
9+
go.uber.org/multierr v1.6.0 // indirect
10+
)
11+
12+
require github.com/mattn/go-sqlite3 v1.14.17

examples/httpPlusdb/go.sum

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
6+
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
7+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
8+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
12+
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
13+
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
14+
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
15+
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
16+
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
17+
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
18+
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
19+
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
20+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
177 KB
Loading

examples/httpPlusdb/main.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"database/sql"
19+
"fmt"
20+
"net/http"
21+
"os"
22+
23+
_ "github.com/mattn/go-sqlite3"
24+
"go.uber.org/zap"
25+
)
26+
27+
const sqlQuery = "SELECT * FROM contacts"
28+
const dbName = "test.db"
29+
const tableDefinition = `CREATE TABLE contacts (
30+
contact_id INTEGER PRIMARY KEY,
31+
first_name TEXT NOT NULL,
32+
last_name TEXT NOT NULL,
33+
email TEXT NOT NULL UNIQUE,
34+
phone TEXT NOT NULL UNIQUE);`
35+
const tableInsertion = `INSERT INTO 'contacts'
36+
('first_name', 'last_name', 'email', 'phone') VALUES
37+
('Moshe', 'Levi', '[email protected]', '052-1234567');`
38+
39+
// Server is Http server that exposes multiple endpoints.
40+
type Server struct {
41+
db *sql.DB
42+
}
43+
44+
// Create the db file.
45+
func CreateDb() {
46+
file, err := os.Create(dbName)
47+
if err != nil {
48+
panic(err)
49+
}
50+
err = file.Close()
51+
if err != nil {
52+
panic(err)
53+
}
54+
}
55+
56+
// NewServer creates a server struct after creating the DB and initializing it
57+
// and creating a table named 'contacts' and adding a single row to it.
58+
func NewServer() *Server {
59+
CreateDb()
60+
61+
database, err := sql.Open("sqlite3", dbName)
62+
63+
if err != nil {
64+
panic(err)
65+
}
66+
67+
_, err = database.Exec(tableDefinition)
68+
69+
if err != nil {
70+
panic(err)
71+
}
72+
73+
_, err = database.Exec(tableInsertion)
74+
75+
if err != nil {
76+
panic(err)
77+
}
78+
79+
return &Server{
80+
db: database,
81+
}
82+
}
83+
84+
func (s *Server) queryDb(w http.ResponseWriter, req *http.Request) {
85+
ctx := req.Context()
86+
87+
conn, err := s.db.Conn(ctx)
88+
89+
if err != nil {
90+
panic(err)
91+
}
92+
93+
rows, err := conn.QueryContext(req.Context(), sqlQuery)
94+
if err != nil {
95+
panic(err)
96+
}
97+
98+
logger.Info("queryDb called")
99+
for rows.Next() {
100+
var id int
101+
var firstName string
102+
var lastName string
103+
var email string
104+
var phone string
105+
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
106+
if err != nil {
107+
panic(err)
108+
}
109+
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
110+
}
111+
}
112+
113+
var logger *zap.Logger
114+
115+
func setupHandler(s *Server) *http.ServeMux {
116+
mux := http.NewServeMux()
117+
mux.HandleFunc("/query_db", s.queryDb)
118+
return mux
119+
}
120+
121+
func main() {
122+
var err error
123+
logger, err = zap.NewDevelopment()
124+
if err != nil {
125+
fmt.Printf("error creating zap logger, error:%v", err)
126+
return
127+
}
128+
port := fmt.Sprintf(":%d", 8080)
129+
logger.Info("starting http server", zap.String("port", port))
130+
131+
s := NewServer()
132+
mux := setupHandler(s)
133+
if err := http.ListenAndServe(port, mux); err != nil {
134+
logger.Error("error running server", zap.Error(err))
135+
}
136+
}

pkg/inject/injector.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ type StructField struct {
6767
Field string
6868
}
6969

70+
// FlagField is used for configuring the ebpf programs by injecting boolean values.
71+
type FlagField struct {
72+
VarName string
73+
Value bool
74+
}
75+
7076
// Inject injects instrumentation for the provided library data type.
71-
func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string, fields []*StructField, initAlloc bool) (*ebpf.CollectionSpec, error) {
77+
func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string, fields []*StructField, flagFields []*FlagField, initAlloc bool) (*ebpf.CollectionSpec, error) {
7278
spec, err := loadBpf()
7379
if err != nil {
7480
return nil, err
@@ -88,6 +94,11 @@ func (i *Injector) Inject(loadBpf loadBpfFunc, library string, libVersion string
8894
if err := i.addCommonInjections(injectedVars, initAlloc); err != nil {
8995
return nil, fmt.Errorf("adding instrumenter injections: %w", err)
9096
}
97+
98+
if err := i.addConfigInjections(injectedVars, flagFields); err != nil {
99+
return nil, fmt.Errorf("adding flags injections: %w", err)
100+
}
101+
91102
log.Logger.V(0).Info("Injecting variables", "vars", injectedVars)
92103
if len(injectedVars) > 0 {
93104
err = spec.RewriteConstants(injectedVars)
@@ -112,6 +123,13 @@ func (i *Injector) addCommonInjections(varsMap map[string]interface{}, initAlloc
112123
return nil
113124
}
114125

126+
func (i *Injector) addConfigInjections(varsMap map[string]interface{}, flagFields []*FlagField) error {
127+
for _, dm := range flagFields {
128+
varsMap[dm.VarName] = dm.Value
129+
}
130+
return nil
131+
}
132+
115133
func (i *Injector) getFieldOffset(structName string, fieldName string, libVersion string) (uint64, bool) {
116134
strct, ok := i.data.Data[structName]
117135
if !ok {

0 commit comments

Comments
 (0)