Compare commits
2 Commits
v4.1.10
...
b6d2d459c5
| Author | SHA1 | Date | |
|---|---|---|---|
| b6d2d459c5 | |||
| c2d5dd4ffd |
@@ -3,16 +3,14 @@ name: coverage
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main, v3, v4 ]
|
branches: [ main, v3, v4 ]
|
||||||
paths-ignore:
|
|
||||||
- '.github/**'
|
|
||||||
- '.gitea/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, v3, v4 ]
|
branches: [ main, v3, v4 ]
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
if: github.server_url != 'https://github.com'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: checkout code
|
- name: checkout code
|
||||||
@@ -41,8 +39,8 @@ jobs:
|
|||||||
name: autocommit
|
name: autocommit
|
||||||
with:
|
with:
|
||||||
commit_message: Apply Code Coverage Badge
|
commit_message: Apply Code Coverage Badge
|
||||||
skip_fetch: false
|
skip_fetch: true
|
||||||
skip_checkout: false
|
skip_checkout: true
|
||||||
file_pattern: ./README.md
|
file_pattern: ./README.md
|
||||||
|
|
||||||
- name: push
|
- name: push
|
||||||
@@ -3,10 +3,10 @@ name: lint
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
branches: [ master, v3, v4 ]
|
branches:
|
||||||
paths-ignore:
|
- master
|
||||||
- '.github/**'
|
- v3
|
||||||
- '.gitea/**'
|
- v4
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
@@ -24,6 +24,6 @@ jobs:
|
|||||||
- name: setup deps
|
- name: setup deps
|
||||||
run: go get -v ./...
|
run: go get -v ./...
|
||||||
- name: run lint
|
- name: run lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: https://github.com/golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: 'latest'
|
version: 'latest'
|
||||||
@@ -3,12 +3,15 @@ name: test
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
branches: [ master, v3, v4 ]
|
branches:
|
||||||
|
- master
|
||||||
|
- v3
|
||||||
|
- v4
|
||||||
push:
|
push:
|
||||||
branches: [ master, v3, v4 ]
|
branches:
|
||||||
paths-ignore:
|
- master
|
||||||
- '.github/**'
|
- v3
|
||||||
- '.gitea/**'
|
- v4
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -3,12 +3,15 @@ name: test
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
branches: [ master, v3, v4 ]
|
branches:
|
||||||
|
- master
|
||||||
|
- v3
|
||||||
|
- v4
|
||||||
push:
|
push:
|
||||||
branches: [ master, v3, v4 ]
|
branches:
|
||||||
paths-ignore:
|
- master
|
||||||
- '.github/**'
|
- v3
|
||||||
- '.gitea/**'
|
- v4
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -32,19 +35,19 @@ jobs:
|
|||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
- name: setup go work
|
- name: setup go work
|
||||||
env:
|
env:
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: |
|
run: |
|
||||||
go work init
|
go work init
|
||||||
go work use .
|
go work use .
|
||||||
go work use micro-tests
|
go work use micro-tests
|
||||||
- name: setup deps
|
- name: setup deps
|
||||||
env:
|
env:
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: go get -v ./...
|
run: go get -v ./...
|
||||||
- name: run tests
|
- name: run tests
|
||||||
env:
|
env:
|
||||||
INTEGRATION_TESTS: yes
|
INTEGRATION_TESTS: yes
|
||||||
GOWORK: ${{ github.workspace }}/go.work
|
GOWORK: /workspace/${{ github.repository_owner }}/go.work
|
||||||
run: |
|
run: |
|
||||||
cd micro-tests
|
cd micro-tests
|
||||||
go test -mod readonly -v ./... || true
|
go test -mod readonly -v ./... || true
|
||||||
52
.github/workflows/job_sync.yml
vendored
52
.github/workflows/job_sync.yml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: sync
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '*/5 * * * *'
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync:
|
|
||||||
if: github.server_url != 'https://github.com'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: init
|
|
||||||
run: |
|
|
||||||
git config --global user.email "vtolstov <vtolstov@users.noreply.github.com>"
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
echo "machine git.unistack.org login vtolstov password ${{ secrets.TOKEN_GITEA }}" >> /root/.netrc
|
|
||||||
echo "machine github.com login vtolstov password ${{ secrets.TOKEN_GITHUB }}" >> /root/.netrc
|
|
||||||
|
|
||||||
- name: sync master
|
|
||||||
run: |
|
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch master --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
|
||||||
cd repo
|
|
||||||
git remote add --no-tags --fetch --track master upstream https://github.com/${GITHUB_REPOSITORY}
|
|
||||||
git merge upstream/master
|
|
||||||
git push upstream master --progress
|
|
||||||
git push origin master --progress
|
|
||||||
cd ../
|
|
||||||
rm -rf repo
|
|
||||||
|
|
||||||
- name: sync v3
|
|
||||||
run: |
|
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch v3 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
|
||||||
cd repo
|
|
||||||
git remote add --no-tags --fetch --track v3 upstream https://github.com/${GITHUB_REPOSITORY}
|
|
||||||
git merge upstream/v3
|
|
||||||
git push upstream v3 --progress
|
|
||||||
git push origin v3 --progress
|
|
||||||
cd ../
|
|
||||||
rm -rf repo
|
|
||||||
|
|
||||||
- name: sync v4
|
|
||||||
run: |
|
|
||||||
git clone --filter=blob:none --filter=tree:0 --branch v4 --single-branch ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY} repo
|
|
||||||
cd repo
|
|
||||||
git remote add --no-tags --fetch --track v4 upstream https://github.com/${GITHUB_REPOSITORY}
|
|
||||||
git merge upstream/v4
|
|
||||||
git push upstream v4 --progress
|
|
||||||
git push origin v4 --progress
|
|
||||||
cd ../
|
|
||||||
rm -rf repo
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
run:
|
run:
|
||||||
concurrency: 8
|
concurrency: 8
|
||||||
timeout: 5m
|
deadline: 5m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: true
|
tests: true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Micro
|
# Micro
|
||||||

|

|
||||||
[](https://opensource.org/licenses/Apache-2.0)
|
[](https://opensource.org/licenses/Apache-2.0)
|
||||||
[](https://pkg.go.dev/go.unistack.org/micro/v4?tab=overview)
|
[](https://pkg.go.dev/go.unistack.org/micro/v4?tab=overview)
|
||||||
[](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av4+event%3Apush)
|
[](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av4+event%3Apush)
|
||||||
|
|||||||
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Use this section to tell people about which versions of your project are
|
||||||
|
currently being supported with security updates.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 3.7.x | :white_check_mark: |
|
||||||
|
| < 3.7.0 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you find any issue, please create github issue in this repo
|
||||||
2
go.mod
2
go.mod
@@ -12,7 +12,6 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
|
||||||
github.com/spf13/cast v1.7.1
|
github.com/spf13/cast v1.7.1
|
||||||
github.com/stretchr/testify v1.10.0
|
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
go.uber.org/automaxprocs v1.6.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
go.unistack.org/micro-proto/v4 v4.1.0
|
go.unistack.org/micro-proto/v4 v4.1.0
|
||||||
@@ -27,6 +26,7 @@ require (
|
|||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ func NewClientCallWrapper(keys ...string) client.CallWrapper {
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Set(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ook {
|
if !ook {
|
||||||
@@ -59,8 +59,8 @@ func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{},
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range w.keys {
|
for _, k := range w.keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Set(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ook {
|
if !ook {
|
||||||
@@ -80,8 +80,8 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range w.keys {
|
for _, k := range w.keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Set(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ook {
|
if !ook {
|
||||||
@@ -103,8 +103,8 @@ func NewServerHandlerWrapper(keys ...string) server.HandlerWrapper {
|
|||||||
omd = metadata.New(len(imd))
|
omd = metadata.New(len(imd))
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
if v := imd.Get(k); v != nil {
|
if v, ok := imd.Get(k); ok {
|
||||||
omd.Set(k, v...)
|
omd.Add(k, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ook {
|
if !ook {
|
||||||
|
|||||||
@@ -37,9 +37,20 @@ var DefaultMetadataFunc = func(ctx context.Context) (context.Context, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if xid == "" {
|
if xid == "" {
|
||||||
xid = imd.GetJoined(DefaultMetadataKey)
|
var ids []string
|
||||||
if xid == "" {
|
if ids, iok = imd.Get(DefaultMetadataKey); iok {
|
||||||
xid = omd.GetJoined(DefaultMetadataKey)
|
for i := range ids {
|
||||||
|
if ids[i] != "" {
|
||||||
|
xid = ids[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ids, ook = omd.Get(DefaultMetadataKey); ook {
|
||||||
|
for i := range ids {
|
||||||
|
if ids[i] != "" {
|
||||||
|
xid = ids[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package requestid
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"slices"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.unistack.org/micro/v4/metadata"
|
"go.unistack.org/micro/v4/metadata"
|
||||||
@@ -25,10 +24,10 @@ func TestDefaultMetadataFunc(t *testing.T) {
|
|||||||
t.Fatalf("md missing in outgoing context")
|
t.Fatalf("md missing in outgoing context")
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := imd.Get(DefaultMetadataKey)
|
_, iok := imd.Get(DefaultMetadataKey)
|
||||||
ov := omd.Get(DefaultMetadataKey)
|
_, ook := omd.Get(DefaultMetadataKey)
|
||||||
|
|
||||||
if !slices.Equal(iv, ov) {
|
if !iok || !ook {
|
||||||
t.Fatalf("missing metadata key value %v != %v", iv, ov)
|
t.Fatalf("missing metadata key value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate sh -c "go run gen.go > wrap_gen.go"
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// namedValueToLabels convert driver arguments to interface{} slice
|
|
||||||
func namedValueToLabels(named []driver.NamedValue) []interface{} {
|
|
||||||
largs := make([]interface{}, 0, len(named)*2)
|
|
||||||
var name string
|
|
||||||
for _, param := range named {
|
|
||||||
if param.Name != "" {
|
|
||||||
name = param.Name
|
|
||||||
} else {
|
|
||||||
name = fmt.Sprintf("$%d", param.Ordinal)
|
|
||||||
}
|
|
||||||
largs = append(largs, fmt.Sprintf("%s=%v", name, param.Value))
|
|
||||||
}
|
|
||||||
return largs
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCallerName get the name of the function A where A() -> B() -> GetFunctionCallerName()
|
|
||||||
func getCallerName() string {
|
|
||||||
pc, _, _, ok := runtime.Caller(3)
|
|
||||||
details := runtime.FuncForPC(pc)
|
|
||||||
var callerName string
|
|
||||||
if ok && details != nil {
|
|
||||||
callerName = details.Name()
|
|
||||||
} else {
|
|
||||||
callerName = labelUnknown
|
|
||||||
}
|
|
||||||
return callerName
|
|
||||||
}
|
|
||||||
@@ -1,467 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql/driver"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.unistack.org/micro/v4/hooks/requestid"
|
|
||||||
"go.unistack.org/micro/v4/tracer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ driver.Conn = (*wrapperConn)(nil)
|
|
||||||
_ driver.ConnBeginTx = (*wrapperConn)(nil)
|
|
||||||
_ driver.ConnPrepareContext = (*wrapperConn)(nil)
|
|
||||||
_ driver.Pinger = (*wrapperConn)(nil)
|
|
||||||
_ driver.Validator = (*wrapperConn)(nil)
|
|
||||||
_ driver.Queryer = (*wrapperConn)(nil) // nolint:staticcheck
|
|
||||||
_ driver.QueryerContext = (*wrapperConn)(nil)
|
|
||||||
_ driver.Execer = (*wrapperConn)(nil) // nolint:staticcheck
|
|
||||||
_ driver.ExecerContext = (*wrapperConn)(nil)
|
|
||||||
// _ driver.Connector
|
|
||||||
// _ driver.Driver
|
|
||||||
// _ driver.DriverContext
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrapperConn defines a wrapper for driver.Conn
|
|
||||||
type wrapperConn struct {
|
|
||||||
d *wrapperDriver
|
|
||||||
dname string
|
|
||||||
conn driver.Conn
|
|
||||||
opts Options
|
|
||||||
ctx context.Context
|
|
||||||
//span tracer.Span
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements driver.Conn Close
|
|
||||||
func (w *wrapperConn) Close() error {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Close"}
|
|
||||||
ts := time.Now()
|
|
||||||
err := w.conn.Close()
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Close", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin implements driver.Conn Begin
|
|
||||||
func (w *wrapperConn) Begin() (driver.Tx, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
labels := []string{labelMethod, "Begin"}
|
|
||||||
ts := time.Now()
|
|
||||||
tx, err := w.conn.Begin() // nolint:staticcheck
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Begin", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Begin", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return &wrapperTx{tx: tx, opts: w.opts, ctx: ctx}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeginTx implements driver.ConnBeginTx BeginTx
|
|
||||||
func (w *wrapperConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
nctx, span := w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
span.AddLabels("db.method", "BeginTx")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "BeginTx", labelQuery, name}
|
|
||||||
|
|
||||||
connBeginTx, ok := w.conn.(driver.ConnBeginTx)
|
|
||||||
if !ok {
|
|
||||||
return w.Begin()
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
tx, err := connBeginTx.BeginTx(nctx, opts)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "BeginTx", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "BeginTx", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return &wrapperTx{tx: tx, opts: w.opts, ctx: ctx, span: span}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare implements driver.Conn Prepare
|
|
||||||
func (w *wrapperConn) Prepare(query string) (driver.Stmt, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Prepare", labelQuery, getCallerName()}
|
|
||||||
ts := time.Now()
|
|
||||||
stmt, err := w.conn.Prepare(query)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Prepare", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Prepare", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return wrapStmt(stmt, query, w.opts), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareContext implements driver.ConnPrepareContext PrepareContext
|
|
||||||
func (w *wrapperConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
|
||||||
var nctx context.Context
|
|
||||||
var span tracer.Span
|
|
||||||
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "PrepareContext")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "PrepareContext", labelQuery, name}
|
|
||||||
conn, ok := w.conn.(driver.ConnPrepareContext)
|
|
||||||
if !ok {
|
|
||||||
return w.Prepare(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
stmt, err := conn.PrepareContext(nctx, query)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "PrepareContext", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "PrepareContext", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return wrapStmt(stmt, query, w.opts), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec implements driver.Execer Exec
|
|
||||||
func (w *wrapperConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Exec", labelQuery, getCallerName()}
|
|
||||||
|
|
||||||
// nolint:staticcheck
|
|
||||||
conn, ok := w.conn.(driver.Execer)
|
|
||||||
if !ok {
|
|
||||||
return nil, driver.ErrSkip
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
res, err := conn.Exec(query, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Exec", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec implements driver.StmtExecContext ExecContext
|
|
||||||
func (w *wrapperConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
var nctx context.Context
|
|
||||||
var span tracer.Span
|
|
||||||
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "ExecContext")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
defer span.Finish()
|
|
||||||
if len(args) > 0 {
|
|
||||||
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "ExecContext", labelQuery, name}
|
|
||||||
|
|
||||||
conn, ok := w.conn.(driver.ExecerContext)
|
|
||||||
if !ok {
|
|
||||||
// nolint:staticcheck
|
|
||||||
return nil, driver.ErrSkip
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
res, err := conn.ExecContext(nctx, query, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping implements driver.Pinger Ping
|
|
||||||
func (w *wrapperConn) Ping(ctx context.Context) error {
|
|
||||||
conn, ok := w.conn.(driver.Pinger)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
// fallback path to check db alive
|
|
||||||
pc, err := w.d.Open(w.dname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var nctx context.Context //nolint:gosimple
|
|
||||||
nctx = ctx
|
|
||||||
/*
|
|
||||||
var span tracer.Span
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "Ping")
|
|
||||||
defer span.Finish()
|
|
||||||
*/
|
|
||||||
labels := []string{labelMethod, "Ping"}
|
|
||||||
ts := time.Now()
|
|
||||||
err := conn.Ping(nctx)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
// span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Ping", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query implements driver.Queryer Query
|
|
||||||
func (w *wrapperConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
// nolint:staticcheck
|
|
||||||
conn, ok := w.conn.(driver.Queryer)
|
|
||||||
if !ok {
|
|
||||||
return nil, driver.ErrSkip
|
|
||||||
}
|
|
||||||
|
|
||||||
labels := []string{labelMethod, "Query", labelQuery, getCallerName()}
|
|
||||||
ts := time.Now()
|
|
||||||
rows, err := conn.Query(query, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Query", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryContext implements Driver.QueryerContext QueryContext
|
|
||||||
func (w *wrapperConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
var nctx context.Context
|
|
||||||
var span tracer.Span
|
|
||||||
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "QueryContext")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
defer span.Finish()
|
|
||||||
if len(args) > 0 {
|
|
||||||
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "QueryContext", labelQuery, name}
|
|
||||||
conn, ok := w.conn.(driver.QueryerContext)
|
|
||||||
if !ok {
|
|
||||||
return nil, driver.ErrSkip
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
rows, err := conn.QueryContext(nctx, query, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckNamedValue implements driver.NamedValueChecker
|
|
||||||
func (w *wrapperConn) CheckNamedValue(v *driver.NamedValue) error {
|
|
||||||
s, ok := w.conn.(driver.NamedValueChecker)
|
|
||||||
if !ok {
|
|
||||||
return driver.ErrSkip
|
|
||||||
}
|
|
||||||
return s.CheckNamedValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValid implements driver.Validator
|
|
||||||
func (w *wrapperConn) IsValid() bool {
|
|
||||||
v, ok := w.conn.(driver.Validator)
|
|
||||||
if !ok {
|
|
||||||
return w.conn != nil
|
|
||||||
}
|
|
||||||
return v.IsValid()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapperConn) ResetSession(ctx context.Context) error {
|
|
||||||
s, ok := w.conn.(driver.SessionResetter)
|
|
||||||
if !ok {
|
|
||||||
return driver.ErrSkip
|
|
||||||
}
|
|
||||||
return s.ResetSession(ctx)
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql/driver"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// _ driver.DriverContext = (*wrapperDriver)(nil)
|
|
||||||
// _ driver.Connector = (*wrapperDriver)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
type conn interface {
|
|
||||||
driver.Pinger
|
|
||||||
driver.Execer
|
|
||||||
driver.ExecerContext
|
|
||||||
driver.Queryer
|
|
||||||
driver.QueryerContext
|
|
||||||
driver.Conn
|
|
||||||
driver.ConnPrepareContext
|
|
||||||
driver.ConnBeginTx
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// wrapperDriver defines a wrapper for driver.Driver
|
|
||||||
type wrapperDriver struct {
|
|
||||||
driver driver.Driver
|
|
||||||
opts Options
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWrapper creates and returns a new SQL driver with passed capabilities
|
|
||||||
func NewWrapper(d driver.Driver, opts ...Option) driver.Driver {
|
|
||||||
return &wrapperDriver{driver: d, opts: NewOptions(opts...), ctx: context.Background()}
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrappedConnector struct {
|
|
||||||
connector driver.Connector
|
|
||||||
// name string
|
|
||||||
opts Options
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWrapperConnector(c driver.Connector, opts ...Option) driver.Connector {
|
|
||||||
return &wrappedConnector{connector: c, opts: NewOptions(opts...), ctx: context.Background()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect implements driver.Driver Connect
|
|
||||||
func (w *wrappedConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
|
||||||
return w.connector.Connect(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Driver implements driver.Driver Driver
|
|
||||||
func (w *wrappedConnector) Driver() driver.Driver {
|
|
||||||
return w.connector.Driver()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Connect implements driver.Driver OpenConnector
|
|
||||||
func (w *wrapperDriver) OpenConnector(name string) (driver.Conn, error) {
|
|
||||||
return &wrapperConnector{driver: w.driver, name: name, opts: w.opts}, nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Open implements driver.Driver Open
|
|
||||||
func (w *wrapperDriver) Open(name string) (driver.Conn, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // Ensure eventual timeout
|
|
||||||
// defer cancel()
|
|
||||||
|
|
||||||
/*
|
|
||||||
connector, err := w.OpenConnector(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return connector.Connect(ctx)
|
|
||||||
*/
|
|
||||||
|
|
||||||
ts := time.Now()
|
|
||||||
c, err := w.driver.Open(name)
|
|
||||||
td := time.Since(ts)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled {
|
|
||||||
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Open", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
_ = td
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapConn(c, w.opts), nil
|
|
||||||
}
|
|
||||||
167
hooks/sql/gen.go
167
hooks/sql/gen.go
@@ -1,167 +0,0 @@
|
|||||||
//go:build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var connIfaces = []string{
|
|
||||||
"driver.ConnBeginTx",
|
|
||||||
"driver.ConnPrepareContext",
|
|
||||||
"driver.Execer",
|
|
||||||
"driver.ExecerContext",
|
|
||||||
"driver.NamedValueChecker",
|
|
||||||
"driver.Pinger",
|
|
||||||
"driver.Queryer",
|
|
||||||
"driver.QueryerContext",
|
|
||||||
"driver.SessionResetter",
|
|
||||||
"driver.Validator",
|
|
||||||
}
|
|
||||||
|
|
||||||
var stmtIfaces = []string{
|
|
||||||
"driver.StmtExecContext",
|
|
||||||
"driver.StmtQueryContext",
|
|
||||||
"driver.ColumnConverter",
|
|
||||||
"driver.NamedValueChecker",
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHash(s []string) string {
|
|
||||||
h := md5.New()
|
|
||||||
io.WriteString(h, strings.Join(s, "|"))
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
comboConn := all(connIfaces)
|
|
||||||
|
|
||||||
sort.Slice(comboConn, func(i, j int) bool {
|
|
||||||
return len(comboConn[i]) < len(comboConn[j])
|
|
||||||
})
|
|
||||||
|
|
||||||
comboStmt := all(stmtIfaces)
|
|
||||||
|
|
||||||
sort.Slice(comboStmt, func(i, j int) bool {
|
|
||||||
return len(comboStmt[i]) < len(comboStmt[j])
|
|
||||||
})
|
|
||||||
|
|
||||||
b := bytes.NewBuffer(nil)
|
|
||||||
b.WriteString("// Code generated. DO NOT EDIT.\n\n")
|
|
||||||
b.WriteString("package sql\n\n")
|
|
||||||
b.WriteString(`import "database/sql/driver"`)
|
|
||||||
b.WriteString("\n\n")
|
|
||||||
|
|
||||||
b.WriteString("func wrapConn(dc driver.Conn, opts Options) driver.Conn {\n")
|
|
||||||
b.WriteString("\tc := &wrapperConn{conn: dc, opts: opts}\n")
|
|
||||||
|
|
||||||
for idx := len(comboConn) - 1; idx >= 0; idx-- {
|
|
||||||
ifaces := comboConn[idx]
|
|
||||||
n := len(ifaces)
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
h := getHash(ifaces)
|
|
||||||
b.WriteString(fmt.Sprintf("\tif _, ok := dc.(wrapConn%04d_%s); ok {\n", n, h))
|
|
||||||
b.WriteString("\treturn struct {\n")
|
|
||||||
b.WriteString("\t\tdriver.Conn\n")
|
|
||||||
b.WriteString(fmt.Sprintf("\t\t\t%s", strings.Join(ifaces, "\n\t\t\t")))
|
|
||||||
b.WriteString("\t\t\n}{")
|
|
||||||
for idx := range ifaces {
|
|
||||||
if idx > 0 {
|
|
||||||
b.WriteString(", ")
|
|
||||||
b.WriteString("c")
|
|
||||||
} else if idx == 0 {
|
|
||||||
b.WriteString("c")
|
|
||||||
} else {
|
|
||||||
b.WriteString("c")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(", c}\n")
|
|
||||||
b.WriteString("}\n\n")
|
|
||||||
}
|
|
||||||
b.WriteString("return c\n")
|
|
||||||
b.WriteString("}\n")
|
|
||||||
|
|
||||||
for idx := len(comboConn) - 1; idx >= 0; idx-- {
|
|
||||||
ifaces := comboConn[idx]
|
|
||||||
n := len(ifaces)
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
h := getHash(ifaces)
|
|
||||||
b.WriteString(fmt.Sprintf("// %s\n", strings.Join(ifaces, "|")))
|
|
||||||
b.WriteString(fmt.Sprintf("type wrapConn%04d_%s interface {\n", n, h))
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
b.WriteString(fmt.Sprintf("\t%s\n", iface))
|
|
||||||
}
|
|
||||||
b.WriteString("}\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteString("func wrapStmt(stmt driver.Stmt, query string, opts Options) driver.Stmt {\n")
|
|
||||||
b.WriteString("\tc := &wrapperStmt{stmt: stmt, query: query, opts: opts}\n")
|
|
||||||
|
|
||||||
for idx := len(comboStmt) - 1; idx >= 0; idx-- {
|
|
||||||
ifaces := comboStmt[idx]
|
|
||||||
n := len(ifaces)
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
h := getHash(ifaces)
|
|
||||||
b.WriteString(fmt.Sprintf("\tif _, ok := stmt.(wrapStmt%04d_%s); ok {\n", n, h))
|
|
||||||
b.WriteString("\treturn struct {\n")
|
|
||||||
b.WriteString("\t\tdriver.Stmt\n")
|
|
||||||
b.WriteString(fmt.Sprintf("\t\t\t%s", strings.Join(ifaces, "\n\t\t\t")))
|
|
||||||
b.WriteString("\t\t\n}{")
|
|
||||||
for idx := range ifaces {
|
|
||||||
if idx > 0 {
|
|
||||||
b.WriteString(", ")
|
|
||||||
b.WriteString("c")
|
|
||||||
} else if idx == 0 {
|
|
||||||
b.WriteString("c")
|
|
||||||
} else {
|
|
||||||
b.WriteString("c")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteString(", c}\n")
|
|
||||||
b.WriteString("}\n\n")
|
|
||||||
}
|
|
||||||
b.WriteString("return c\n")
|
|
||||||
b.WriteString("}\n")
|
|
||||||
|
|
||||||
for idx := len(comboStmt) - 1; idx >= 0; idx-- {
|
|
||||||
ifaces := comboStmt[idx]
|
|
||||||
n := len(ifaces)
|
|
||||||
if n == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
h := getHash(ifaces)
|
|
||||||
b.WriteString(fmt.Sprintf("// %s\n", strings.Join(ifaces, "|")))
|
|
||||||
b.WriteString(fmt.Sprintf("type wrapStmt%04d_%s interface {\n", n, h))
|
|
||||||
for _, iface := range ifaces {
|
|
||||||
b.WriteString(fmt.Sprintf("\t%s\n", iface))
|
|
||||||
}
|
|
||||||
b.WriteString("}\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s\n", b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// all returns all combinations for a given string array.
|
|
||||||
func all[T any](set []T) (subsets [][]T) {
|
|
||||||
length := uint(len(set))
|
|
||||||
for subsetBits := 1; subsetBits < (1 << length); subsetBits++ {
|
|
||||||
var subset []T
|
|
||||||
for object := uint(0); object < length; object++ {
|
|
||||||
if (subsetBits>>object)&1 == 1 {
|
|
||||||
subset = append(subset, set[object])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subsets = append(subsets, subset)
|
|
||||||
}
|
|
||||||
return subsets
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.unistack.org/micro/v4/logger"
|
|
||||||
"go.unistack.org/micro/v4/meter"
|
|
||||||
"go.unistack.org/micro/v4/tracer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultMeterStatsInterval holds default stats interval
|
|
||||||
DefaultMeterStatsInterval = 5 * time.Second
|
|
||||||
// DefaultLoggerObserver used to prepare labels for logger
|
|
||||||
DefaultLoggerObserver = func(ctx context.Context, method string, query string, td time.Duration, err error) []interface{} {
|
|
||||||
labels := []interface{}{"db.method", method, "took", fmt.Sprintf("%v", td)}
|
|
||||||
if err != nil {
|
|
||||||
labels = append(labels, "error", err.Error())
|
|
||||||
}
|
|
||||||
if query != labelUnknown {
|
|
||||||
labels = append(labels, "query", query)
|
|
||||||
}
|
|
||||||
return labels
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
MaxOpenConnections = "micro_sql_max_open_conn"
|
|
||||||
OpenConnections = "micro_sql_open_conn"
|
|
||||||
InuseConnections = "micro_sql_inuse_conn"
|
|
||||||
IdleConnections = "micro_sql_idle_conn"
|
|
||||||
WaitConnections = "micro_sql_waited_conn"
|
|
||||||
BlockedSeconds = "micro_sql_blocked_seconds"
|
|
||||||
MaxIdleClosed = "micro_sql_max_idle_closed"
|
|
||||||
MaxIdletimeClosed = "micro_sql_closed_max_idle"
|
|
||||||
MaxLifetimeClosed = "micro_sql_closed_max_lifetime"
|
|
||||||
|
|
||||||
meterRequestTotal = "micro_sql_request_total"
|
|
||||||
meterRequestLatencyMicroseconds = "micro_sql_latency_microseconds"
|
|
||||||
meterRequestDurationSeconds = "micro_sql_request_duration_seconds"
|
|
||||||
|
|
||||||
labelUnknown = "unknown"
|
|
||||||
labelQuery = "db_statement"
|
|
||||||
labelMethod = "db_method"
|
|
||||||
labelStatus = "status"
|
|
||||||
labelSuccess = "success"
|
|
||||||
labelFailure = "failure"
|
|
||||||
labelHost = "db_host"
|
|
||||||
labelDatabase = "db_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options struct holds wrapper options
|
|
||||||
type Options struct {
|
|
||||||
Logger logger.Logger
|
|
||||||
Meter meter.Meter
|
|
||||||
Tracer tracer.Tracer
|
|
||||||
DatabaseHost string
|
|
||||||
DatabaseName string
|
|
||||||
MeterStatsInterval time.Duration
|
|
||||||
LoggerLevel logger.Level
|
|
||||||
LoggerEnabled bool
|
|
||||||
LoggerObserver func(ctx context.Context, method string, name string, td time.Duration, err error) []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option func signature
|
|
||||||
type Option func(*Options)
|
|
||||||
|
|
||||||
// NewOptions create new Options struct from provided option slice
|
|
||||||
func NewOptions(opts ...Option) Options {
|
|
||||||
options := Options{
|
|
||||||
Logger: logger.DefaultLogger,
|
|
||||||
Meter: meter.DefaultMeter,
|
|
||||||
Tracer: tracer.DefaultTracer,
|
|
||||||
MeterStatsInterval: DefaultMeterStatsInterval,
|
|
||||||
LoggerLevel: logger.ErrorLevel,
|
|
||||||
LoggerObserver: DefaultLoggerObserver,
|
|
||||||
}
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
options.Meter = options.Meter.Clone(
|
|
||||||
meter.Labels(
|
|
||||||
labelHost, options.DatabaseHost,
|
|
||||||
labelDatabase, options.DatabaseName,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
options.Logger = options.Logger.Clone(logger.WithAddCallerSkipCount(1))
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetricInterval specifies stats interval for *sql.DB
|
|
||||||
func MetricInterval(td time.Duration) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.MeterStatsInterval = td
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DatabaseHost(host string) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.DatabaseHost = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DatabaseName(name string) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.DatabaseName = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Meter passes meter.Meter to wrapper
|
|
||||||
func Meter(m meter.Meter) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.Meter = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logger passes logger.Logger to wrapper
|
|
||||||
func Logger(l logger.Logger) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.Logger = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggerEnabled enable sql logging
|
|
||||||
func LoggerEnabled(b bool) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.LoggerEnabled = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggerLevel passes logger.Level option
|
|
||||||
func LoggerLevel(lvl logger.Level) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.LoggerLevel = lvl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoggerObserver passes observer to fill logger fields
|
|
||||||
func LoggerObserver(obs func(context.Context, string, string, time.Duration, error) []interface{}) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.LoggerObserver = obs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tracer passes tracer.Tracer to wrapper
|
|
||||||
func Tracer(t tracer.Tracer) Option {
|
|
||||||
return func(o *Options) {
|
|
||||||
o.Tracer = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type queryNameKey struct{}
|
|
||||||
|
|
||||||
// QueryName passes query name to wrapper func
|
|
||||||
func QueryName(ctx context.Context, name string) context.Context {
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
return context.WithValue(ctx, queryNameKey{}, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQueryName(ctx context.Context) string {
|
|
||||||
if v, ok := ctx.Value(queryNameKey{}).(string); ok && v != labelUnknown {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return getCallerName()
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Statser interface {
|
|
||||||
Stats() sql.DBStats
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStatsMeter(ctx context.Context, db Statser, opts ...Option) {
|
|
||||||
options := NewOptions(opts...)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(options.MeterStatsInterval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
if db == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stats := db.Stats()
|
|
||||||
options.Meter.Counter(MaxOpenConnections).Set(uint64(stats.MaxOpenConnections))
|
|
||||||
options.Meter.Counter(OpenConnections).Set(uint64(stats.OpenConnections))
|
|
||||||
options.Meter.Counter(InuseConnections).Set(uint64(stats.InUse))
|
|
||||||
options.Meter.Counter(IdleConnections).Set(uint64(stats.Idle))
|
|
||||||
options.Meter.Counter(WaitConnections).Set(uint64(stats.WaitCount))
|
|
||||||
options.Meter.FloatCounter(BlockedSeconds).Set(stats.WaitDuration.Seconds())
|
|
||||||
options.Meter.Counter(MaxIdleClosed).Set(uint64(stats.MaxIdleClosed))
|
|
||||||
options.Meter.Counter(MaxIdletimeClosed).Set(uint64(stats.MaxIdleTimeClosed))
|
|
||||||
options.Meter.Counter(MaxLifetimeClosed).Set(uint64(stats.MaxLifetimeClosed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql/driver"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
requestid "go.unistack.org/micro/v4/hooks/requestid"
|
|
||||||
"go.unistack.org/micro/v4/tracer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ driver.Stmt = (*wrapperStmt)(nil)
|
|
||||||
_ driver.StmtQueryContext = (*wrapperStmt)(nil)
|
|
||||||
_ driver.StmtExecContext = (*wrapperStmt)(nil)
|
|
||||||
_ driver.NamedValueChecker = (*wrapperStmt)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrapperStmt defines a wrapper for driver.Stmt
|
|
||||||
type wrapperStmt struct {
|
|
||||||
stmt driver.Stmt
|
|
||||||
opts Options
|
|
||||||
query string
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements driver.Stmt Close
|
|
||||||
func (w *wrapperStmt) Close() error {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Close"}
|
|
||||||
ts := time.Now()
|
|
||||||
err := w.stmt.Close()
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Close", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumInput implements driver.Stmt NumInput
|
|
||||||
func (w *wrapperStmt) NumInput() int {
|
|
||||||
return w.stmt.NumInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckNamedValue implements driver.NamedValueChecker
|
|
||||||
func (w *wrapperStmt) CheckNamedValue(v *driver.NamedValue) error {
|
|
||||||
s, ok := w.stmt.(driver.NamedValueChecker)
|
|
||||||
if !ok {
|
|
||||||
return driver.ErrSkip
|
|
||||||
}
|
|
||||||
return s.CheckNamedValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec implements driver.Stmt Exec
|
|
||||||
func (w *wrapperStmt) Exec(args []driver.Value) (driver.Result, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Exec"}
|
|
||||||
ts := time.Now()
|
|
||||||
res, err := w.stmt.Exec(args) // nolint:staticcheck
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Exec", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query implements driver.Stmt Query
|
|
||||||
func (w *wrapperStmt) Query(args []driver.Value) (driver.Rows, error) {
|
|
||||||
var ctx context.Context
|
|
||||||
if w.ctx != nil {
|
|
||||||
ctx = w.ctx
|
|
||||||
} else {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
_ = ctx
|
|
||||||
labels := []string{labelMethod, "Query"}
|
|
||||||
ts := time.Now()
|
|
||||||
rows, err := w.stmt.Query(args) // nolint:staticcheck
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Query", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColumnConverter implements driver.ColumnConverter
|
|
||||||
func (w *wrapperStmt) ColumnConverter(idx int) driver.ValueConverter {
|
|
||||||
s, ok := w.stmt.(driver.ColumnConverter) // nolint:staticcheck
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return s.ColumnConverter(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecContext implements driver.StmtExecContext ExecContext
|
|
||||||
func (w *wrapperStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
var nctx context.Context
|
|
||||||
var span tracer.Span
|
|
||||||
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "ExecContext")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
defer span.Finish()
|
|
||||||
if len(args) > 0 {
|
|
||||||
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
|
||||||
}
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "ExecContext", labelQuery, name}
|
|
||||||
|
|
||||||
if conn, ok := w.stmt.(driver.StmtExecContext); ok {
|
|
||||||
ts := time.Now()
|
|
||||||
res, err := conn.ExecContext(nctx, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
values, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, 0, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ts := time.Now()
|
|
||||||
res, err := w.Exec(values) // nolint:staticcheck
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryContext implements driver.StmtQueryContext StmtQueryContext
|
|
||||||
func (w *wrapperStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
var nctx context.Context
|
|
||||||
var span tracer.Span
|
|
||||||
|
|
||||||
name := getQueryName(ctx)
|
|
||||||
if w.ctx != nil {
|
|
||||||
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
} else {
|
|
||||||
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
|
||||||
}
|
|
||||||
span.AddLabels("db.method", "QueryContext")
|
|
||||||
span.AddLabels("db.statement", name)
|
|
||||||
defer span.Finish()
|
|
||||||
if len(args) > 0 {
|
|
||||||
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
|
||||||
}
|
|
||||||
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
|
||||||
span.AddLabels("x-request-id", id)
|
|
||||||
}
|
|
||||||
labels := []string{labelMethod, "QueryContext", labelQuery, name}
|
|
||||||
if conn, ok := w.stmt.(driver.StmtQueryContext); ok {
|
|
||||||
ts := time.Now()
|
|
||||||
rows, err := conn.QueryContext(nctx, args)
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
values, err := namedValueToValue(args)
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, 0, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ts := time.Now()
|
|
||||||
rows, err := w.Query(values) // nolint:staticcheck
|
|
||||||
td := time.Since(ts)
|
|
||||||
te := td.Seconds()
|
|
||||||
if err != nil {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
|
||||||
span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
} else {
|
|
||||||
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
|
||||||
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql/driver"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.unistack.org/micro/v4/tracer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ driver.Tx = (*wrapperTx)(nil)
|
|
||||||
|
|
||||||
// wrapperTx defines a wrapper for driver.Tx
|
|
||||||
type wrapperTx struct {
|
|
||||||
tx driver.Tx
|
|
||||||
span tracer.Span
|
|
||||||
opts Options
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit implements driver.Tx Commit
|
|
||||||
func (w *wrapperTx) Commit() error {
|
|
||||||
ts := time.Now()
|
|
||||||
err := w.tx.Commit()
|
|
||||||
td := time.Since(ts)
|
|
||||||
_ = td
|
|
||||||
if w.span != nil {
|
|
||||||
if err != nil {
|
|
||||||
w.span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
}
|
|
||||||
w.span.Finish()
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Commit", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
w.ctx = nil
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback implements driver.Tx Rollback
|
|
||||||
func (w *wrapperTx) Rollback() error {
|
|
||||||
ts := time.Now()
|
|
||||||
err := w.tx.Rollback()
|
|
||||||
td := time.Since(ts)
|
|
||||||
_ = td
|
|
||||||
if w.span != nil {
|
|
||||||
if err != nil {
|
|
||||||
w.span.SetStatus(tracer.SpanStatusError, err.Error())
|
|
||||||
}
|
|
||||||
w.span.Finish()
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
|
||||||
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Rollback", getCallerName(), td, err)...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
w.ctx = nil
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package sql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
func wrapDriver(d driver.Driver, opts Options) driver.Driver {
|
|
||||||
if _, ok := d.(driver.DriverContext); ok {
|
|
||||||
return &wrapperDriver{driver: d, opts: opts}
|
|
||||||
}
|
|
||||||
return struct{ driver.Driver }{&wrapperDriver{driver: d, opts: opts}}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// WrapConn allows an existing driver.Conn to be wrapped.
|
|
||||||
func WrapConn(c driver.Conn, opts ...Option) driver.Conn {
|
|
||||||
return wrapConn(c, NewOptions(opts...))
|
|
||||||
}
|
|
||||||
20699
hooks/sql/wrap_gen.go
20699
hooks/sql/wrap_gen.go
File diff suppressed because it is too large
Load Diff
@@ -406,7 +406,7 @@ func TestLogger(t *testing.T) {
|
|||||||
func Test_WithContextAttrFunc(t *testing.T) {
|
func Test_WithContextAttrFunc(t *testing.T) {
|
||||||
loggerContextAttrFuncs := []logger.ContextAttrFunc{
|
loggerContextAttrFuncs := []logger.ContextAttrFunc{
|
||||||
func(ctx context.Context) []interface{} {
|
func(ctx context.Context) []interface{} {
|
||||||
md, ok := metadata.FromOutgoingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -425,7 +425,7 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
|||||||
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
|
logger.DefaultContextAttrFuncs = append(logger.DefaultContextAttrFuncs, loggerContextAttrFuncs...)
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
ctx = metadata.AppendOutgoingContext(ctx, "X-Request-Id", uuid.New().String(),
|
ctx = metadata.AppendIncomingContext(ctx, "X-Request-Id", uuid.New().String(),
|
||||||
"Source-Service", "Test-System")
|
"Source-Service", "Test-System")
|
||||||
|
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
@@ -445,9 +445,9 @@ func Test_WithContextAttrFunc(t *testing.T) {
|
|||||||
t.Fatalf("logger info, buf %s", buf.Bytes())
|
t.Fatalf("logger info, buf %s", buf.Bytes())
|
||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
omd, _ := metadata.FromOutgoingContext(ctx)
|
imd, _ := metadata.FromIncomingContext(ctx)
|
||||||
l.Info(ctx, "test message1")
|
l.Info(ctx, "test message1")
|
||||||
omd.Set("Source-Service", "Test-System2")
|
imd.Set("Source-Service", "Test-System2")
|
||||||
l.Info(ctx, "test message2")
|
l.Info(ctx, "test message2")
|
||||||
|
|
||||||
// t.Logf("xxx %s", buf.Bytes())
|
// t.Logf("xxx %s", buf.Bytes())
|
||||||
|
|||||||
@@ -69,15 +69,6 @@ func (md Metadata) Copy() Metadata {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsMap returns a copy of Metadata with map[string]string.
|
|
||||||
func (md Metadata) AsMap() map[string]string {
|
|
||||||
out := make(map[string]string, len(md))
|
|
||||||
for k, v := range md {
|
|
||||||
out[k] = strings.Join(v, ",")
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsHTTP1 returns a copy of Metadata
|
// AsHTTP1 returns a copy of Metadata
|
||||||
// with CanonicalMIMEHeaderKey.
|
// with CanonicalMIMEHeaderKey.
|
||||||
func (md Metadata) AsHTTP1() map[string][]string {
|
func (md Metadata) AsHTTP1() map[string][]string {
|
||||||
@@ -106,31 +97,65 @@ func (md Metadata) CopyTo(out Metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get obtains the values for a given key.
|
// Get obtains the values for a given key.
|
||||||
func (md Metadata) Get(k string) []string {
|
func (md Metadata) MustGet(k string) []string {
|
||||||
|
v, ok := md.Get(k)
|
||||||
|
if !ok {
|
||||||
|
panic("missing metadata key")
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get obtains the values for a given key.
|
||||||
|
func (md Metadata) Get(k string) ([]string, bool) {
|
||||||
v, ok := md[k]
|
v, ok := md[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
v, ok = md[strings.ToLower(k)]
|
v, ok = md[strings.ToLower(k)]
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
v = md[textproto.CanonicalMIMEHeaderKey(k)]
|
v, ok = md[textproto.CanonicalMIMEHeaderKey(k)]
|
||||||
|
}
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetJoined obtains the values for a given key
|
||||||
|
// with joined values with "," symbol
|
||||||
|
func (md Metadata) MustGetJoined(k string) string {
|
||||||
|
v, ok := md.GetJoined(k)
|
||||||
|
if !ok {
|
||||||
|
panic("missing metadata key")
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetJoined obtains the values for a given key
|
// GetJoined obtains the values for a given key
|
||||||
// with joined values with "," symbol
|
// with joined values with "," symbol
|
||||||
func (md Metadata) GetJoined(k string) string {
|
func (md Metadata) GetJoined(k string) (string, bool) {
|
||||||
return strings.Join(md.Get(k), ",")
|
v, ok := md.Get(k)
|
||||||
|
if !ok {
|
||||||
|
return "", ok
|
||||||
|
}
|
||||||
|
return strings.Join(v, ","), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the value of a given key with a slice of values.
|
// Set sets the value of a given key with a slice of values.
|
||||||
func (md Metadata) Set(key string, vals ...string) {
|
func (md Metadata) Add(key string, vals ...string) {
|
||||||
if len(vals) == 0 {
|
if len(vals) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
md[key] = vals
|
md[key] = vals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set sets the value of a given key with a slice of values.
|
||||||
|
func (md Metadata) Set(kvs ...string) {
|
||||||
|
if len(kvs)%2 == 1 {
|
||||||
|
panic(fmt.Sprintf("metadata: Set got an odd number of input pairs for metadata: %d", len(kvs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(kvs); i += 2 {
|
||||||
|
md[kvs[i]] = append(md[kvs[i]], kvs[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append adds the values to key k, not overwriting what was already stored at
|
// Append adds the values to key k, not overwriting what was already stored at
|
||||||
// that key.
|
// that key.
|
||||||
func (md Metadata) Append(key string, vals ...string) {
|
func (md Metadata) Append(key string, vals ...string) {
|
||||||
@@ -207,6 +232,24 @@ func AppendContext(ctx context.Context, kv ...string) context.Context {
|
|||||||
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
return context.WithValue(ctx, metadataCurrentKey{}, rawMetadata{md: md.md, added: added})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendIncomingContext returns a new context with the provided kv merged
|
||||||
|
// with any existing metadata in the context. Please refer to the documentation
|
||||||
|
// of Pairs for a description of kv.
|
||||||
|
func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
|
||||||
|
if len(kv)%2 == 1 {
|
||||||
|
panic(fmt.Sprintf("metadata: AppendIncomingContext got an odd number of input pairs for metadata: %d", len(kv)))
|
||||||
|
}
|
||||||
|
md, _ := ctx.Value(metadataIncomingKey{}).(rawMetadata)
|
||||||
|
added := make([][]string, len(md.added)+1)
|
||||||
|
copy(added, md.added)
|
||||||
|
kvCopy := make([]string, 0, len(kv))
|
||||||
|
for i := 0; i < len(kv); i += 2 {
|
||||||
|
kvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])
|
||||||
|
}
|
||||||
|
added[len(added)-1] = kvCopy
|
||||||
|
return context.WithValue(ctx, metadataIncomingKey{}, rawMetadata{md: md.md, added: added})
|
||||||
|
}
|
||||||
|
|
||||||
// AppendOutgoingContext returns a new context with the provided kv merged
|
// AppendOutgoingContext returns a new context with the provided kv merged
|
||||||
// with any existing metadata in the context. Please refer to the documentation
|
// with any existing metadata in the context. Please refer to the documentation
|
||||||
// of Pairs for a description of kv.
|
// of Pairs for a description of kv.
|
||||||
|
|||||||
@@ -5,15 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TesSet(t *testing.T) {
|
|
||||||
md := Pairs("key1", "val1", "key2", "val2")
|
|
||||||
md.Set("key1", "val2", "val3")
|
|
||||||
v := md.GetJoined("X-Request-Id")
|
|
||||||
if v != "val2, val3" {
|
|
||||||
t.Fatal("set not works")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func TestAppendOutgoingContextModify(t *testing.T) {
|
func TestAppendOutgoingContextModify(t *testing.T) {
|
||||||
md := Pairs("key1", "val1")
|
md := Pairs("key1", "val1")
|
||||||
@@ -28,8 +19,8 @@ func TestAppendOutgoingContextModify(t *testing.T) {
|
|||||||
func TestLowercase(t *testing.T) {
|
func TestLowercase(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md["x-request-id"] = []string{"12345"}
|
md["x-request-id"] = []string{"12345"}
|
||||||
v := md.GetJoined("X-Request-Id")
|
v, ok := md.GetJoined("X-Request-Id")
|
||||||
if v == "" {
|
if !ok || v == "" {
|
||||||
t.Fatalf("metadata invalid %#+v", md)
|
t.Fatalf("metadata invalid %#+v", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,9 +47,33 @@ func TestMultipleUsage(t *testing.T) {
|
|||||||
_ = omd
|
_ = omd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMetadataSetMultiple(t *testing.T) {
|
||||||
|
md := New(4)
|
||||||
|
md.Set("key1", "val1", "key2", "val2")
|
||||||
|
|
||||||
|
if v, ok := md.GetJoined("key1"); !ok || v != "val1" {
|
||||||
|
t.Fatalf("invalid kv %#+v", md)
|
||||||
|
}
|
||||||
|
if v, ok := md.GetJoined("key2"); !ok || v != "val2" {
|
||||||
|
t.Fatalf("invalid kv %#+v", md)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppend(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = AppendIncomingContext(ctx, "key1", "val1", "key2", "val2")
|
||||||
|
md, ok := FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("metadata empty")
|
||||||
|
}
|
||||||
|
if _, ok := md.Get("key1"); !ok {
|
||||||
|
t.Fatal("key1 not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPairs(t *testing.T) {
|
func TestPairs(t *testing.T) {
|
||||||
md := Pairs("key1", "val1", "key2", "val2")
|
md := Pairs("key1", "val1", "key2", "val2")
|
||||||
if v := md.Get("key1"); v == nil {
|
if _, ok := md.Get("key1"); !ok {
|
||||||
t.Fatal("key1 not found")
|
t.Fatal("key1 not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +97,7 @@ func TestPassing(t *testing.T) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("missing metadata from outgoing context")
|
t.Fatalf("missing metadata from outgoing context")
|
||||||
}
|
}
|
||||||
if v := md.Get("Key1"); v == nil || v[0] != "Val1" {
|
if v, ok := md.Get("Key1"); !ok || v[0] != "Val1" {
|
||||||
t.Fatalf("invalid metadata value %#+v", md)
|
t.Fatalf("invalid metadata value %#+v", md)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,21 +127,21 @@ func TestIterator(t *testing.T) {
|
|||||||
func TestMedataCanonicalKey(t *testing.T) {
|
func TestMedataCanonicalKey(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md.Set("x-request-id", "12345")
|
md.Set("x-request-id", "12345")
|
||||||
v := md.GetJoined("x-request-id")
|
v, ok := md.GetJoined("x-request-id")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
v = md.GetJoined("X-Request-Id")
|
v, ok = md.GetJoined("X-Request-Id")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
}
|
}
|
||||||
v = md.GetJoined("X-Request-ID")
|
v, ok = md.GetJoined("X-Request-ID")
|
||||||
if v == "" {
|
if !ok {
|
||||||
t.Fatalf("failed to get x-request-id")
|
t.Fatalf("failed to get x-request-id")
|
||||||
} else if v != "12345" {
|
} else if v != "12345" {
|
||||||
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
|
||||||
@@ -138,8 +153,8 @@ func TestMetadataSet(t *testing.T) {
|
|||||||
|
|
||||||
md.Set("Key", "val")
|
md.Set("Key", "val")
|
||||||
|
|
||||||
val := md.GetJoined("Key")
|
val, ok := md.GetJoined("Key")
|
||||||
if val == "" {
|
if !ok {
|
||||||
t.Fatal("key Key not found")
|
t.Fatal("key Key not found")
|
||||||
}
|
}
|
||||||
if val != "val" {
|
if val != "val" {
|
||||||
@@ -154,8 +169,8 @@ func TestMetadataDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
md.Del("Baz")
|
md.Del("Baz")
|
||||||
v := md.Get("Baz")
|
_, ok := md.Get("Baz")
|
||||||
if v != nil {
|
if ok {
|
||||||
t.Fatal("key Baz not deleted")
|
t.Fatal("key Baz not deleted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,6 +269,20 @@ func TestNewOutgoingContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppendIncomingContext(t *testing.T) {
|
||||||
|
md := New(1)
|
||||||
|
md.Set("key1", "val1")
|
||||||
|
ctx := AppendIncomingContext(context.TODO(), "key2", "val2")
|
||||||
|
|
||||||
|
nmd, ok := FromIncomingContext(ctx)
|
||||||
|
if nmd == nil || !ok {
|
||||||
|
t.Fatal("AppendIncomingContext not works")
|
||||||
|
}
|
||||||
|
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
|
||||||
|
t.Fatal("AppendIncomingContext not works")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppendOutgoingContext(t *testing.T) {
|
func TestAppendOutgoingContext(t *testing.T) {
|
||||||
md := New(1)
|
md := New(1)
|
||||||
md.Set("key1", "val1")
|
md.Set("key1", "val1")
|
||||||
@@ -263,7 +292,7 @@ func TestAppendOutgoingContext(t *testing.T) {
|
|||||||
if nmd == nil || !ok {
|
if nmd == nil || !ok {
|
||||||
t.Fatal("AppendOutgoingContext not works")
|
t.Fatal("AppendOutgoingContext not works")
|
||||||
}
|
}
|
||||||
if v := nmd.GetJoined("key2"); v != "val2" {
|
if v, ok := nmd.GetJoined("key2"); !ok || v != "val2" {
|
||||||
t.Fatal("AppendOutgoingContext not works")
|
t.Fatal("AppendOutgoingContext not works")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
service.go
20
service.go
@@ -99,7 +99,6 @@ type service struct {
|
|||||||
done chan struct{}
|
done chan struct{}
|
||||||
opts Options
|
opts Options
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
stopped bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService creates and returns a new Service based on the packages within.
|
// NewService creates and returns a new Service based on the packages within.
|
||||||
@@ -425,7 +424,7 @@ func (s *service) Stop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.notifyShutdown()
|
close(s.done)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -449,23 +448,10 @@ func (s *service) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait on context cancel
|
||||||
<-s.done
|
<-s.done
|
||||||
|
|
||||||
return nil
|
return s.Stop()
|
||||||
}
|
|
||||||
|
|
||||||
// notifyShutdown marks the service as stopped and closes the done channel.
|
|
||||||
// It ensures the channel is closed only once, preventing multiple closures.
|
|
||||||
func (s *service) notifyShutdown() {
|
|
||||||
s.Lock()
|
|
||||||
if s.stopped {
|
|
||||||
s.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.stopped = true
|
|
||||||
s.Unlock()
|
|
||||||
|
|
||||||
close(s.done)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Namer interface {
|
type Namer interface {
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package micro
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go.unistack.org/micro/v4/broker"
|
"go.unistack.org/micro/v4/broker"
|
||||||
"go.unistack.org/micro/v4/client"
|
"go.unistack.org/micro/v4/client"
|
||||||
"go.unistack.org/micro/v4/config"
|
"go.unistack.org/micro/v4/config"
|
||||||
@@ -739,41 +737,3 @@ func Test_getNameIndex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func TestServiceShutdown(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Fatalf("service shutdown failed: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s, ok := NewService().(*service)
|
|
||||||
require.NotNil(t, s)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
require.NoError(t, s.Start())
|
|
||||||
require.False(t, s.stopped)
|
|
||||||
|
|
||||||
require.NoError(t, s.Stop())
|
|
||||||
require.True(t, s.stopped)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceMultipleShutdowns(t *testing.T) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
t.Fatalf("service shutdown failed: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
s := NewService()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
// first call
|
|
||||||
require.NoError(t, s.Stop())
|
|
||||||
// duplicate call
|
|
||||||
require.NoError(t, s.Stop())
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.NoError(t, s.Run())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -489,74 +489,35 @@ func URLMap(query string) (map[string]interface{}, error) {
|
|||||||
return mp.(map[string]interface{}), nil
|
return mp.(map[string]interface{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlattenMap flattens a nested map into a single-level map using dot notation for nested keys.
|
// FlattenMap expand key.subkey to nested map
|
||||||
// In case of key conflicts, all nested levels will be discarded in favor of the first-level key.
|
func FlattenMap(a map[string]interface{}) map[string]interface{} {
|
||||||
//
|
// preprocess map
|
||||||
// Example #1:
|
nb := make(map[string]interface{}, len(a))
|
||||||
//
|
for k, v := range a {
|
||||||
// Input:
|
ps := strings.Split(k, ".")
|
||||||
// {
|
if len(ps) == 1 {
|
||||||
// "user.name": "alex",
|
nb[k] = v
|
||||||
// "user.document.id": "document_id"
|
|
||||||
// "user.document.number": "document_number"
|
|
||||||
// }
|
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// "user": {
|
|
||||||
// "name": "alex",
|
|
||||||
// "document": {
|
|
||||||
// "id": "document_id"
|
|
||||||
// "number": "document_number"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Example #2 (with conflicts):
|
|
||||||
//
|
|
||||||
// Input:
|
|
||||||
// {
|
|
||||||
// "user": "alex",
|
|
||||||
// "user.document.id": "document_id"
|
|
||||||
// "user.document.number": "document_number"
|
|
||||||
// }
|
|
||||||
// Output:
|
|
||||||
// {
|
|
||||||
// "user": "alex"
|
|
||||||
// }
|
|
||||||
func FlattenMap(input map[string]interface{}) map[string]interface{} {
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
|
|
||||||
for k, v := range input {
|
|
||||||
parts := strings.Split(k, ".")
|
|
||||||
|
|
||||||
if len(parts) == 1 {
|
|
||||||
result[k] = v
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
em := make(map[string]interface{})
|
||||||
current := result
|
em[ps[len(ps)-1]] = v
|
||||||
|
for i := len(ps) - 2; i > 0; i-- {
|
||||||
for i, part := range parts {
|
nm := make(map[string]interface{})
|
||||||
// last element in the path
|
nm[ps[i]] = em
|
||||||
if i == len(parts)-1 {
|
em = nm
|
||||||
current[part] = v
|
}
|
||||||
break
|
if vm, ok := nb[ps[0]]; ok {
|
||||||
}
|
// nested map
|
||||||
|
nm := vm.(map[string]interface{})
|
||||||
// initialize map for current level if not exist
|
for vk, vv := range em {
|
||||||
if _, ok := current[part]; !ok {
|
nm[vk] = vv
|
||||||
current[part] = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if nested, ok := current[part].(map[string]interface{}); ok {
|
|
||||||
current = nested // continue to the nested map
|
|
||||||
} else {
|
|
||||||
break // if current element is not a map, ignore it
|
|
||||||
}
|
}
|
||||||
|
nb[ps[0]] = nm
|
||||||
|
} else {
|
||||||
|
nb[ps[0]] = em
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nb
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
rutil "go.unistack.org/micro/v4/util/reflect"
|
rutil "go.unistack.org/micro/v4/util/reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -320,140 +319,3 @@ func TestIsZero(t *testing.T) {
|
|||||||
|
|
||||||
// t.Logf("XX %#+v\n", ok)
|
// t.Logf("XX %#+v\n", ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFlattenMap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input map[string]interface{}
|
|
||||||
expected map[string]interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty map",
|
|
||||||
input: map[string]interface{}{},
|
|
||||||
expected: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "nil map",
|
|
||||||
input: nil,
|
|
||||||
expected: map[string]interface{}{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"username": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"username": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "three level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "four level",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
"user.info.permissions.read": "available",
|
|
||||||
"user.info.permissions.write": "available",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"name": "username",
|
|
||||||
"password": "password",
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
"info": map[string]interface{}{
|
|
||||||
"permissions": map[string]interface{}{
|
|
||||||
"read": "available",
|
|
||||||
"write": "available",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "key conflicts",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"user": "user",
|
|
||||||
"user.name": "username",
|
|
||||||
"user.password": "password",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"user": "user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "overwriting conflicts",
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user.document.id": "document_id",
|
|
||||||
"user.document.number": "document_number",
|
|
||||||
"user.info.address": "address",
|
|
||||||
"user.info.phone": "phone",
|
|
||||||
},
|
|
||||||
expected: map[string]interface{}{
|
|
||||||
"order_id": "order_id",
|
|
||||||
"user": map[string]interface{}{
|
|
||||||
"document": map[string]interface{}{
|
|
||||||
"id": "document_id",
|
|
||||||
"number": "document_number",
|
|
||||||
},
|
|
||||||
"info": map[string]interface{}{
|
|
||||||
"address": "address",
|
|
||||||
"phone": "phone",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
for range 100 { // need to exclude the impact of key order in the map on the test.
|
|
||||||
require.Equal(t, tt.expected, rutil.FlattenMap(tt.input))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user