Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SELECT/INSERT/UPDATE/DELETE db operation + span names #1253

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/e2e/k8s/sample-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ spec:
value: "tracecontext,baggage"
- name: OTEL_GO_AUTO_INCLUDE_DB_STATEMENT
value: "true"
- name: OTEL_GO_AUTO_INCLUDE_DB_OPERATION
value: "true"
- name: OTEL_BSP_SCHEDULE_DELAY
value: "60000"
resources: {}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
- Support `google.golang.org/grpc` `1.68.0`. ([#1251](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1251))
- Support `golang.org/x/net` `0.31.0`. ([#1254](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1254))
- Support `go.opentelemetry.io/[email protected]`. ([#1302](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1302))
- Support `SELECT`, `INSERT`, `UPDATE`, and `DELETE` for database span names and `db.operation.name` attribute. ([#1253](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1253))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Alternatively, you can add support for additional or different configurations by
| Environment variable | Description | Default value |
|-------------------------------------|--------------------------------------------------------|---------------|
| `OTEL_GO_AUTO_INCLUDE_DB_STATEMENT` | Sets whether to include SQL queries in the trace data. | |
| `OTEL_GO_AUTO_INCLUDE_DB_OPERATION` | Sets whether to include SQL operation in the trace data. | |

## Traces exporter

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/hashicorp/go-version v1.7.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we instead use the code this was based on: https://github.com/vitessio/vitess/tree/main/go/vt/sqlparser

It seems like this was a fork of that repository1, but it hasn't been maintained or synced since that fork.

Would it be too large of a dependency to just rely on vitess directly?

Footnotes

  1. https://github.com/xwb1989/sqlparser?tab=readme-ov-file#notice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did think about using vitess directly, but it seemed like a lot more than what we needed. Since this is just an implementation detail, we could refactor it later if we want but I think this is the best option for right now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned when looking through all the changes that have been applied to the upstream code over the past 6 years, there's a considerable amount of changes. Notably:

Should we make our own fork of this pacakge?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we maybe get a better understanding of dependency size by looking at what the binary size is based on the possible dependencies?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll admit it looks like you've dug into it deeper than I did, hah. These are some great points so maybe that is the better approach. I'll try switching it to that

go.opentelemetry.io/collector/pdata v1.19.0
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0
go.opentelemetry.io/otel v1.32.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/collector/pdata v1.19.0 h1:jmnU5R8TOCbwRr4B8sjdRxM7L5WnEKlQWX1dtLYxIbE=
Expand Down
31 changes: 31 additions & 0 deletions internal/pkg/instrumentation/bpf/database/sql/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os"
"strconv"

sql "github.com/xwb1989/sqlparser"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through a couple sql parsing libraries, this isn't the most maintained... https://github.com/krasun/gosqlparser seems good but for some reason I don't think it supports * in queries (??). For what we're doing now I think it's fine but this could use another look some day


"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
Expand All @@ -27,6 +29,9 @@ const (

// IncludeDBStatementEnvVar is the environment variable to opt-in for sql query inclusion in the trace.
IncludeDBStatementEnvVar = "OTEL_GO_AUTO_INCLUDE_DB_STATEMENT"

// IncludeDBOperationEnvVar is the environment variable to opt-in for sql query operation in the trace.
IncludeDBOperationEnvVar = "OTEL_GO_AUTO_INCLUDE_DB_OPERATION"
MrAlias marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call this OTEL_GO_AUTO_PARSE_DB_STATEMENT instead? Give we are going to use the parsed semantics for more than just the operation it might help better convey the intention.

)

// New returns a new [probe.Probe].
Expand Down Expand Up @@ -97,6 +102,32 @@ func processFn(e *event) ptrace.SpanSlice {
span.Attributes().PutStr(string(semconv.DBQueryTextKey), query)
}

includeOperationVal := os.Getenv(IncludeDBOperationEnvVar)
if includeOperationVal != "" {
include, err := strconv.ParseBool(includeOperationVal)
if err == nil && include {
q, err := sql.Parse(query)
if err == nil {
operation := ""
switch q.(type) {
case *sql.Select:
operation = "SELECT"
case *sql.Update:
operation = "UPDATE"
case *sql.Insert:
operation = "INSERT"
case *sql.Delete:
operation = "DELETE"
}

if operation != "" {
span.Attributes().PutStr(string(semconv.DBOperationNameKey), operation)
span.SetName(operation)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we try to build the {db.query.summary} or {db.operation.name} {target} here given we have already parsed the query?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally doing db.query.summary, but we don't have any of the values for {target} (db.collection, db.namespace, or server address). So, it recommends just using the operation:

If a corresponding {target} value is not available for a specific operation, the instrumentation SHOULD omit the {target}. For example, for an operation describing SQL query on an anonymous table like SELECT * FROM (SELECT * FROM table) t, span name should be SELECT.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table might be defined in the query statement though, right? The query parsing library should be able to get this, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I could find, unfortunately :\ The first library I tried (https://github.com/krasun/gosqlparser) does have that, which is how I was building it at first. But that library breaks when it tries to parse a * character (like SELECT * FROM). Which makes no sense to me, but that's my understanding from trying to debug it and read that library's code. I'm open to add a TODO here for this though because maybe I'm just missing something

Copy link
Contributor

@MrAlias MrAlias Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should work, right?

package main

import (
	"fmt"

	"github.com/xwb1989/sqlparser"
)

// Parse takes a SQL query string and returns the parsed query statement type
// and table name, or an error if parsing failed.
func Parse(query string) (string, string, error) {
	stmt, err := sqlparser.Parse(query)
	if err != nil {
		return "", "", fmt.Errorf("failed to parse query: %w", err)
	}

	switch stmt := stmt.(type) {
	case *sqlparser.Select:
		return "SELECT", getTableName(stmt.From), nil
	case *sqlparser.Update:
		return "UPDATE", getTableName(stmt.TableExprs), nil
	case *sqlparser.Insert:
		return "INSERT", stmt.Table.Name.String(), nil
	case *sqlparser.Delete:
		return "DELETE", getTableName(stmt.TableExprs), nil
	default:
		return "UNKNOWN", "", fmt.Errorf("unsupported statement type")
	}
}

// getTableName extracts the table name from a SQL node.
func getTableName(node sqlparser.SQLNode) string {
	switch tableExpr := node.(type) {
	case sqlparser.TableName:
		return tableExpr.Name.String()
	case sqlparser.TableExprs:
		for _, expr := range tableExpr {
			if tableName, ok := expr.(*sqlparser.AliasedTableExpr); ok {
				if name, ok := tableName.Expr.(sqlparser.TableName); ok {
					return name.Name.String()
				}
			}
		}
	}
	return ""
}

func main() {
	queries := []string{
		"SELECT * FROM users WHERE id = 1",
		"SELECT id, name FROM users WHERE id = 1",
		"INSERT INTO orders (id, amount) VALUES (1, 100)",
		"UPDATE products SET price = 19.99 WHERE id = 10",
		"DELETE FROM sessions WHERE expired = true",
		"CREATE TABLE logs (id INT, message TEXT)",
	}
	for _, query := range queries {
		fmt.Println("Query: ", query)
		statement, table, err := Parse(query)
		if err != nil {
			fmt.Println("Error:", err)
			continue
		}
		fmt.Printf("Statement: %s, Table: %s\n", statement, table)
	}
}
$ go run .
Query:  SELECT * FROM users WHERE id = 1
Statement: SELECT, Table: users
Query:  SELECT id, name FROM users WHERE id = 1
Statement: SELECT, Table: users
Query:  INSERT INTO orders (id, amount) VALUES (1, 100)
Statement: INSERT, Table: orders
Query:  UPDATE products SET price = 19.99 WHERE id = 10
Statement: UPDATE, Table: products
Query:  DELETE FROM sessions WHERE expired = true
Statement: DELETE, Table: sessions
Query:  CREATE TABLE logs (id INT, message TEXT)
Error: unsupported statement type

FWIW, looking at vitess, it seems like it should be able to support more statement types in the table lookup (i.e. table create).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using "vitess.io/vitess/go/vt/sqlparser":

package main

import (
	"fmt"

	"vitess.io/vitess/go/vt/sqlparser"
)

// Parse takes a SQL query string and returns the parsed query statement type
// and table name(s), or an error if parsing failed.
func Parse(query string) (string, []string, error) {
	p, err := sqlparser.New(sqlparser.Options{})
	if err != nil {
		return "", nil, fmt.Errorf("failed to create parser: %w", err)
	}

	stmt, err := p.Parse(query)
	if err != nil {
		return "", nil, fmt.Errorf("failed to parse query: %w", err)
	}

	var statementType string
	var tables []string

	switch stmt := stmt.(type) {
	case *sqlparser.Select:
		statementType = "SELECT"
		tables = extractTables(stmt.From)
	case *sqlparser.Update:
		statementType = "UPDATE"
		tables = extractTables(stmt.TableExprs)
	case *sqlparser.Insert:
		statementType = "INSERT"
		tables = []string{stmt.Table.TableNameString()}
	case *sqlparser.Delete:
		statementType = "DELETE"
		tables = extractTables(stmt.TableExprs)
	case *sqlparser.CreateTable:
		statementType = "CREATE TABLE"
		tables = []string{stmt.Table.Name.String()}
	case *sqlparser.AlterTable:
		statementType = "ALTER TABLE"
		tables = []string{stmt.Table.Name.String()}
	case *sqlparser.DropTable:
		statementType = "DROP TABLE"
		for _, table := range stmt.FromTables {
			tables = append(tables, table.Name.String())
		}
	case *sqlparser.CreateDatabase:
		statementType = "CREATE DATABASE"
		tables = []string{stmt.DBName.String()}
	case *sqlparser.DropDatabase:
		statementType = "DROP DATABASE"
		tables = []string{stmt.DBName.String()}
	case *sqlparser.TruncateTable:
		statementType = "TRUNCATE TABLE"
		tables = []string{stmt.Table.Name.String()}
	default:
		return "UNKNOWN", nil, fmt.Errorf("unsupported statement type")
	}

	return statementType, tables, nil
}

// extractTables extracts table names from a list of SQL nodes.
func extractTables(exprs sqlparser.TableExprs) []string {
	var tables []string
	for _, expr := range exprs {
		switch tableExpr := expr.(type) {
		case *sqlparser.AliasedTableExpr:
			if name, ok := tableExpr.Expr.(sqlparser.TableName); ok {
				tables = append(tables, name.Name.String())
			}
		}
	}
	return tables
}

func main() {
	queries := []string{
		"SELECT * FROM users WHERE id = 1",
		"SELECT id, name FROM users WHERE id = 1",
		"INSERT INTO users (id, name) VALUES (1, 'Alice')",
		"UPDATE users SET name = 'Bob' WHERE id = 1",
		"DELETE FROM users WHERE id = 1",
		"CREATE TABLE users (id INT, name VARCHAR(100))",
		"ALTER TABLE users ADD COLUMN age INT",
		"DROP TABLE users",
		"CREATE DATABASE test_db",
		"DROP DATABASE test_db",
		"TRUNCATE TABLE users",
	}

	for _, query := range queries {
		fmt.Println("Query: ", query)
		statement, tables, err := Parse(query)
		if err != nil {
			fmt.Printf("Error parsing query: %s\nQuery: %s\n\n", err, query)
			continue
		}
		fmt.Printf("Statement: %s, Tables/DBs: %v\n", statement, tables)
	}
}
$ go run .
Query:  SELECT * FROM users WHERE id = 1
Statement: SELECT, Tables/DBs: [users]
Query:  SELECT id, name FROM users WHERE id = 1
Statement: SELECT, Tables/DBs: [users]
Query:  INSERT INTO users (id, name) VALUES (1, 'Alice')
Statement: INSERT, Tables/DBs: [users]
Query:  UPDATE users SET name = 'Bob' WHERE id = 1
Statement: UPDATE, Tables/DBs: [users]
Query:  DELETE FROM users WHERE id = 1
Statement: DELETE, Tables/DBs: [users]
Query:  CREATE TABLE users (id INT, name VARCHAR(100))
Statement: CREATE TABLE, Tables/DBs: [users]
Query:  ALTER TABLE users ADD COLUMN age INT
Statement: ALTER TABLE, Tables/DBs: [users]
Query:  DROP TABLE users
Statement: DROP TABLE, Tables/DBs: [users]
Query:  CREATE DATABASE test_db
Statement: CREATE DATABASE, Tables/DBs: [test_db]
Query:  DROP DATABASE test_db
Statement: DROP DATABASE, Tables/DBs: [test_db]
Query:  TRUNCATE TABLE users
Statement: TRUNCATE TABLE, Tables/DBs: [users]

}
}
}
}

return spans
}

Expand Down
58 changes: 56 additions & 2 deletions internal/pkg/instrumentation/bpf/database/sql/probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package sql

import (
"os"
"testing"
"time"

Expand All @@ -18,7 +19,60 @@ import (
"go.opentelemetry.io/auto/internal/pkg/instrumentation/utils"
)

func BenchmarkProcessFn(b *testing.B) {
tests := []struct {
name string
query string
}{
{
name: "no query (baseline)",
query: "",
},
{
name: "simple query",
query: "SELECT * FROM customers",
},
{
name: "medium query",
query: "SELECT * FROM customers WHERE first_name='Mike' AND last_name IN ('Santa', 'Banana')",
},
{
name: "hard query",
query: "WITH (SELECT last_name FROM customers WHERE first_name='Mike' AND country='North Pole') AS test_table SELECT * FROM test_table WHERE first_name='Mike' AND last_name IN ('Santa', 'Banana')",
},
}

start := time.Unix(0, time.Now().UnixNano()) // No wall clock.
end := start.Add(1 * time.Second)

startOffset := utils.TimeToBootOffset(start)
endOffset := utils.TimeToBootOffset(end)

traceID := trace.TraceID{1}
spanID := trace.SpanID{1}

for _, t := range tests {
b.Run(t.name, func(b *testing.B) {
var byteQuery [256]byte
copy(byteQuery[:], []byte(t.query))
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = processFn(&event{
BaseSpanProperties: context.BaseSpanProperties{
StartTime: startOffset,
EndTime: endOffset,
SpanContext: context.EBPFSpanContext{TraceID: traceID, SpanID: spanID},
},
Query: byteQuery,
})
}
})
}
}

func TestProbeConvertEvent(t *testing.T) {
err := os.Setenv(IncludeDBOperationEnvVar, "true")
assert.NoError(t, err)
start := time.Unix(0, time.Now().UnixNano()) // No wall clock.
end := start.Add(1 * time.Second)

Expand All @@ -41,14 +95,14 @@ func TestProbeConvertEvent(t *testing.T) {
want := func() ptrace.SpanSlice {
spans := ptrace.NewSpanSlice()
span := spans.AppendEmpty()
span.SetName("DB")
span.SetName("SELECT")
span.SetKind(ptrace.SpanKindClient)
span.SetStartTimestamp(utils.BootOffsetToTimestamp(startOffset))
span.SetEndTimestamp(utils.BootOffsetToTimestamp(endOffset))
span.SetTraceID(pcommon.TraceID(traceID))
span.SetSpanID(pcommon.SpanID(spanID))
span.SetFlags(uint32(trace.FlagsSampled))
utils.Attributes(span.Attributes(), semconv.DBQueryText("SELECT * FROM foo"))
utils.Attributes(span.Attributes(), semconv.DBQueryText("SELECT * FROM foo"), semconv.DBOperationName("SELECT"))
return spans
}()
assert.Equal(t, want, got)
Expand Down
164 changes: 164 additions & 0 deletions internal/test/e2e/databasesql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,118 @@ func (s *Server) queryDb(w http.ResponseWriter, req *http.Request) {
}
}

func (s *Server) insert(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "INSERT INTO contacts (first_name) VALUES ('Mike')")
if err != nil {
panic(err)
}

logger.Info("insert called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) update(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "UPDATE contacts SET last_name = 'Santa' WHERE first_name = 'Mike'")
if err != nil {
panic(err)
}

logger.Info("update called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) delete(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "DELETE FROM contacts WHERE first_name = 'Mike'")
if err != nil {
panic(err)
}

logger.Info("delete called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) drop(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "DROP TABLE contacts")
if err != nil {
panic(err)
}

logger.Info("drop called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

var logger *zap.Logger

func main() {
Expand All @@ -115,6 +227,10 @@ func main() {
s := NewServer()

http.HandleFunc("/query_db", s.queryDb)
http.HandleFunc("/insert", s.insert)
http.HandleFunc("/update", s.update)
http.HandleFunc("/delete", s.delete)
http.HandleFunc("/drop", s.drop)
go func() {
_ = http.ListenAndServe(":8080", nil)
}()
Expand All @@ -134,6 +250,54 @@ func main() {
logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/insert")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/update")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/delete")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/drop")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

// give time for auto-instrumentation to report signal
time.Sleep(5 * time.Second)
}
Loading
Loading