Skip to content
This repository has been archived by the owner on Jun 14, 2023. It is now read-only.

feat(sql): add database/sql plugin #22

Merged
merged 34 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c0f2c05
add Driver.driver interface implements
kagaya85 Jul 5, 2021
fa07065
add Ping function
kagaya85 Jul 5, 2021
6063900
add parseAddr & fallback connector
kagaya85 Jul 6, 2021
82bd97b
add driver.Conn wrapper
kagaya85 Jul 6, 2021
f94c937
implements driver.Stmt & do some refactor
kagaya85 Jul 7, 2021
ad7b5a1
add fallback connector
kagaya85 Jul 7, 2021
ecd42a1
go mod tidy
kagaya85 Jul 7, 2021
1ba7cc0
add createSpan func for componentID & spanLayer
kagaya85 Jul 13, 2021
155fc08
add driver options
kagaya85 Jul 19, 2021
78b5c97
add query & param report options
kagaya85 Jul 20, 2021
e12962c
init e2e test file
kagaya85 Jul 20, 2021
495a9bd
add expected.data.yaml & sql test client
kagaya85 Jul 21, 2021
7e8e9f1
Merge branch 'master' into master
rainbend Jul 29, 2021
2e21a0d
update license header
kagaya85 Jul 30, 2021
8b58378
fix: duplicate span reporting when using conn.ExecContext & conn.Quer…
kagaya85 Aug 4, 2021
176bf7c
rename module name
kagaya85 Aug 5, 2021
4969ea2
Merge branch 'master' into master
rainbend Aug 6, 2021
ece35a9
Merge branch 'master' into master
rainbend Aug 10, 2021
30b6581
update error handle
kagaya85 Aug 10, 2021
11ab3bd
add sql.DB wrapper
kagaya85 Aug 10, 2021
84c65db
add tx/stmt/conn wrapper
kagaya85 Aug 10, 2021
4d9af1d
update license header & readme
kagaya85 Aug 10, 2021
df4bebf
bug fixed
kagaya85 Aug 10, 2021
0b53ecf
fix e2e fail
kagaya85 Aug 11, 2021
0ea71d8
update reademe & plugin_test
kagaya85 Aug 11, 2021
e6ae24e
Merge branch 'master' into master
kagaya85 Aug 11, 2021
139ad51
change tx span to localspan
kagaya85 Aug 18, 2021
ea33fb9
Merge branch master
kagaya85 Aug 23, 2021
bffebd7
ci: add checkout for paths-filter
kagaya85 Aug 23, 2021
a7353c2
Merge branch 'ci-filter'
kagaya85 Aug 23, 2021
6fef37b
ci: fix empty error
kagaya85 Aug 23, 2021
987d946
Merge branch 'ci-filter'
kagaya85 Aug 23, 2021
35afb74
Merge branch 'master' into master
rainbend Aug 23, 2021
46c206d
add tx.Exec test
kagaya85 Aug 25, 2021
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The plugins of [go2sky](https://github.com/SkyAPM/go2sky)
1. [go-micro](micro/README.md)
1. [go-restful](go-restful/README.md)
1. [go-kratos](kratos/README.md)
1. [sql](sql/README.md)

### Log Plugins
1. [logrus](logrus/README.md)
Expand Down
42 changes: 42 additions & 0 deletions sql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Go2Sky with database/sql

## Installation

```bash
go get -u github.com/SkyAPM/go2sky-plugins/sql
```

## Usage

```go
import (
sqlPlugin "github.com/SkyAPM/go2sky-plugins/sql"

"github.com/SkyAPM/go2sky"
"github.com/SkyAPM/go2sky/reporter"
_ "github.com/go-sql-driver/mysql"
)

// init reporter
re, err := reporter.NewLogReporter()
defer re.Close()

// init tracer
tracer, err := go2sky.NewTracer("service-name", go2sky.WithReporter(re))
if err != nil {
log.Fatalf("init tracer error: %v", err)
}

// use sql plugin to open db with tracer
db, err := sqlPlugin.Open("mysql", dsn, tracer,
sqlPlugin.WithSqlDBType(sqlPlugin.MYSQL),
sqlPlugin.WithQueryReport(),
sqlPlugin.WithParamReport(),
sqlPlugin.WithPeerAddr("127.0.0.1:3306"),
)
if err != nil {
log.Fatalf("open db error: %v \n", err)
}

// use db handler as usual.
```
121 changes: 121 additions & 0 deletions sql/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// Copyright 2021 SkyAPM org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package sql

import (
"context"
"database/sql/driver"
"errors"
"fmt"
"regexp"
"strings"

"github.com/SkyAPM/go2sky"
agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
)

const (
componentIDUnknown = 0
componentIDMysql = 5012
)

const (
tagDbType = "db.type"
tagDbInstance = "db.instance"
tagDbStatement = "db.statement"
tagDbSqlParameters = "db.sql.parameters"
)

var ErrUnsupportedOp = errors.New("operation unsupported by the underlying driver")

// namedValueToValueString converts driver arguments of NamedValue format to Value string format.
func namedValueToValueString(named []driver.NamedValue) string {
b := make([]string, 0, len(named))
for _, param := range named {
b = append(b, fmt.Sprintf("%v", param.Value))
}
return strings.Join(b, ",")
}

// namedValueToValue converts driver arguments of NamedValue format to Value format.
// Implemented in the same way as in database/sql/ctxutil.go.
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
return nil, errors.New("sql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}

func argsToString(args []interface{}) string {
sb := strings.Builder{}
for _, arg := range args {
sb.WriteString(fmt.Sprintf("%v, ", arg))
}
return sb.String()
}

func createSpan(ctx context.Context, tracer *go2sky.Tracer, opts *options, operation string) (go2sky.Span, error) {
s, _, err := tracer.CreateLocalSpan(ctx,
go2sky.WithSpanType(go2sky.SpanTypeExit),
go2sky.WithOperationName(opts.getOpName(operation)),
)
if err != nil {
return nil, err
}
s.SetPeer(opts.peer)
s.SetComponent(opts.componentID)
s.SetSpanLayer(agentv3.SpanLayer_Database)
s.Tag(tagDbType, string(opts.dbType))
s.Tag(tagDbInstance, opts.peer)
return s, nil
}

func createLocalSpan(ctx context.Context, tracer *go2sky.Tracer, opts *options, operation string) (go2sky.Span, context.Context, error) {
s, nCtx, err := tracer.CreateLocalSpan(ctx,
go2sky.WithSpanType(go2sky.SpanTypeLocal),
go2sky.WithOperationName(opts.getOpName(operation)),
)
if err != nil {
return nil, nil, err
}
s.SetComponent(opts.componentID)
s.SetSpanLayer(agentv3.SpanLayer_Database)
s.Tag(tagDbType, string(opts.dbType))
s.Tag(tagDbInstance, opts.peer)
return s, nCtx, nil
}

// parseDsn parse dsn to a endpoint addr string (host:port)
func parseDsn(dbType DBType, dsn string) string {
var addr string
switch dbType {
case MYSQL:
// [user[:password]@][net[(addr)]]/dbname[?param1=value1&paramN=valueN]
re := regexp.MustCompile(`\(.+\)`)
addr = re.FindString(dsn)
addr = addr[1 : len(addr)-1]
case IPV4:
// ipv4 addr
re := regexp.MustCompile(`((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}:\d{1,5}`)
addr = re.FindString(dsn)
}
return addr
}
132 changes: 132 additions & 0 deletions sql/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// Copyright 2021 SkyAPM org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package sql

import (
"context"
"database/sql"
"time"
)

type Conn struct {
*sql.Conn

db *DB
}

func (c *Conn) PingContext(ctx context.Context) error {
span, err := createSpan(ctx, c.db.tracer, c.db.opts, "ping")
if err != nil {
return err
}
defer span.End()
err = c.Conn.PingContext(ctx)
if err != nil {
span.Error(time.Now(), err.Error())
}
return err
}

func (c *Conn) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
span, err := createSpan(ctx, c.db.tracer, c.db.opts, "execute")
if err != nil {
return nil, err
}
defer span.End()

if c.db.opts.reportQuery {
span.Tag(tagDbStatement, query)
}
if c.db.opts.reportParam {
span.Tag(tagDbSqlParameters, argsToString(args))
}

res, err := c.Conn.ExecContext(ctx, query, args...)
if err != nil {
span.Error(time.Now(), err.Error())
}
return res, err
}

func (c *Conn) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
span, err := createSpan(ctx, c.db.tracer, c.db.opts, "query")
if err != nil {
return nil, err
}
defer span.End()

if c.db.opts.reportQuery {
span.Tag(tagDbStatement, query)
}
if c.db.opts.reportParam {
span.Tag(tagDbSqlParameters, argsToString(args))
}

rows, err := c.Conn.QueryContext(ctx, query, args...)
if err != nil {
span.Error(time.Now(), err.Error())
}
return rows, err
}

func (c *Conn) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
span, err := createSpan(ctx, c.db.tracer, c.db.opts, "query")
if err != nil {
return nil
}
defer span.End()

if c.db.opts.reportQuery {
span.Tag(tagDbStatement, query)
}
if c.db.opts.reportParam {
span.Tag(tagDbSqlParameters, argsToString(args))
}

return c.Conn.QueryRowContext(ctx, query, args...)
}

func (c *Conn) PrepareContext(ctx context.Context, query string) (*Stmt, error) {
stmt, err := c.Conn.PrepareContext(ctx, query)
return &Stmt{
Stmt: stmt,
db: c.db,
query: query,
}, err
}

func (c *Conn) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
span, nCtx, err := createLocalSpan(ctx, c.db.tracer, c.db.opts, "transaction")
if err != nil {
return nil, err
}

tx, err := c.Conn.BeginTx(ctx, opts)
if err != nil {
span.Error(time.Now(), err.Error())
span.End()
return nil, err
}

return &Tx{
Tx: tx,
db: c.db,
span: span,
ctx: nCtx,
}, nil

}
Loading