[Vendor] Update go-redis to v8.5.0 (#13749)
* Update go-redis to v8.4.0 * github.com/go-redis/redis/v8 v8.4.0 -> v8.5.0 * Apply suggestions from code review Co-authored-by: zeripath <art27@cantab.net> * TODO * Use the Queue termination channel as the default context for pushes Signed-off-by: Andrew Thornton <art27@cantab.net> * missed one Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net>tokarchuk/v1.17
parent
4cffc46f65
commit
ac97ea573c
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2017-2020 Damian Gryski <damian@gryski.com> |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
@ -0,0 +1,79 @@ |
|||||||
|
package rendezvous |
||||||
|
|
||||||
|
type Rendezvous struct { |
||||||
|
nodes map[string]int |
||||||
|
nstr []string |
||||||
|
nhash []uint64 |
||||||
|
hash Hasher |
||||||
|
} |
||||||
|
|
||||||
|
type Hasher func(s string) uint64 |
||||||
|
|
||||||
|
func New(nodes []string, hash Hasher) *Rendezvous { |
||||||
|
r := &Rendezvous{ |
||||||
|
nodes: make(map[string]int, len(nodes)), |
||||||
|
nstr: make([]string, len(nodes)), |
||||||
|
nhash: make([]uint64, len(nodes)), |
||||||
|
hash: hash, |
||||||
|
} |
||||||
|
|
||||||
|
for i, n := range nodes { |
||||||
|
r.nodes[n] = i |
||||||
|
r.nstr[i] = n |
||||||
|
r.nhash[i] = hash(n) |
||||||
|
} |
||||||
|
|
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
func (r *Rendezvous) Lookup(k string) string { |
||||||
|
// short-circuit if we're empty
|
||||||
|
if len(r.nodes) == 0 { |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
khash := r.hash(k) |
||||||
|
|
||||||
|
var midx int |
||||||
|
var mhash = xorshiftMult64(khash ^ r.nhash[0]) |
||||||
|
|
||||||
|
for i, nhash := range r.nhash[1:] { |
||||||
|
if h := xorshiftMult64(khash ^ nhash); h > mhash { |
||||||
|
midx = i + 1 |
||||||
|
mhash = h |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return r.nstr[midx] |
||||||
|
} |
||||||
|
|
||||||
|
func (r *Rendezvous) Add(node string) { |
||||||
|
r.nodes[node] = len(r.nstr) |
||||||
|
r.nstr = append(r.nstr, node) |
||||||
|
r.nhash = append(r.nhash, r.hash(node)) |
||||||
|
} |
||||||
|
|
||||||
|
func (r *Rendezvous) Remove(node string) { |
||||||
|
// find index of node to remove
|
||||||
|
nidx := r.nodes[node] |
||||||
|
|
||||||
|
// remove from the slices
|
||||||
|
l := len(r.nstr) |
||||||
|
r.nstr[nidx] = r.nstr[l] |
||||||
|
r.nstr = r.nstr[:l] |
||||||
|
|
||||||
|
r.nhash[nidx] = r.nhash[l] |
||||||
|
r.nhash = r.nhash[:l] |
||||||
|
|
||||||
|
// update the map
|
||||||
|
delete(r.nodes, node) |
||||||
|
moved := r.nstr[nidx] |
||||||
|
r.nodes[moved] = nidx |
||||||
|
} |
||||||
|
|
||||||
|
func xorshiftMult64(x uint64) uint64 { |
||||||
|
x ^= x >> 12 // a
|
||||||
|
x ^= x << 25 // b
|
||||||
|
x ^= x >> 27 // c
|
||||||
|
return x * 2685821657736338717 |
||||||
|
} |
@ -1,22 +0,0 @@ |
|||||||
dist: xenial |
|
||||||
language: go |
|
||||||
|
|
||||||
services: |
|
||||||
- redis-server |
|
||||||
|
|
||||||
go: |
|
||||||
- 1.12.x |
|
||||||
- 1.13.x |
|
||||||
- tip |
|
||||||
|
|
||||||
matrix: |
|
||||||
allow_failures: |
|
||||||
- go: tip |
|
||||||
|
|
||||||
env: |
|
||||||
- GO111MODULE=on |
|
||||||
|
|
||||||
go_import_path: github.com/go-redis/redis |
|
||||||
|
|
||||||
before_install: |
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.21.0 |
|
@ -1,46 +0,0 @@ |
|||||||
# Changelog |
|
||||||
|
|
||||||
## v7.2 |
|
||||||
|
|
||||||
- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users. |
|
||||||
|
|
||||||
## v7.1 |
|
||||||
|
|
||||||
- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer` interface. |
|
||||||
|
|
||||||
## v7 |
|
||||||
|
|
||||||
- *Important*. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a transactional pipeline. |
|
||||||
- WrapProcess is replaced with more convenient AddHook that has access to context.Context. |
|
||||||
- WithContext now can not be used to create a shallow copy of the client. |
|
||||||
- New methods ProcessContext, DoContext, and ExecContext. |
|
||||||
- Client respects Context.Deadline when setting net.Conn deadline. |
|
||||||
- Client listens on Context.Done while waiting for a connection from the pool and returns an error when context context is cancelled. |
|
||||||
- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow detecting reconnections. |
|
||||||
- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse the time. |
|
||||||
- `SetLimiter` is removed and added `Options.Limiter` instead. |
|
||||||
- `HMSet` is deprecated as of Redis v4. |
|
||||||
|
|
||||||
## v6.15 |
|
||||||
|
|
||||||
- Cluster and Ring pipelines process commands for each node in its own goroutine. |
|
||||||
|
|
||||||
## 6.14 |
|
||||||
|
|
||||||
- Added Options.MinIdleConns. |
|
||||||
- Added Options.MaxConnAge. |
|
||||||
- PoolStats.FreeConns is renamed to PoolStats.IdleConns. |
|
||||||
- Add Client.Do to simplify creating custom commands. |
|
||||||
- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. |
|
||||||
- Lower memory usage. |
|
||||||
|
|
||||||
## v6.13 |
|
||||||
|
|
||||||
- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. |
|
||||||
- Cluster client was optimized to use much less memory when reloading cluster state. |
|
||||||
- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. |
|
||||||
- Dialer.KeepAlive is set to 5 minutes by default. |
|
||||||
|
|
||||||
## v6.12 |
|
||||||
|
|
||||||
- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup |
|
@ -1,128 +0,0 @@ |
|||||||
# Redis client for Golang |
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/go-redis/redis.png?branch=master)](https://travis-ci.org/go-redis/redis) |
|
||||||
[![GoDoc](https://godoc.org/github.com/go-redis/redis?status.svg)](https://godoc.org/github.com/go-redis/redis) |
|
||||||
[![Airbrake](https://img.shields.io/badge/kudos-airbrake.io-orange.svg)](https://airbrake.io) |
|
||||||
|
|
||||||
Supports: |
|
||||||
|
|
||||||
- Redis 3 commands except QUIT, MONITOR, SLOWLOG and SYNC. |
|
||||||
- Automatic connection pooling with [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. |
|
||||||
- [Pub/Sub](https://godoc.org/github.com/go-redis/redis#PubSub). |
|
||||||
- [Transactions](https://godoc.org/github.com/go-redis/redis#example-Client-TxPipeline). |
|
||||||
- [Pipeline](https://godoc.org/github.com/go-redis/redis#example-Client-Pipeline) and [TxPipeline](https://godoc.org/github.com/go-redis/redis#example-Client-TxPipeline). |
|
||||||
- [Scripting](https://godoc.org/github.com/go-redis/redis#Script). |
|
||||||
- [Timeouts](https://godoc.org/github.com/go-redis/redis#Options). |
|
||||||
- [Redis Sentinel](https://godoc.org/github.com/go-redis/redis#NewFailoverClient). |
|
||||||
- [Redis Cluster](https://godoc.org/github.com/go-redis/redis#NewClusterClient). |
|
||||||
- [Cluster of Redis Servers](https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup) without using cluster mode and Redis Sentinel. |
|
||||||
- [Ring](https://godoc.org/github.com/go-redis/redis#NewRing). |
|
||||||
- [Instrumentation](https://godoc.org/github.com/go-redis/redis#ex-package--Instrumentation). |
|
||||||
- [Cache friendly](https://github.com/go-redis/cache). |
|
||||||
- [Rate limiting](https://github.com/go-redis/redis_rate). |
|
||||||
- [Distributed Locks](https://github.com/bsm/redislock). |
|
||||||
|
|
||||||
API docs: https://godoc.org/github.com/go-redis/redis. |
|
||||||
Examples: https://godoc.org/github.com/go-redis/redis#pkg-examples. |
|
||||||
|
|
||||||
## Installation |
|
||||||
|
|
||||||
go-redis requires a Go version with [Modules](https://github.com/golang/go/wiki/Modules) support and uses import versioning. So please make sure to initialize a Go module before installing go-redis: |
|
||||||
|
|
||||||
``` shell |
|
||||||
go mod init github.com/my/repo |
|
||||||
go get github.com/go-redis/redis/v7 |
|
||||||
``` |
|
||||||
|
|
||||||
Import: |
|
||||||
|
|
||||||
``` go |
|
||||||
import "github.com/go-redis/redis/v7" |
|
||||||
``` |
|
||||||
|
|
||||||
## Quickstart |
|
||||||
|
|
||||||
``` go |
|
||||||
func ExampleNewClient() { |
|
||||||
client := redis.NewClient(&redis.Options{ |
|
||||||
Addr: "localhost:6379", |
|
||||||
Password: "", // no password set |
|
||||||
DB: 0, // use default DB |
|
||||||
}) |
|
||||||
|
|
||||||
pong, err := client.Ping().Result() |
|
||||||
fmt.Println(pong, err) |
|
||||||
// Output: PONG <nil> |
|
||||||
} |
|
||||||
|
|
||||||
func ExampleClient() { |
|
||||||
client := redis.NewClient(&redis.Options{ |
|
||||||
Addr: "localhost:6379", |
|
||||||
Password: "", // no password set |
|
||||||
DB: 0, // use default DB |
|
||||||
}) |
|
||||||
err := client.Set("key", "value", 0).Err() |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
|
|
||||||
val, err := client.Get("key").Result() |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
fmt.Println("key", val) |
|
||||||
|
|
||||||
val2, err := client.Get("key2").Result() |
|
||||||
if err == redis.Nil { |
|
||||||
fmt.Println("key2 does not exist") |
|
||||||
} else if err != nil { |
|
||||||
panic(err) |
|
||||||
} else { |
|
||||||
fmt.Println("key2", val2) |
|
||||||
} |
|
||||||
// Output: key value |
|
||||||
// key2 does not exist |
|
||||||
} |
|
||||||
``` |
|
||||||
|
|
||||||
## Howto |
|
||||||
|
|
||||||
Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. |
|
||||||
|
|
||||||
## Look and feel |
|
||||||
|
|
||||||
Some corner cases: |
|
||||||
|
|
||||||
``` go |
|
||||||
// SET key value EX 10 NX |
|
||||||
set, err := client.SetNX("key", "value", 10*time.Second).Result() |
|
||||||
|
|
||||||
// SORT list LIMIT 0 2 ASC |
|
||||||
vals, err := client.Sort("list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() |
|
||||||
|
|
||||||
// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 |
|
||||||
vals, err := client.ZRangeByScoreWithScores("zset", &redis.ZRangeBy{ |
|
||||||
Min: "-inf", |
|
||||||
Max: "+inf", |
|
||||||
Offset: 0, |
|
||||||
Count: 2, |
|
||||||
}).Result() |
|
||||||
|
|
||||||
// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM |
|
||||||
vals, err := client.ZInterStore("out", &redis.ZStore{ |
|
||||||
Keys: []string{"zset1", "zset2"}, |
|
||||||
Weights: []int64{2, 3} |
|
||||||
}).Result() |
|
||||||
|
|
||||||
// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" |
|
||||||
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() |
|
||||||
|
|
||||||
// custom command |
|
||||||
res, err := client.Do("set", "key", "value").Result() |
|
||||||
``` |
|
||||||
|
|
||||||
## See also |
|
||||||
|
|
||||||
- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) |
|
||||||
- [Golang msgpack](https://github.com/vmihailenco/msgpack) |
|
||||||
- [Golang message task queue](https://github.com/vmihailenco/taskq) |
|
@ -1,22 +0,0 @@ |
|||||||
package redis |
|
||||||
|
|
||||||
import "sync/atomic" |
|
||||||
|
|
||||||
func (c *ClusterClient) DBSize() *IntCmd { |
|
||||||
cmd := NewIntCmd("dbsize") |
|
||||||
var size int64 |
|
||||||
err := c.ForEachMaster(func(master *Client) error { |
|
||||||
n, err := master.DBSize().Result() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
atomic.AddInt64(&size, n) |
|
||||||
return nil |
|
||||||
}) |
|
||||||
if err != nil { |
|
||||||
cmd.SetErr(err) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
cmd.val = size |
|
||||||
return cmd |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@ |
|||||||
module github.com/go-redis/redis/v7 |
|
||||||
|
|
||||||
require ( |
|
||||||
github.com/golang/protobuf v1.3.2 // indirect |
|
||||||
github.com/kr/pretty v0.1.0 // indirect |
|
||||||
github.com/onsi/ginkgo v1.10.1 |
|
||||||
github.com/onsi/gomega v1.7.0 |
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect |
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect |
|
||||||
golang.org/x/text v0.3.2 // indirect |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect |
|
||||||
gopkg.in/yaml.v2 v2.2.4 // indirect |
|
||||||
) |
|
||||||
|
|
||||||
go 1.11 |
|
@ -1,47 +0,0 @@ |
|||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= |
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= |
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= |
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= |
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= |
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= |
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
|
||||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= |
|
||||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
|
||||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= |
|
||||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= |
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= |
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= |
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= |
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= |
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= |
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= |
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= |
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= |
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= |
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= |
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= |
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= |
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
||||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= |
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
|
@ -1,81 +0,0 @@ |
|||||||
/* |
|
||||||
Copyright 2013 Google Inc. |
|
||||||
|
|
||||||
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 consistenthash provides an implementation of a ring hash.
|
|
||||||
package consistenthash |
|
||||||
|
|
||||||
import ( |
|
||||||
"hash/crc32" |
|
||||||
"sort" |
|
||||||
"strconv" |
|
||||||
) |
|
||||||
|
|
||||||
type Hash func(data []byte) uint32 |
|
||||||
|
|
||||||
type Map struct { |
|
||||||
hash Hash |
|
||||||
replicas int |
|
||||||
keys []int // Sorted
|
|
||||||
hashMap map[int]string |
|
||||||
} |
|
||||||
|
|
||||||
func New(replicas int, fn Hash) *Map { |
|
||||||
m := &Map{ |
|
||||||
replicas: replicas, |
|
||||||
hash: fn, |
|
||||||
hashMap: make(map[int]string), |
|
||||||
} |
|
||||||
if m.hash == nil { |
|
||||||
m.hash = crc32.ChecksumIEEE |
|
||||||
} |
|
||||||
return m |
|
||||||
} |
|
||||||
|
|
||||||
// Returns true if there are no items available.
|
|
||||||
func (m *Map) IsEmpty() bool { |
|
||||||
return len(m.keys) == 0 |
|
||||||
} |
|
||||||
|
|
||||||
// Adds some keys to the hash.
|
|
||||||
func (m *Map) Add(keys ...string) { |
|
||||||
for _, key := range keys { |
|
||||||
for i := 0; i < m.replicas; i++ { |
|
||||||
hash := int(m.hash([]byte(strconv.Itoa(i) + key))) |
|
||||||
m.keys = append(m.keys, hash) |
|
||||||
m.hashMap[hash] = key |
|
||||||
} |
|
||||||
} |
|
||||||
sort.Ints(m.keys) |
|
||||||
} |
|
||||||
|
|
||||||
// Gets the closest item in the hash to the provided key.
|
|
||||||
func (m *Map) Get(key string) string { |
|
||||||
if m.IsEmpty() { |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
hash := int(m.hash([]byte(key))) |
|
||||||
|
|
||||||
// Binary search for appropriate replica.
|
|
||||||
idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) |
|
||||||
|
|
||||||
// Means we have cycled back to the first replica.
|
|
||||||
if idx == len(m.keys) { |
|
||||||
idx = 0 |
|
||||||
} |
|
||||||
|
|
||||||
return m.hashMap[m.keys[idx]] |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
package internal |
|
||||||
|
|
||||||
import ( |
|
||||||
"math/rand" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// Retry backoff with jitter sleep to prevent overloaded conditions during intervals
|
|
||||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
||||||
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { |
|
||||||
if retry < 0 { |
|
||||||
retry = 0 |
|
||||||
} |
|
||||||
|
|
||||||
backoff := minBackoff << uint(retry) |
|
||||||
if backoff > maxBackoff || backoff < minBackoff { |
|
||||||
backoff = maxBackoff |
|
||||||
} |
|
||||||
|
|
||||||
if backoff == 0 { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return time.Duration(rand.Int63n(int64(backoff))) |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
package internal |
|
||||||
|
|
||||||
import ( |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
) |
|
||||||
|
|
||||||
var Logger = log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile) |
|
@ -1,112 +0,0 @@ |
|||||||
package pool |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"sync" |
|
||||||
) |
|
||||||
|
|
||||||
type StickyConnPool struct { |
|
||||||
pool *ConnPool |
|
||||||
reusable bool |
|
||||||
|
|
||||||
cn *Conn |
|
||||||
closed bool |
|
||||||
mu sync.Mutex |
|
||||||
} |
|
||||||
|
|
||||||
var _ Pooler = (*StickyConnPool)(nil) |
|
||||||
|
|
||||||
func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool { |
|
||||||
return &StickyConnPool{ |
|
||||||
pool: pool, |
|
||||||
reusable: reusable, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) NewConn(context.Context) (*Conn, error) { |
|
||||||
panic("not implemented") |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) CloseConn(*Conn) error { |
|
||||||
panic("not implemented") |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Get(ctx context.Context) (*Conn, error) { |
|
||||||
p.mu.Lock() |
|
||||||
defer p.mu.Unlock() |
|
||||||
|
|
||||||
if p.closed { |
|
||||||
return nil, ErrClosed |
|
||||||
} |
|
||||||
if p.cn != nil { |
|
||||||
return p.cn, nil |
|
||||||
} |
|
||||||
|
|
||||||
cn, err := p.pool.Get(ctx) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
p.cn = cn |
|
||||||
return cn, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) putUpstream() { |
|
||||||
p.pool.Put(p.cn) |
|
||||||
p.cn = nil |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Put(cn *Conn) {} |
|
||||||
|
|
||||||
func (p *StickyConnPool) removeUpstream(reason error) { |
|
||||||
p.pool.Remove(p.cn, reason) |
|
||||||
p.cn = nil |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Remove(cn *Conn, reason error) { |
|
||||||
p.removeUpstream(reason) |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Len() int { |
|
||||||
p.mu.Lock() |
|
||||||
defer p.mu.Unlock() |
|
||||||
|
|
||||||
if p.cn == nil { |
|
||||||
return 0 |
|
||||||
} |
|
||||||
return 1 |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) IdleLen() int { |
|
||||||
p.mu.Lock() |
|
||||||
defer p.mu.Unlock() |
|
||||||
|
|
||||||
if p.cn == nil { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Stats() *Stats { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (p *StickyConnPool) Close() error { |
|
||||||
p.mu.Lock() |
|
||||||
defer p.mu.Unlock() |
|
||||||
|
|
||||||
if p.closed { |
|
||||||
return ErrClosed |
|
||||||
} |
|
||||||
p.closed = true |
|
||||||
|
|
||||||
if p.cn != nil { |
|
||||||
if p.reusable { |
|
||||||
p.putUpstream() |
|
||||||
} else { |
|
||||||
p.removeUpstream(ErrClosed) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
package internal |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7/internal/util" |
|
||||||
) |
|
||||||
|
|
||||||
func Sleep(ctx context.Context, dur time.Duration) error { |
|
||||||
t := time.NewTimer(dur) |
|
||||||
defer t.Stop() |
|
||||||
|
|
||||||
select { |
|
||||||
case <-t.C: |
|
||||||
return nil |
|
||||||
case <-ctx.Done(): |
|
||||||
return ctx.Err() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func ToLower(s string) string { |
|
||||||
if isLower(s) { |
|
||||||
return s |
|
||||||
} |
|
||||||
|
|
||||||
b := make([]byte, len(s)) |
|
||||||
for i := range b { |
|
||||||
c := s[i] |
|
||||||
if c >= 'A' && c <= 'Z' { |
|
||||||
c += 'a' - 'A' |
|
||||||
} |
|
||||||
b[i] = c |
|
||||||
} |
|
||||||
return util.BytesToString(b) |
|
||||||
} |
|
||||||
|
|
||||||
func isLower(s string) bool { |
|
||||||
for i := 0; i < len(s); i++ { |
|
||||||
c := s[i] |
|
||||||
if c >= 'A' && c <= 'Z' { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
func Unwrap(err error) error { |
|
||||||
u, ok := err.(interface { |
|
||||||
Unwrap() error |
|
||||||
}) |
|
||||||
if !ok { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return u.Unwrap() |
|
||||||
} |
|
@ -1,62 +0,0 @@ |
|||||||
package redis |
|
||||||
|
|
||||||
import ( |
|
||||||
"crypto/sha1" |
|
||||||
"encoding/hex" |
|
||||||
"io" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
type scripter interface { |
|
||||||
Eval(script string, keys []string, args ...interface{}) *Cmd |
|
||||||
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd |
|
||||||
ScriptExists(hashes ...string) *BoolSliceCmd |
|
||||||
ScriptLoad(script string) *StringCmd |
|
||||||
} |
|
||||||
|
|
||||||
var _ scripter = (*Client)(nil) |
|
||||||
var _ scripter = (*Ring)(nil) |
|
||||||
var _ scripter = (*ClusterClient)(nil) |
|
||||||
|
|
||||||
type Script struct { |
|
||||||
src, hash string |
|
||||||
} |
|
||||||
|
|
||||||
func NewScript(src string) *Script { |
|
||||||
h := sha1.New() |
|
||||||
_, _ = io.WriteString(h, src) |
|
||||||
return &Script{ |
|
||||||
src: src, |
|
||||||
hash: hex.EncodeToString(h.Sum(nil)), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Script) Hash() string { |
|
||||||
return s.hash |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Script) Load(c scripter) *StringCmd { |
|
||||||
return c.ScriptLoad(s.src) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Script) Exists(c scripter) *BoolSliceCmd { |
|
||||||
return c.ScriptExists(s.hash) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd { |
|
||||||
return c.Eval(s.src, keys, args...) |
|
||||||
} |
|
||||||
|
|
||||||
func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd { |
|
||||||
return c.EvalSha(s.hash, keys, args...) |
|
||||||
} |
|
||||||
|
|
||||||
// Run optimistically uses EVALSHA to run the script. If script does not exist
|
|
||||||
// it is retried using EVAL.
|
|
||||||
func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd { |
|
||||||
r := s.EvalSha(c, keys, args...) |
|
||||||
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { |
|
||||||
return s.Eval(c, keys, args...) |
|
||||||
} |
|
||||||
return r |
|
||||||
} |
|
@ -1,509 +0,0 @@ |
|||||||
package redis |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"crypto/tls" |
|
||||||
"errors" |
|
||||||
"net" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-redis/redis/v7/internal" |
|
||||||
"github.com/go-redis/redis/v7/internal/pool" |
|
||||||
) |
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// FailoverOptions are used to configure a failover client and should
|
|
||||||
// be passed to NewFailoverClient.
|
|
||||||
type FailoverOptions struct { |
|
||||||
// The master name.
|
|
||||||
MasterName string |
|
||||||
// A seed list of host:port addresses of sentinel nodes.
|
|
||||||
SentinelAddrs []string |
|
||||||
SentinelUsername string |
|
||||||
SentinelPassword string |
|
||||||
|
|
||||||
// Following options are copied from Options struct.
|
|
||||||
|
|
||||||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error) |
|
||||||
OnConnect func(*Conn) error |
|
||||||
|
|
||||||
Username string |
|
||||||
Password string |
|
||||||
DB int |
|
||||||
|
|
||||||
MaxRetries int |
|
||||||
MinRetryBackoff time.Duration |
|
||||||
MaxRetryBackoff time.Duration |
|
||||||
|
|
||||||
DialTimeout time.Duration |
|
||||||
ReadTimeout time.Duration |
|
||||||
WriteTimeout time.Duration |
|
||||||
|
|
||||||
PoolSize int |
|
||||||
MinIdleConns int |
|
||||||
MaxConnAge time.Duration |
|
||||||
PoolTimeout time.Duration |
|
||||||
IdleTimeout time.Duration |
|
||||||
IdleCheckFrequency time.Duration |
|
||||||
|
|
||||||
TLSConfig *tls.Config |
|
||||||
} |
|
||||||
|
|
||||||
func (opt *FailoverOptions) options() *Options { |
|
||||||
return &Options{ |
|
||||||
Addr: "FailoverClient", |
|
||||||
Dialer: opt.Dialer, |
|
||||||
OnConnect: opt.OnConnect, |
|
||||||
|
|
||||||
DB: opt.DB, |
|
||||||
Username: opt.Username, |
|
||||||
Password: opt.Password, |
|
||||||
|
|
||||||
MaxRetries: opt.MaxRetries, |
|
||||||
MinRetryBackoff: opt.MinRetryBackoff, |
|
||||||
MaxRetryBackoff: opt.MaxRetryBackoff, |
|
||||||
|
|
||||||
DialTimeout: opt.DialTimeout, |
|
||||||
ReadTimeout: opt.ReadTimeout, |
|
||||||
WriteTimeout: opt.WriteTimeout, |
|
||||||
|
|
||||||
PoolSize: opt.PoolSize, |
|
||||||
PoolTimeout: opt.PoolTimeout, |
|
||||||
IdleTimeout: opt.IdleTimeout, |
|
||||||
IdleCheckFrequency: opt.IdleCheckFrequency, |
|
||||||
MinIdleConns: opt.MinIdleConns, |
|
||||||
MaxConnAge: opt.MaxConnAge, |
|
||||||
|
|
||||||
TLSConfig: opt.TLSConfig, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NewFailoverClient returns a Redis client that uses Redis Sentinel
|
|
||||||
// for automatic failover. It's safe for concurrent use by multiple
|
|
||||||
// goroutines.
|
|
||||||
func NewFailoverClient(failoverOpt *FailoverOptions) *Client { |
|
||||||
opt := failoverOpt.options() |
|
||||||
opt.init() |
|
||||||
|
|
||||||
failover := &sentinelFailover{ |
|
||||||
masterName: failoverOpt.MasterName, |
|
||||||
sentinelAddrs: failoverOpt.SentinelAddrs, |
|
||||||
username: failoverOpt.SentinelUsername, |
|
||||||
password: failoverOpt.SentinelPassword, |
|
||||||
|
|
||||||
opt: opt, |
|
||||||
} |
|
||||||
|
|
||||||
c := Client{ |
|
||||||
baseClient: newBaseClient(opt, failover.Pool()), |
|
||||||
ctx: context.Background(), |
|
||||||
} |
|
||||||
c.cmdable = c.Process |
|
||||||
c.onClose = failover.Close |
|
||||||
|
|
||||||
return &c |
|
||||||
} |
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type SentinelClient struct { |
|
||||||
*baseClient |
|
||||||
ctx context.Context |
|
||||||
} |
|
||||||
|
|
||||||
func NewSentinelClient(opt *Options) *SentinelClient { |
|
||||||
opt.init() |
|
||||||
c := &SentinelClient{ |
|
||||||
baseClient: &baseClient{ |
|
||||||
opt: opt, |
|
||||||
connPool: newConnPool(opt), |
|
||||||
}, |
|
||||||
ctx: context.Background(), |
|
||||||
} |
|
||||||
return c |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) Context() context.Context { |
|
||||||
return c.ctx |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) WithContext(ctx context.Context) *SentinelClient { |
|
||||||
if ctx == nil { |
|
||||||
panic("nil context") |
|
||||||
} |
|
||||||
clone := *c |
|
||||||
clone.ctx = ctx |
|
||||||
return &clone |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) Process(cmd Cmder) error { |
|
||||||
return c.ProcessContext(c.ctx, cmd) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) ProcessContext(ctx context.Context, cmd Cmder) error { |
|
||||||
return c.baseClient.process(ctx, cmd) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) pubSub() *PubSub { |
|
||||||
pubsub := &PubSub{ |
|
||||||
opt: c.opt, |
|
||||||
|
|
||||||
newConn: func(channels []string) (*pool.Conn, error) { |
|
||||||
return c.newConn(context.TODO()) |
|
||||||
}, |
|
||||||
closeConn: c.connPool.CloseConn, |
|
||||||
} |
|
||||||
pubsub.init() |
|
||||||
return pubsub |
|
||||||
} |
|
||||||
|
|
||||||
// Ping is used to test if a connection is still alive, or to
|
|
||||||
// measure latency.
|
|
||||||
func (c *SentinelClient) Ping() *StringCmd { |
|
||||||
cmd := NewStringCmd("ping") |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Subscribe subscribes the client to the specified channels.
|
|
||||||
// Channels can be omitted to create empty subscription.
|
|
||||||
func (c *SentinelClient) Subscribe(channels ...string) *PubSub { |
|
||||||
pubsub := c.pubSub() |
|
||||||
if len(channels) > 0 { |
|
||||||
_ = pubsub.Subscribe(channels...) |
|
||||||
} |
|
||||||
return pubsub |
|
||||||
} |
|
||||||
|
|
||||||
// PSubscribe subscribes the client to the given patterns.
|
|
||||||
// Patterns can be omitted to create empty subscription.
|
|
||||||
func (c *SentinelClient) PSubscribe(channels ...string) *PubSub { |
|
||||||
pubsub := c.pubSub() |
|
||||||
if len(channels) > 0 { |
|
||||||
_ = pubsub.PSubscribe(channels...) |
|
||||||
} |
|
||||||
return pubsub |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) GetMasterAddrByName(name string) *StringSliceCmd { |
|
||||||
cmd := NewStringSliceCmd("sentinel", "get-master-addr-by-name", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
func (c *SentinelClient) Sentinels(name string) *SliceCmd { |
|
||||||
cmd := NewSliceCmd("sentinel", "sentinels", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Failover forces a failover as if the master was not reachable, and without
|
|
||||||
// asking for agreement to other Sentinels.
|
|
||||||
func (c *SentinelClient) Failover(name string) *StatusCmd { |
|
||||||
cmd := NewStatusCmd("sentinel", "failover", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Reset resets all the masters with matching name. The pattern argument is a
|
|
||||||
// glob-style pattern. The reset process clears any previous state in a master
|
|
||||||
// (including a failover in progress), and removes every slave and sentinel
|
|
||||||
// already discovered and associated with the master.
|
|
||||||
func (c *SentinelClient) Reset(pattern string) *IntCmd { |
|
||||||
cmd := NewIntCmd("sentinel", "reset", pattern) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// FlushConfig forces Sentinel to rewrite its configuration on disk, including
|
|
||||||
// the current Sentinel state.
|
|
||||||
func (c *SentinelClient) FlushConfig() *StatusCmd { |
|
||||||
cmd := NewStatusCmd("sentinel", "flushconfig") |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Master shows the state and info of the specified master.
|
|
||||||
func (c *SentinelClient) Master(name string) *StringStringMapCmd { |
|
||||||
cmd := NewStringStringMapCmd("sentinel", "master", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Masters shows a list of monitored masters and their state.
|
|
||||||
func (c *SentinelClient) Masters() *SliceCmd { |
|
||||||
cmd := NewSliceCmd("sentinel", "masters") |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Slaves shows a list of slaves for the specified master and their state.
|
|
||||||
func (c *SentinelClient) Slaves(name string) *SliceCmd { |
|
||||||
cmd := NewSliceCmd("sentinel", "slaves", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// CkQuorum checks if the current Sentinel configuration is able to reach the
|
|
||||||
// quorum needed to failover a master, and the majority needed to authorize the
|
|
||||||
// failover. This command should be used in monitoring systems to check if a
|
|
||||||
// Sentinel deployment is ok.
|
|
||||||
func (c *SentinelClient) CkQuorum(name string) *StringCmd { |
|
||||||
cmd := NewStringCmd("sentinel", "ckquorum", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Monitor tells the Sentinel to start monitoring a new master with the specified
|
|
||||||
// name, ip, port, and quorum.
|
|
||||||
func (c *SentinelClient) Monitor(name, ip, port, quorum string) *StringCmd { |
|
||||||
cmd := NewStringCmd("sentinel", "monitor", name, ip, port, quorum) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Set is used in order to change configuration parameters of a specific master.
|
|
||||||
func (c *SentinelClient) Set(name, option, value string) *StringCmd { |
|
||||||
cmd := NewStringCmd("sentinel", "set", name, option, value) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
// Remove is used in order to remove the specified master: the master will no
|
|
||||||
// longer be monitored, and will totally be removed from the internal state of
|
|
||||||
// the Sentinel.
|
|
||||||
func (c *SentinelClient) Remove(name string) *StringCmd { |
|
||||||
cmd := NewStringCmd("sentinel", "remove", name) |
|
||||||
_ = c.Process(cmd) |
|
||||||
return cmd |
|
||||||
} |
|
||||||
|
|
||||||
type sentinelFailover struct { |
|
||||||
sentinelAddrs []string |
|
||||||
|
|
||||||
opt *Options |
|
||||||
username string |
|
||||||
password string |
|
||||||
|
|
||||||
pool *pool.ConnPool |
|
||||||
poolOnce sync.Once |
|
||||||
|
|
||||||
mu sync.RWMutex |
|
||||||
masterName string |
|
||||||
_masterAddr string |
|
||||||
sentinel *SentinelClient |
|
||||||
pubsub *PubSub |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) Close() error { |
|
||||||
c.mu.Lock() |
|
||||||
defer c.mu.Unlock() |
|
||||||
if c.sentinel != nil { |
|
||||||
return c.closeSentinel() |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) closeSentinel() error { |
|
||||||
firstErr := c.pubsub.Close() |
|
||||||
c.pubsub = nil |
|
||||||
|
|
||||||
err := c.sentinel.Close() |
|
||||||
if err != nil && firstErr == nil { |
|
||||||
firstErr = err |
|
||||||
} |
|
||||||
c.sentinel = nil |
|
||||||
|
|
||||||
return firstErr |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) Pool() *pool.ConnPool { |
|
||||||
c.poolOnce.Do(func() { |
|
||||||
opt := *c.opt |
|
||||||
opt.Dialer = c.dial |
|
||||||
c.pool = newConnPool(&opt) |
|
||||||
}) |
|
||||||
return c.pool |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) dial(ctx context.Context, network, _ string) (net.Conn, error) { |
|
||||||
addr, err := c.MasterAddr() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if c.opt.Dialer != nil { |
|
||||||
return c.opt.Dialer(ctx, network, addr) |
|
||||||
} |
|
||||||
return net.DialTimeout("tcp", addr, c.opt.DialTimeout) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) MasterAddr() (string, error) { |
|
||||||
addr, err := c.masterAddr() |
|
||||||
if err != nil { |
|
||||||
return "", err |
|
||||||
} |
|
||||||
c.switchMaster(addr) |
|
||||||
return addr, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) masterAddr() (string, error) { |
|
||||||
c.mu.RLock() |
|
||||||
sentinel := c.sentinel |
|
||||||
c.mu.RUnlock() |
|
||||||
|
|
||||||
if sentinel != nil { |
|
||||||
addr := c.getMasterAddr(sentinel) |
|
||||||
if addr != "" { |
|
||||||
return addr, nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
c.mu.Lock() |
|
||||||
defer c.mu.Unlock() |
|
||||||
|
|
||||||
if c.sentinel != nil { |
|
||||||
addr := c.getMasterAddr(c.sentinel) |
|
||||||
if addr != "" { |
|
||||||
return addr, nil |
|
||||||
} |
|
||||||
_ = c.closeSentinel() |
|
||||||
} |
|
||||||
|
|
||||||
for i, sentinelAddr := range c.sentinelAddrs { |
|
||||||
sentinel := NewSentinelClient(&Options{ |
|
||||||
Addr: sentinelAddr, |
|
||||||
Dialer: c.opt.Dialer, |
|
||||||
|
|
||||||
Username: c.username, |
|
||||||
Password: c.password, |
|
||||||
|
|
||||||
MaxRetries: c.opt.MaxRetries, |
|
||||||
|
|
||||||
DialTimeout: c.opt.DialTimeout, |
|
||||||
ReadTimeout: c.opt.ReadTimeout, |
|
||||||
WriteTimeout: c.opt.WriteTimeout, |
|
||||||
|
|
||||||
PoolSize: c.opt.PoolSize, |
|
||||||
PoolTimeout: c.opt.PoolTimeout, |
|
||||||
IdleTimeout: c.opt.IdleTimeout, |
|
||||||
IdleCheckFrequency: c.opt.IdleCheckFrequency, |
|
||||||
|
|
||||||
TLSConfig: c.opt.TLSConfig, |
|
||||||
}) |
|
||||||
|
|
||||||
masterAddr, err := sentinel.GetMasterAddrByName(c.masterName).Result() |
|
||||||
if err != nil { |
|
||||||
internal.Logger.Printf("sentinel: GetMasterAddrByName master=%q failed: %s", |
|
||||||
c.masterName, err) |
|
||||||
_ = sentinel.Close() |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
// Push working sentinel to the top.
|
|
||||||
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0] |
|
||||||
c.setSentinel(sentinel) |
|
||||||
|
|
||||||
addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) |
|
||||||
return addr, nil |
|
||||||
} |
|
||||||
|
|
||||||
return "", errors.New("redis: all sentinels are unreachable") |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) getMasterAddr(sentinel *SentinelClient) string { |
|
||||||
addr, err := sentinel.GetMasterAddrByName(c.masterName).Result() |
|
||||||
if err != nil { |
|
||||||
internal.Logger.Printf("sentinel: GetMasterAddrByName name=%q failed: %s", |
|
||||||
c.masterName, err) |
|
||||||
return "" |
|
||||||
} |
|
||||||
return net.JoinHostPort(addr[0], addr[1]) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) switchMaster(addr string) { |
|
||||||
c.mu.RLock() |
|
||||||
masterAddr := c._masterAddr |
|
||||||
c.mu.RUnlock() |
|
||||||
if masterAddr == addr { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
c.mu.Lock() |
|
||||||
defer c.mu.Unlock() |
|
||||||
|
|
||||||
if c._masterAddr == addr { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
internal.Logger.Printf("sentinel: new master=%q addr=%q", |
|
||||||
c.masterName, addr) |
|
||||||
_ = c.Pool().Filter(func(cn *pool.Conn) bool { |
|
||||||
return cn.RemoteAddr().String() != addr |
|
||||||
}) |
|
||||||
c._masterAddr = addr |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) setSentinel(sentinel *SentinelClient) { |
|
||||||
if c.sentinel != nil { |
|
||||||
panic("not reached") |
|
||||||
} |
|
||||||
c.sentinel = sentinel |
|
||||||
c.discoverSentinels() |
|
||||||
|
|
||||||
c.pubsub = sentinel.Subscribe("+switch-master") |
|
||||||
go c.listen(c.pubsub) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) discoverSentinels() { |
|
||||||
sentinels, err := c.sentinel.Sentinels(c.masterName).Result() |
|
||||||
if err != nil { |
|
||||||
internal.Logger.Printf("sentinel: Sentinels master=%q failed: %s", c.masterName, err) |
|
||||||
return |
|
||||||
} |
|
||||||
for _, sentinel := range sentinels { |
|
||||||
vals := sentinel.([]interface{}) |
|
||||||
for i := 0; i < len(vals); i += 2 { |
|
||||||
key := vals[i].(string) |
|
||||||
if key == "name" { |
|
||||||
sentinelAddr := vals[i+1].(string) |
|
||||||
if !contains(c.sentinelAddrs, sentinelAddr) { |
|
||||||
internal.Logger.Printf("sentinel: discovered new sentinel=%q for master=%q", |
|
||||||
sentinelAddr, c.masterName) |
|
||||||
c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (c *sentinelFailover) listen(pubsub *PubSub) { |
|
||||||
ch := pubsub.Channel() |
|
||||||
for { |
|
||||||
msg, ok := <-ch |
|
||||||
if !ok { |
|
||||||
break |
|
||||||
} |
|
||||||
|
|
||||||
if msg.Channel == "+switch-master" { |
|
||||||
parts := strings.Split(msg.Payload, " ") |
|
||||||
if parts[0] != c.masterName { |
|
||||||
internal.Logger.Printf("sentinel: ignore addr for master=%q", parts[0]) |
|
||||||
continue |
|
||||||
} |
|
||||||
addr := net.JoinHostPort(parts[3], parts[4]) |
|
||||||
c.switchMaster(addr) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func contains(slice []string, str string) bool { |
|
||||||
for _, s := range slice { |
|
||||||
if s == str { |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
@ -1,2 +1,3 @@ |
|||||||
*.rdb |
*.rdb |
||||||
testdata/*/ |
testdata/*/ |
||||||
|
.idea/ |
@ -0,0 +1,4 @@ |
|||||||
|
semi: false |
||||||
|
singleQuote: true |
||||||
|
proseWrap: always |
||||||
|
printWidth: 100 |
@ -0,0 +1,20 @@ |
|||||||
|
dist: xenial |
||||||
|
language: go |
||||||
|
|
||||||
|
services: |
||||||
|
- redis-server |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.14.x |
||||||
|
- 1.15.x |
||||||
|
- tip |
||||||
|
|
||||||
|
matrix: |
||||||
|
allow_failures: |
||||||
|
- go: tip |
||||||
|
|
||||||
|
go_import_path: github.com/go-redis/redis |
||||||
|
|
||||||
|
before_install: |
||||||
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- |
||||||
|
-b $(go env GOPATH)/bin v1.32.2 |
@ -0,0 +1,5 @@ |
|||||||
|
# Changelog |
||||||
|
|
||||||
|
> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev) |
||||||
|
|
||||||
|
See https://redis.uptrace.dev/changelog/ |
@ -0,0 +1,159 @@ |
|||||||
|
# Redis client for Golang |
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/go-redis/redis.png?branch=master)](https://travis-ci.org/go-redis/redis) |
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-redis/redis/v8)](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) |
||||||
|
[![Documentation](https://img.shields.io/badge/redis-documentation-informational)](https://redis.uptrace.dev/) |
||||||
|
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj) |
||||||
|
|
||||||
|
> :heart: [**Uptrace.dev** - distributed traces, logs, and errors in one place](https://uptrace.dev) |
||||||
|
|
||||||
|
- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions. |
||||||
|
- [Documentation](https://redis.uptrace.dev) |
||||||
|
- [Reference](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) |
||||||
|
- [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples) |
||||||
|
- [RealWorld example app](https://github.com/uptrace/go-treemux-realworld-example-app) |
||||||
|
|
||||||
|
## Ecosystem |
||||||
|
|
||||||
|
- [Redis Mock](https://github.com/go-redis/redismock). |
||||||
|
- [Distributed Locks](https://github.com/bsm/redislock). |
||||||
|
- [Redis Cache](https://github.com/go-redis/cache). |
||||||
|
- [Rate limiting](https://github.com/go-redis/redis_rate). |
||||||
|
|
||||||
|
## Features |
||||||
|
|
||||||
|
- Redis 3 commands except QUIT, MONITOR, and SYNC. |
||||||
|
- Automatic connection pooling with |
||||||
|
[circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. |
||||||
|
- [Pub/Sub](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#PubSub). |
||||||
|
- [Transactions](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-TxPipeline). |
||||||
|
- [Pipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-Pipeline) and |
||||||
|
[TxPipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-TxPipeline). |
||||||
|
- [Scripting](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Script). |
||||||
|
- [Timeouts](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Options). |
||||||
|
- [Redis Sentinel](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewFailoverClient). |
||||||
|
- [Redis Cluster](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewClusterClient). |
||||||
|
- [Cluster of Redis Servers](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-NewClusterClient--ManualSetup) |
||||||
|
without using cluster mode and Redis Sentinel. |
||||||
|
- [Ring](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewRing). |
||||||
|
- [Instrumentation](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#ex-package--Instrumentation). |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
go-redis supports 2 last Go versions and requires a Go version with |
||||||
|
[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go |
||||||
|
module: |
||||||
|
|
||||||
|
```shell |
||||||
|
go mod init github.com/my/repo |
||||||
|
``` |
||||||
|
|
||||||
|
And then install go-redis/v8 (note _v8_ in the import; omitting it is a popular mistake): |
||||||
|
|
||||||
|
```shell |
||||||
|
go get github.com/go-redis/redis/v8 |
||||||
|
``` |
||||||
|
|
||||||
|
## Quickstart |
||||||
|
|
||||||
|
```go |
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"github.com/go-redis/redis/v8" |
||||||
|
) |
||||||
|
|
||||||
|
var ctx = context.Background() |
||||||
|
|
||||||
|
func ExampleClient() { |
||||||
|
rdb := redis.NewClient(&redis.Options{ |
||||||
|
Addr: "localhost:6379", |
||||||
|
Password: "", // no password set |
||||||
|
DB: 0, // use default DB |
||||||
|
}) |
||||||
|
|
||||||
|
err := rdb.Set(ctx, "key", "value", 0).Err() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
val, err := rdb.Get(ctx, "key").Result() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
fmt.Println("key", val) |
||||||
|
|
||||||
|
val2, err := rdb.Get(ctx, "key2").Result() |
||||||
|
if err == redis.Nil { |
||||||
|
fmt.Println("key2 does not exist") |
||||||
|
} else if err != nil { |
||||||
|
panic(err) |
||||||
|
} else { |
||||||
|
fmt.Println("key2", val2) |
||||||
|
} |
||||||
|
// Output: key value |
||||||
|
// key2 does not exist |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Look and feel |
||||||
|
|
||||||
|
Some corner cases: |
||||||
|
|
||||||
|
```go |
||||||
|
// SET key value EX 10 NX |
||||||
|
set, err := rdb.SetNX(ctx, "key", "value", 10*time.Second).Result() |
||||||
|
|
||||||
|
// SET key value keepttl NX |
||||||
|
set, err := rdb.SetNX(ctx, "key", "value", redis.KeepTTL).Result() |
||||||
|
|
||||||
|
// SORT list LIMIT 0 2 ASC |
||||||
|
vals, err := rdb.Sort(ctx, "list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() |
||||||
|
|
||||||
|
// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 |
||||||
|
vals, err := rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{ |
||||||
|
Min: "-inf", |
||||||
|
Max: "+inf", |
||||||
|
Offset: 0, |
||||||
|
Count: 2, |
||||||
|
}).Result() |
||||||
|
|
||||||
|
// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM |
||||||
|
vals, err := rdb.ZInterStore(ctx, "out", &redis.ZStore{ |
||||||
|
Keys: []string{"zset1", "zset2"}, |
||||||
|
Weights: []int64{2, 3} |
||||||
|
}).Result() |
||||||
|
|
||||||
|
// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" |
||||||
|
vals, err := rdb.Eval(ctx, "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() |
||||||
|
|
||||||
|
// custom command |
||||||
|
res, err := rdb.Do(ctx, "set", "key", "value").Result() |
||||||
|
``` |
||||||
|
## Run the test |
||||||
|
go-redis will start a redis-server and run the test cases. |
||||||
|
|
||||||
|
The paths of redis-server bin file and redis config file are definded in `main_test.go`: |
||||||
|
``` |
||||||
|
var ( |
||||||
|
redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server")) |
||||||
|
redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "redis.conf")) |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
For local testing, you can change the variables to refer to your local files, or create a soft link to the corresponding folder for redis-server and copy the config file to `testdata/redis/`: |
||||||
|
``` |
||||||
|
ln -s /usr/bin/redis-server ./go-redis/testdata/redis/src |
||||||
|
cp ./go-redis/testdata/redis.conf ./go-redis/testdata/redis/ |
||||||
|
``` |
||||||
|
|
||||||
|
Lastly, run: |
||||||
|
``` |
||||||
|
go test |
||||||
|
``` |
||||||
|
|
||||||
|
## See also |
||||||
|
|
||||||
|
- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux) |
||||||
|
- [Golang PostgreSQL ORM](https://github.com/go-pg/pg) |
||||||
|
- [Golang msgpack](https://github.com/vmihailenco/msgpack) |
||||||
|
- [Golang message task queue](https://github.com/vmihailenco/taskq) |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@ |
|||||||
|
package redis |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"sync/atomic" |
||||||
|
) |
||||||
|
|
||||||
|
func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd { |
||||||
|
cmd := NewIntCmd(ctx, "dbsize") |
||||||
|
var size int64 |
||||||
|
err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error { |
||||||
|
n, err := master.DBSize(ctx).Result() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
atomic.AddInt64(&size, n) |
||||||
|
return nil |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
cmd.SetErr(err) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
cmd.val = size |
||||||
|
return cmd |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@ |
|||||||
|
module github.com/go-redis/redis/v8 |
||||||
|
|
||||||
|
go 1.13 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 |
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f |
||||||
|
github.com/onsi/ginkgo v1.15.0 |
||||||
|
github.com/onsi/gomega v1.10.5 |
||||||
|
go.opentelemetry.io/otel v0.16.0 |
||||||
|
) |
@ -0,0 +1,97 @@ |
|||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= |
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= |
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= |
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= |
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= |
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= |
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= |
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= |
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= |
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= |
||||||
|
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= |
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||||
|
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= |
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= |
||||||
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= |
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= |
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= |
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= |
||||||
|
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= |
||||||
|
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= |
||||||
|
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= |
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= |
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= |
||||||
|
github.com/onsi/gomega v1.10.4 h1:NiTx7EEvBzu9sFOD1zORteLSt3o8gnlvZZwSE9TnY9U= |
||||||
|
github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= |
||||||
|
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= |
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= |
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
||||||
|
go.opentelemetry.io/otel v0.16.0 h1:uIWEbdeb4vpKPGITLsRVUS44L5oDbDUCZxn8lkxhmgw= |
||||||
|
go.opentelemetry.io/otel v0.16.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= |
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= |
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
||||||
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= |
||||||
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= |
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
||||||
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= |
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= |
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= |
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= |
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= |
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= |
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= |
||||||
|
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= |
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= |
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= |
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= |
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= |
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,56 @@ |
|||||||
|
package internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func AppendArg(b []byte, v interface{}) []byte { |
||||||
|
switch v := v.(type) { |
||||||
|
case nil: |
||||||
|
return append(b, "<nil>"...) |
||||||
|
case string: |
||||||
|
return appendUTF8String(b, Bytes(v)) |
||||||
|
case []byte: |
||||||
|
return appendUTF8String(b, v) |
||||||
|
case int: |
||||||
|
return strconv.AppendInt(b, int64(v), 10) |
||||||
|
case int8: |
||||||
|
return strconv.AppendInt(b, int64(v), 10) |
||||||
|
case int16: |
||||||
|
return strconv.AppendInt(b, int64(v), 10) |
||||||
|
case int32: |
||||||
|
return strconv.AppendInt(b, int64(v), 10) |
||||||
|
case int64: |
||||||
|
return strconv.AppendInt(b, v, 10) |
||||||
|
case uint: |
||||||
|
return strconv.AppendUint(b, uint64(v), 10) |
||||||
|
case uint8: |
||||||
|
return strconv.AppendUint(b, uint64(v), 10) |
||||||
|
case uint16: |
||||||
|
return strconv.AppendUint(b, uint64(v), 10) |
||||||
|
case uint32: |
||||||
|
return strconv.AppendUint(b, uint64(v), 10) |
||||||
|
case uint64: |
||||||
|
return strconv.AppendUint(b, v, 10) |
||||||
|
case float32: |
||||||
|
return strconv.AppendFloat(b, float64(v), 'f', -1, 64) |
||||||
|
case float64: |
||||||
|
return strconv.AppendFloat(b, v, 'f', -1, 64) |
||||||
|
case bool: |
||||||
|
if v { |
||||||
|
return append(b, "true"...) |
||||||
|
} |
||||||
|
return append(b, "false"...) |
||||||
|
case time.Time: |
||||||
|
return v.AppendFormat(b, time.RFC3339Nano) |
||||||
|
default: |
||||||
|
return append(b, fmt.Sprint(v)...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func appendUTF8String(dst []byte, src []byte) []byte { |
||||||
|
dst = append(dst, src...) |
||||||
|
return dst |
||||||
|
} |
@ -1,8 +1,9 @@ |
|||||||
package hashtag |
package hashtag |
||||||
|
|
||||||
import ( |
import ( |
||||||
"math/rand" |
|
||||||
"strings" |
"strings" |
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/rand" |
||||||
) |
) |
||||||
|
|
||||||
const slotNumber = 16384 |
const slotNumber = 16384 |
@ -0,0 +1,151 @@ |
|||||||
|
package hscan |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// decoderFunc represents decoding functions for default built-in types.
|
||||||
|
type decoderFunc func(reflect.Value, string) error |
||||||
|
|
||||||
|
var ( |
||||||
|
// List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1).
|
||||||
|
decoders = []decoderFunc{ |
||||||
|
reflect.Bool: decodeBool, |
||||||
|
reflect.Int: decodeInt, |
||||||
|
reflect.Int8: decodeInt, |
||||||
|
reflect.Int16: decodeInt, |
||||||
|
reflect.Int32: decodeInt, |
||||||
|
reflect.Int64: decodeInt, |
||||||
|
reflect.Uint: decodeUint, |
||||||
|
reflect.Uint8: decodeUint, |
||||||
|
reflect.Uint16: decodeUint, |
||||||
|
reflect.Uint32: decodeUint, |
||||||
|
reflect.Uint64: decodeUint, |
||||||
|
reflect.Float32: decodeFloat, |
||||||
|
reflect.Float64: decodeFloat, |
||||||
|
reflect.Complex64: decodeUnsupported, |
||||||
|
reflect.Complex128: decodeUnsupported, |
||||||
|
reflect.Array: decodeUnsupported, |
||||||
|
reflect.Chan: decodeUnsupported, |
||||||
|
reflect.Func: decodeUnsupported, |
||||||
|
reflect.Interface: decodeUnsupported, |
||||||
|
reflect.Map: decodeUnsupported, |
||||||
|
reflect.Ptr: decodeUnsupported, |
||||||
|
reflect.Slice: decodeSlice, |
||||||
|
reflect.String: decodeString, |
||||||
|
reflect.Struct: decodeUnsupported, |
||||||
|
reflect.UnsafePointer: decodeUnsupported, |
||||||
|
} |
||||||
|
|
||||||
|
// Global map of struct field specs that is populated once for every new
|
||||||
|
// struct type that is scanned. This caches the field types and the corresponding
|
||||||
|
// decoder functions to avoid iterating through struct fields on subsequent scans.
|
||||||
|
globalStructMap = newStructMap() |
||||||
|
) |
||||||
|
|
||||||
|
func Struct(dst interface{}) (StructValue, error) { |
||||||
|
v := reflect.ValueOf(dst) |
||||||
|
|
||||||
|
// The dstination to scan into should be a struct pointer.
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() { |
||||||
|
return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) |
||||||
|
} |
||||||
|
|
||||||
|
v = v.Elem() |
||||||
|
if v.Kind() != reflect.Struct { |
||||||
|
return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) |
||||||
|
} |
||||||
|
|
||||||
|
return StructValue{ |
||||||
|
spec: globalStructMap.get(v.Type()), |
||||||
|
value: v, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Scan scans the results from a key-value Redis map result set to a destination struct.
|
||||||
|
// The Redis keys are matched to the struct's field with the `redis` tag.
|
||||||
|
func Scan(dst interface{}, keys []interface{}, vals []interface{}) error { |
||||||
|
if len(keys) != len(vals) { |
||||||
|
return errors.New("args should have the same number of keys and vals") |
||||||
|
} |
||||||
|
|
||||||
|
strct, err := Struct(dst) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Iterate through the (key, value) sequence.
|
||||||
|
for i := 0; i < len(vals); i++ { |
||||||
|
key, ok := keys[i].(string) |
||||||
|
if !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
val, ok := vals[i].(string) |
||||||
|
if !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := strct.Scan(key, val); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeBool(f reflect.Value, s string) error { |
||||||
|
b, err := strconv.ParseBool(s) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
f.SetBool(b) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeInt(f reflect.Value, s string) error { |
||||||
|
v, err := strconv.ParseInt(s, 10, 0) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
f.SetInt(v) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeUint(f reflect.Value, s string) error { |
||||||
|
v, err := strconv.ParseUint(s, 10, 0) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
f.SetUint(v) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeFloat(f reflect.Value, s string) error { |
||||||
|
v, err := strconv.ParseFloat(s, 0) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
f.SetFloat(v) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeString(f reflect.Value, s string) error { |
||||||
|
f.SetString(s) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeSlice(f reflect.Value, s string) error { |
||||||
|
// []byte slice ([]uint8).
|
||||||
|
if f.Type().Elem().Kind() == reflect.Uint8 { |
||||||
|
f.SetBytes([]byte(s)) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func decodeUnsupported(v reflect.Value, s string) error { |
||||||
|
return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package hscan |
||||||
|
|
||||||
|
import ( |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
// structMap contains the map of struct fields for target structs
|
||||||
|
// indexed by the struct type.
|
||||||
|
type structMap struct { |
||||||
|
m sync.Map |
||||||
|
} |
||||||
|
|
||||||
|
func newStructMap() *structMap { |
||||||
|
return new(structMap) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *structMap) get(t reflect.Type) *structSpec { |
||||||
|
if v, ok := s.m.Load(t); ok { |
||||||
|
return v.(*structSpec) |
||||||
|
} |
||||||
|
|
||||||
|
spec := newStructSpec(t, "redis") |
||||||
|
s.m.Store(t, spec) |
||||||
|
return spec |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// structSpec contains the list of all fields in a target struct.
|
||||||
|
type structSpec struct { |
||||||
|
m map[string]*structField |
||||||
|
} |
||||||
|
|
||||||
|
func (s *structSpec) set(tag string, sf *structField) { |
||||||
|
s.m[tag] = sf |
||||||
|
} |
||||||
|
|
||||||
|
func newStructSpec(t reflect.Type, fieldTag string) *structSpec { |
||||||
|
out := &structSpec{ |
||||||
|
m: make(map[string]*structField), |
||||||
|
} |
||||||
|
|
||||||
|
num := t.NumField() |
||||||
|
for i := 0; i < num; i++ { |
||||||
|
f := t.Field(i) |
||||||
|
|
||||||
|
tag := f.Tag.Get(fieldTag) |
||||||
|
if tag == "" || tag == "-" { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
tag = strings.Split(tag, ",")[0] |
||||||
|
if tag == "" { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Use the built-in decoder.
|
||||||
|
out.set(tag, &structField{index: i, fn: decoders[f.Type.Kind()]}) |
||||||
|
} |
||||||
|
|
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// structField represents a single field in a target struct.
|
||||||
|
type structField struct { |
||||||
|
index int |
||||||
|
fn decoderFunc |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type StructValue struct { |
||||||
|
spec *structSpec |
||||||
|
value reflect.Value |
||||||
|
} |
||||||
|
|
||||||
|
func (s StructValue) Scan(key string, value string) error { |
||||||
|
field, ok := s.spec.m[key] |
||||||
|
if !ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return field.fn(s.value.Field(field.index), value) |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
package internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel" |
||||||
|
"go.opentelemetry.io/otel/metric" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// WritesCounter is a count of write commands performed.
|
||||||
|
WritesCounter metric.Int64Counter |
||||||
|
// NewConnectionsCounter is a count of new connections.
|
||||||
|
NewConnectionsCounter metric.Int64Counter |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
defer func() { |
||||||
|
if r := recover(); r != nil { |
||||||
|
Logger.Printf(context.Background(), "Error creating meter github.com/go-redis/redis for Instruments", r) |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
meter := metric.Must(otel.Meter("github.com/go-redis/redis")) |
||||||
|
|
||||||
|
WritesCounter = meter.NewInt64Counter("redis.writes", |
||||||
|
metric.WithDescription("the number of writes initiated"), |
||||||
|
) |
||||||
|
|
||||||
|
NewConnectionsCounter = meter.NewInt64Counter("redis.new_connections", |
||||||
|
metric.WithDescription("the number of connections created"), |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/rand" |
||||||
|
) |
||||||
|
|
||||||
|
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { |
||||||
|
if retry < 0 { |
||||||
|
panic("not reached") |
||||||
|
} |
||||||
|
if minBackoff == 0 { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
d := minBackoff << uint(retry) |
||||||
|
if d < minBackoff { |
||||||
|
return maxBackoff |
||||||
|
} |
||||||
|
|
||||||
|
d = minBackoff + time.Duration(rand.Int63n(int64(d))) |
||||||
|
|
||||||
|
if d > maxBackoff || d < minBackoff { |
||||||
|
d = maxBackoff |
||||||
|
} |
||||||
|
|
||||||
|
return d |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
package internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
type Logging interface { |
||||||
|
Printf(ctx context.Context, format string, v ...interface{}) |
||||||
|
} |
||||||
|
|
||||||
|
type logger struct { |
||||||
|
log *log.Logger |
||||||
|
} |
||||||
|
|
||||||
|
func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) { |
||||||
|
_ = l.log.Output(2, fmt.Sprintf(format, v...)) |
||||||
|
} |
||||||
|
|
||||||
|
var Logger Logging = &logger{ |
||||||
|
log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile), |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
package pool |
||||||
|
|
||||||
|
import "context" |
||||||
|
|
||||||
|
type SingleConnPool struct { |
||||||
|
pool Pooler |
||||||
|
cn *Conn |
||||||
|
stickyErr error |
||||||
|
} |
||||||
|
|
||||||
|
var _ Pooler = (*SingleConnPool)(nil) |
||||||
|
|
||||||
|
func NewSingleConnPool(pool Pooler, cn *Conn) *SingleConnPool { |
||||||
|
return &SingleConnPool{ |
||||||
|
pool: pool, |
||||||
|
cn: cn, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) NewConn(ctx context.Context) (*Conn, error) { |
||||||
|
return p.pool.NewConn(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) CloseConn(cn *Conn) error { |
||||||
|
return p.pool.CloseConn(cn) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Get(ctx context.Context) (*Conn, error) { |
||||||
|
if p.stickyErr != nil { |
||||||
|
return nil, p.stickyErr |
||||||
|
} |
||||||
|
return p.cn, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Put(ctx context.Context, cn *Conn) {} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Remove(ctx context.Context, cn *Conn, reason error) { |
||||||
|
p.cn = nil |
||||||
|
p.stickyErr = reason |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Close() error { |
||||||
|
p.cn = nil |
||||||
|
p.stickyErr = ErrClosed |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Len() int { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) IdleLen() int { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (p *SingleConnPool) Stats() *Stats { |
||||||
|
return &Stats{} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
package rand |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/rand" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
// Int returns a non-negative pseudo-random int.
|
||||||
|
func Int() int { return pseudo.Int() } |
||||||
|
|
||||||
|
// Intn returns, as an int, a non-negative pseudo-random number in [0,n).
|
||||||
|
// It panics if n <= 0.
|
||||||
|
func Intn(n int) int { return pseudo.Intn(n) } |
||||||
|
|
||||||
|
// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n).
|
||||||
|
// It panics if n <= 0.
|
||||||
|
func Int63n(n int64) int64 { return pseudo.Int63n(n) } |
||||||
|
|
||||||
|
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n).
|
||||||
|
func Perm(n int) []int { return pseudo.Perm(n) } |
||||||
|
|
||||||
|
// Seed uses the provided seed value to initialize the default Source to a
|
||||||
|
// deterministic state. If Seed is not called, the generator behaves as if
|
||||||
|
// seeded by Seed(1).
|
||||||
|
func Seed(n int64) { pseudo.Seed(n) } |
||||||
|
|
||||||
|
var pseudo = rand.New(&source{src: rand.NewSource(1)}) |
||||||
|
|
||||||
|
type source struct { |
||||||
|
src rand.Source |
||||||
|
mu sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func (s *source) Int63() int64 { |
||||||
|
s.mu.Lock() |
||||||
|
n := s.src.Int63() |
||||||
|
s.mu.Unlock() |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func (s *source) Seed(seed int64) { |
||||||
|
s.mu.Lock() |
||||||
|
s.src.Seed(seed) |
||||||
|
s.mu.Unlock() |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package internal |
||||||
|
|
||||||
|
func String(b []byte) string { |
||||||
|
return string(b) |
||||||
|
} |
||||||
|
|
||||||
|
func Bytes(s string) []byte { |
||||||
|
return []byte(s) |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package internal |
||||||
|
|
||||||
|
import "unsafe" |
||||||
|
|
||||||
|
// String converts byte slice to string.
|
||||||
|
func String(b []byte) string { |
||||||
|
return *(*string)(unsafe.Pointer(&b)) |
||||||
|
} |
||||||
|
|
||||||
|
// Bytes converts string to byte slice.
|
||||||
|
func Bytes(s string) []byte { |
||||||
|
return *(*[]byte)(unsafe.Pointer( |
||||||
|
&struct { |
||||||
|
string |
||||||
|
Cap int |
||||||
|
}{s, len(s)}, |
||||||
|
)) |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
package internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/proto" |
||||||
|
"github.com/go-redis/redis/v8/internal/util" |
||||||
|
"go.opentelemetry.io/otel" |
||||||
|
"go.opentelemetry.io/otel/trace" |
||||||
|
) |
||||||
|
|
||||||
|
func Sleep(ctx context.Context, dur time.Duration) error { |
||||||
|
return WithSpan(ctx, "time.Sleep", func(ctx context.Context, span trace.Span) error { |
||||||
|
t := time.NewTimer(dur) |
||||||
|
defer t.Stop() |
||||||
|
|
||||||
|
select { |
||||||
|
case <-t.C: |
||||||
|
return nil |
||||||
|
case <-ctx.Done(): |
||||||
|
return ctx.Err() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func ToLower(s string) string { |
||||||
|
if isLower(s) { |
||||||
|
return s |
||||||
|
} |
||||||
|
|
||||||
|
b := make([]byte, len(s)) |
||||||
|
for i := range b { |
||||||
|
c := s[i] |
||||||
|
if c >= 'A' && c <= 'Z' { |
||||||
|
c += 'a' - 'A' |
||||||
|
} |
||||||
|
b[i] = c |
||||||
|
} |
||||||
|
return util.BytesToString(b) |
||||||
|
} |
||||||
|
|
||||||
|
func isLower(s string) bool { |
||||||
|
for i := 0; i < len(s); i++ { |
||||||
|
c := s[i] |
||||||
|
if c >= 'A' && c <= 'Z' { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var tracer = otel.Tracer("github.com/go-redis/redis") |
||||||
|
|
||||||
|
func WithSpan(ctx context.Context, name string, fn func(context.Context, trace.Span) error) error { |
||||||
|
if span := trace.SpanFromContext(ctx); !span.IsRecording() { |
||||||
|
return fn(ctx, span) |
||||||
|
} |
||||||
|
|
||||||
|
ctx, span := tracer.Start(ctx, name) |
||||||
|
defer span.End() |
||||||
|
|
||||||
|
return fn(ctx, span) |
||||||
|
} |
||||||
|
|
||||||
|
func RecordError(ctx context.Context, span trace.Span, err error) error { |
||||||
|
if err != proto.Nil { |
||||||
|
span.RecordError(err) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
274
vendor/github.com/go-redis/redis/v7/redis.go → vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
274
vendor/github.com/go-redis/redis/v7/redis.go → vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
335
vendor/github.com/go-redis/redis/v7/ring.go → vendor/github.com/go-redis/redis/v8/ring.go
generated
vendored
335
vendor/github.com/go-redis/redis/v7/ring.go → vendor/github.com/go-redis/redis/v8/ring.go
generated
vendored
@ -0,0 +1,65 @@ |
|||||||
|
package redis |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"crypto/sha1" |
||||||
|
"encoding/hex" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type Scripter interface { |
||||||
|
Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd |
||||||
|
EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd |
||||||
|
ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd |
||||||
|
ScriptLoad(ctx context.Context, script string) *StringCmd |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
_ Scripter = (*Client)(nil) |
||||||
|
_ Scripter = (*Ring)(nil) |
||||||
|
_ Scripter = (*ClusterClient)(nil) |
||||||
|
) |
||||||
|
|
||||||
|
type Script struct { |
||||||
|
src, hash string |
||||||
|
} |
||||||
|
|
||||||
|
func NewScript(src string) *Script { |
||||||
|
h := sha1.New() |
||||||
|
_, _ = io.WriteString(h, src) |
||||||
|
return &Script{ |
||||||
|
src: src, |
||||||
|
hash: hex.EncodeToString(h.Sum(nil)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Script) Hash() string { |
||||||
|
return s.hash |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Script) Load(ctx context.Context, c Scripter) *StringCmd { |
||||||
|
return c.ScriptLoad(ctx, s.src) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Script) Exists(ctx context.Context, c Scripter) *BoolSliceCmd { |
||||||
|
return c.ScriptExists(ctx, s.hash) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Script) Eval(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd { |
||||||
|
return c.Eval(ctx, s.src, keys, args...) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Script) EvalSha(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd { |
||||||
|
return c.EvalSha(ctx, s.hash, keys, args...) |
||||||
|
} |
||||||
|
|
||||||
|
// Run optimistically uses EVALSHA to run the script. If script does not exist
|
||||||
|
// it is retried using EVAL.
|
||||||
|
func (s *Script) Run(ctx context.Context, c Scripter, keys []string, args ...interface{}) *Cmd { |
||||||
|
r := s.EvalSha(ctx, c, keys, args...) |
||||||
|
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { |
||||||
|
return s.Eval(ctx, c, keys, args...) |
||||||
|
} |
||||||
|
return r |
||||||
|
} |
@ -0,0 +1,738 @@ |
|||||||
|
package redis |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"crypto/tls" |
||||||
|
"errors" |
||||||
|
"net" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal" |
||||||
|
"github.com/go-redis/redis/v8/internal/pool" |
||||||
|
"github.com/go-redis/redis/v8/internal/rand" |
||||||
|
) |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// FailoverOptions are used to configure a failover client and should
|
||||||
|
// be passed to NewFailoverClient.
|
||||||
|
type FailoverOptions struct { |
||||||
|
// The master name.
|
||||||
|
MasterName string |
||||||
|
// A seed list of host:port addresses of sentinel nodes.
|
||||||
|
SentinelAddrs []string |
||||||
|
// Sentinel password from "requirepass <password>" (if enabled) in Sentinel configuration
|
||||||
|
SentinelPassword string |
||||||
|
|
||||||
|
// Allows routing read-only commands to the closest master or slave node.
|
||||||
|
// This option only works with NewFailoverClusterClient.
|
||||||
|
RouteByLatency bool |
||||||
|
// Allows routing read-only commands to the random master or slave node.
|
||||||
|
// This option only works with NewFailoverClusterClient.
|
||||||
|
RouteRandomly bool |
||||||
|
|
||||||
|
// Route all commands to slave read-only nodes.
|
||||||
|
SlaveOnly bool |
||||||
|
|
||||||
|
// Following options are copied from Options struct.
|
||||||
|
|
||||||
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error) |
||||||
|
OnConnect func(ctx context.Context, cn *Conn) error |
||||||
|
|
||||||
|
Username string |
||||||
|
Password string |
||||||
|
DB int |
||||||
|
|
||||||
|
MaxRetries int |
||||||
|
MinRetryBackoff time.Duration |
||||||
|
MaxRetryBackoff time.Duration |
||||||
|
|
||||||
|
DialTimeout time.Duration |
||||||
|
ReadTimeout time.Duration |
||||||
|
WriteTimeout time.Duration |
||||||
|
|
||||||
|
PoolSize int |
||||||
|
MinIdleConns int |
||||||
|
MaxConnAge time.Duration |
||||||
|
PoolTimeout time.Duration |
||||||
|
IdleTimeout time.Duration |
||||||
|
IdleCheckFrequency time.Duration |
||||||
|
|
||||||
|
TLSConfig *tls.Config |
||||||
|
} |
||||||
|
|
||||||
|
func (opt *FailoverOptions) clientOptions() *Options { |
||||||
|
return &Options{ |
||||||
|
Addr: "FailoverClient", |
||||||
|
|
||||||
|
Dialer: opt.Dialer, |
||||||
|
OnConnect: opt.OnConnect, |
||||||
|
|
||||||
|
DB: opt.DB, |
||||||
|
Username: opt.Username, |
||||||
|
Password: opt.Password, |
||||||
|
|
||||||
|
MaxRetries: opt.MaxRetries, |
||||||
|
MinRetryBackoff: opt.MinRetryBackoff, |
||||||
|
MaxRetryBackoff: opt.MaxRetryBackoff, |
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout, |
||||||
|
ReadTimeout: opt.ReadTimeout, |
||||||
|
WriteTimeout: opt.WriteTimeout, |
||||||
|
|
||||||
|
PoolSize: opt.PoolSize, |
||||||
|
PoolTimeout: opt.PoolTimeout, |
||||||
|
IdleTimeout: opt.IdleTimeout, |
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency, |
||||||
|
MinIdleConns: opt.MinIdleConns, |
||||||
|
MaxConnAge: opt.MaxConnAge, |
||||||
|
|
||||||
|
TLSConfig: opt.TLSConfig, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (opt *FailoverOptions) sentinelOptions(addr string) *Options { |
||||||
|
return &Options{ |
||||||
|
Addr: addr, |
||||||
|
|
||||||
|
Dialer: opt.Dialer, |
||||||
|
OnConnect: opt.OnConnect, |
||||||
|
|
||||||
|
DB: 0, |
||||||
|
Password: opt.SentinelPassword, |
||||||
|
|
||||||
|
MaxRetries: opt.MaxRetries, |
||||||
|
MinRetryBackoff: opt.MinRetryBackoff, |
||||||
|
MaxRetryBackoff: opt.MaxRetryBackoff, |
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout, |
||||||
|
ReadTimeout: opt.ReadTimeout, |
||||||
|
WriteTimeout: opt.WriteTimeout, |
||||||
|
|
||||||
|
PoolSize: opt.PoolSize, |
||||||
|
PoolTimeout: opt.PoolTimeout, |
||||||
|
IdleTimeout: opt.IdleTimeout, |
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency, |
||||||
|
MinIdleConns: opt.MinIdleConns, |
||||||
|
MaxConnAge: opt.MaxConnAge, |
||||||
|
|
||||||
|
TLSConfig: opt.TLSConfig, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (opt *FailoverOptions) clusterOptions() *ClusterOptions { |
||||||
|
return &ClusterOptions{ |
||||||
|
Dialer: opt.Dialer, |
||||||
|
OnConnect: opt.OnConnect, |
||||||
|
|
||||||
|
Username: opt.Username, |
||||||
|
Password: opt.Password, |
||||||
|
|
||||||
|
MaxRedirects: opt.MaxRetries, |
||||||
|
|
||||||
|
RouteByLatency: opt.RouteByLatency, |
||||||
|
RouteRandomly: opt.RouteRandomly, |
||||||
|
|
||||||
|
MinRetryBackoff: opt.MinRetryBackoff, |
||||||
|
MaxRetryBackoff: opt.MaxRetryBackoff, |
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout, |
||||||
|
ReadTimeout: opt.ReadTimeout, |
||||||
|
WriteTimeout: opt.WriteTimeout, |
||||||
|
|
||||||
|
PoolSize: opt.PoolSize, |
||||||
|
PoolTimeout: opt.PoolTimeout, |
||||||
|
IdleTimeout: opt.IdleTimeout, |
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency, |
||||||
|
MinIdleConns: opt.MinIdleConns, |
||||||
|
MaxConnAge: opt.MaxConnAge, |
||||||
|
|
||||||
|
TLSConfig: opt.TLSConfig, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// NewFailoverClient returns a Redis client that uses Redis Sentinel
|
||||||
|
// for automatic failover. It's safe for concurrent use by multiple
|
||||||
|
// goroutines.
|
||||||
|
func NewFailoverClient(failoverOpt *FailoverOptions) *Client { |
||||||
|
if failoverOpt.RouteByLatency { |
||||||
|
panic("to route commands by latency, use NewFailoverClusterClient") |
||||||
|
} |
||||||
|
if failoverOpt.RouteRandomly { |
||||||
|
panic("to route commands randomly, use NewFailoverClusterClient") |
||||||
|
} |
||||||
|
|
||||||
|
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs)) |
||||||
|
copy(sentinelAddrs, failoverOpt.SentinelAddrs) |
||||||
|
|
||||||
|
failover := &sentinelFailover{ |
||||||
|
opt: failoverOpt, |
||||||
|
sentinelAddrs: sentinelAddrs, |
||||||
|
} |
||||||
|
|
||||||
|
opt := failoverOpt.clientOptions() |
||||||
|
opt.Dialer = masterSlaveDialer(failover) |
||||||
|
opt.init() |
||||||
|
|
||||||
|
connPool := newConnPool(opt) |
||||||
|
|
||||||
|
failover.mu.Lock() |
||||||
|
failover.onFailover = func(ctx context.Context, addr string) { |
||||||
|
_ = connPool.Filter(func(cn *pool.Conn) bool { |
||||||
|
return cn.RemoteAddr().String() != addr |
||||||
|
}) |
||||||
|
} |
||||||
|
failover.mu.Unlock() |
||||||
|
|
||||||
|
c := Client{ |
||||||
|
baseClient: newBaseClient(opt, connPool), |
||||||
|
ctx: context.Background(), |
||||||
|
} |
||||||
|
c.cmdable = c.Process |
||||||
|
c.onClose = failover.Close |
||||||
|
|
||||||
|
return &c |
||||||
|
} |
||||||
|
|
||||||
|
func masterSlaveDialer( |
||||||
|
failover *sentinelFailover, |
||||||
|
) func(ctx context.Context, network, addr string) (net.Conn, error) { |
||||||
|
return func(ctx context.Context, network, _ string) (net.Conn, error) { |
||||||
|
var addr string |
||||||
|
var err error |
||||||
|
|
||||||
|
if failover.opt.SlaveOnly { |
||||||
|
addr, err = failover.RandomSlaveAddr(ctx) |
||||||
|
} else { |
||||||
|
addr, err = failover.MasterAddr(ctx) |
||||||
|
if err == nil { |
||||||
|
failover.trySwitchMaster(ctx, addr) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if failover.opt.Dialer != nil { |
||||||
|
return failover.opt.Dialer(ctx, network, addr) |
||||||
|
} |
||||||
|
return net.DialTimeout("tcp", addr, failover.opt.DialTimeout) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// SentinelClient is a client for a Redis Sentinel.
|
||||||
|
type SentinelClient struct { |
||||||
|
*baseClient |
||||||
|
hooks |
||||||
|
ctx context.Context |
||||||
|
} |
||||||
|
|
||||||
|
func NewSentinelClient(opt *Options) *SentinelClient { |
||||||
|
opt.init() |
||||||
|
c := &SentinelClient{ |
||||||
|
baseClient: &baseClient{ |
||||||
|
opt: opt, |
||||||
|
connPool: newConnPool(opt), |
||||||
|
}, |
||||||
|
ctx: context.Background(), |
||||||
|
} |
||||||
|
return c |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) Context() context.Context { |
||||||
|
return c.ctx |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) WithContext(ctx context.Context) *SentinelClient { |
||||||
|
if ctx == nil { |
||||||
|
panic("nil context") |
||||||
|
} |
||||||
|
clone := *c |
||||||
|
clone.ctx = ctx |
||||||
|
return &clone |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) Process(ctx context.Context, cmd Cmder) error { |
||||||
|
return c.hooks.process(ctx, cmd, c.baseClient.process) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) pubSub() *PubSub { |
||||||
|
pubsub := &PubSub{ |
||||||
|
opt: c.opt, |
||||||
|
|
||||||
|
newConn: func(ctx context.Context, channels []string) (*pool.Conn, error) { |
||||||
|
return c.newConn(ctx) |
||||||
|
}, |
||||||
|
closeConn: c.connPool.CloseConn, |
||||||
|
} |
||||||
|
pubsub.init() |
||||||
|
return pubsub |
||||||
|
} |
||||||
|
|
||||||
|
// Ping is used to test if a connection is still alive, or to
|
||||||
|
// measure latency.
|
||||||
|
func (c *SentinelClient) Ping(ctx context.Context) *StringCmd { |
||||||
|
cmd := NewStringCmd(ctx, "ping") |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Subscribe subscribes the client to the specified channels.
|
||||||
|
// Channels can be omitted to create empty subscription.
|
||||||
|
func (c *SentinelClient) Subscribe(ctx context.Context, channels ...string) *PubSub { |
||||||
|
pubsub := c.pubSub() |
||||||
|
if len(channels) > 0 { |
||||||
|
_ = pubsub.Subscribe(ctx, channels...) |
||||||
|
} |
||||||
|
return pubsub |
||||||
|
} |
||||||
|
|
||||||
|
// PSubscribe subscribes the client to the given patterns.
|
||||||
|
// Patterns can be omitted to create empty subscription.
|
||||||
|
func (c *SentinelClient) PSubscribe(ctx context.Context, channels ...string) *PubSub { |
||||||
|
pubsub := c.pubSub() |
||||||
|
if len(channels) > 0 { |
||||||
|
_ = pubsub.PSubscribe(ctx, channels...) |
||||||
|
} |
||||||
|
return pubsub |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) GetMasterAddrByName(ctx context.Context, name string) *StringSliceCmd { |
||||||
|
cmd := NewStringSliceCmd(ctx, "sentinel", "get-master-addr-by-name", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
func (c *SentinelClient) Sentinels(ctx context.Context, name string) *SliceCmd { |
||||||
|
cmd := NewSliceCmd(ctx, "sentinel", "sentinels", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Failover forces a failover as if the master was not reachable, and without
|
||||||
|
// asking for agreement to other Sentinels.
|
||||||
|
func (c *SentinelClient) Failover(ctx context.Context, name string) *StatusCmd { |
||||||
|
cmd := NewStatusCmd(ctx, "sentinel", "failover", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Reset resets all the masters with matching name. The pattern argument is a
|
||||||
|
// glob-style pattern. The reset process clears any previous state in a master
|
||||||
|
// (including a failover in progress), and removes every slave and sentinel
|
||||||
|
// already discovered and associated with the master.
|
||||||
|
func (c *SentinelClient) Reset(ctx context.Context, pattern string) *IntCmd { |
||||||
|
cmd := NewIntCmd(ctx, "sentinel", "reset", pattern) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// FlushConfig forces Sentinel to rewrite its configuration on disk, including
|
||||||
|
// the current Sentinel state.
|
||||||
|
func (c *SentinelClient) FlushConfig(ctx context.Context) *StatusCmd { |
||||||
|
cmd := NewStatusCmd(ctx, "sentinel", "flushconfig") |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Master shows the state and info of the specified master.
|
||||||
|
func (c *SentinelClient) Master(ctx context.Context, name string) *StringStringMapCmd { |
||||||
|
cmd := NewStringStringMapCmd(ctx, "sentinel", "master", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Masters shows a list of monitored masters and their state.
|
||||||
|
func (c *SentinelClient) Masters(ctx context.Context) *SliceCmd { |
||||||
|
cmd := NewSliceCmd(ctx, "sentinel", "masters") |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Slaves shows a list of slaves for the specified master and their state.
|
||||||
|
func (c *SentinelClient) Slaves(ctx context.Context, name string) *SliceCmd { |
||||||
|
cmd := NewSliceCmd(ctx, "sentinel", "slaves", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// CkQuorum checks if the current Sentinel configuration is able to reach the
|
||||||
|
// quorum needed to failover a master, and the majority needed to authorize the
|
||||||
|
// failover. This command should be used in monitoring systems to check if a
|
||||||
|
// Sentinel deployment is ok.
|
||||||
|
func (c *SentinelClient) CkQuorum(ctx context.Context, name string) *StringCmd { |
||||||
|
cmd := NewStringCmd(ctx, "sentinel", "ckquorum", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Monitor tells the Sentinel to start monitoring a new master with the specified
|
||||||
|
// name, ip, port, and quorum.
|
||||||
|
func (c *SentinelClient) Monitor(ctx context.Context, name, ip, port, quorum string) *StringCmd { |
||||||
|
cmd := NewStringCmd(ctx, "sentinel", "monitor", name, ip, port, quorum) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Set is used in order to change configuration parameters of a specific master.
|
||||||
|
func (c *SentinelClient) Set(ctx context.Context, name, option, value string) *StringCmd { |
||||||
|
cmd := NewStringCmd(ctx, "sentinel", "set", name, option, value) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
// Remove is used in order to remove the specified master: the master will no
|
||||||
|
// longer be monitored, and will totally be removed from the internal state of
|
||||||
|
// the Sentinel.
|
||||||
|
func (c *SentinelClient) Remove(ctx context.Context, name string) *StringCmd { |
||||||
|
cmd := NewStringCmd(ctx, "sentinel", "remove", name) |
||||||
|
_ = c.Process(ctx, cmd) |
||||||
|
return cmd |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type sentinelFailover struct { |
||||||
|
opt *FailoverOptions |
||||||
|
|
||||||
|
sentinelAddrs []string |
||||||
|
|
||||||
|
onFailover func(ctx context.Context, addr string) |
||||||
|
onUpdate func(ctx context.Context) |
||||||
|
|
||||||
|
mu sync.RWMutex |
||||||
|
_masterAddr string |
||||||
|
sentinel *SentinelClient |
||||||
|
pubsub *PubSub |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) Close() error { |
||||||
|
c.mu.Lock() |
||||||
|
defer c.mu.Unlock() |
||||||
|
if c.sentinel != nil { |
||||||
|
return c.closeSentinel() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) closeSentinel() error { |
||||||
|
firstErr := c.pubsub.Close() |
||||||
|
c.pubsub = nil |
||||||
|
|
||||||
|
err := c.sentinel.Close() |
||||||
|
if err != nil && firstErr == nil { |
||||||
|
firstErr = err |
||||||
|
} |
||||||
|
c.sentinel = nil |
||||||
|
|
||||||
|
return firstErr |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) RandomSlaveAddr(ctx context.Context) (string, error) { |
||||||
|
addresses, err := c.slaveAddrs(ctx) |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
if len(addresses) == 0 { |
||||||
|
return c.MasterAddr(ctx) |
||||||
|
} |
||||||
|
return addresses[rand.Intn(len(addresses))], nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) MasterAddr(ctx context.Context) (string, error) { |
||||||
|
c.mu.RLock() |
||||||
|
sentinel := c.sentinel |
||||||
|
c.mu.RUnlock() |
||||||
|
|
||||||
|
if sentinel != nil { |
||||||
|
addr := c.getMasterAddr(ctx, sentinel) |
||||||
|
if addr != "" { |
||||||
|
return addr, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
c.mu.Lock() |
||||||
|
defer c.mu.Unlock() |
||||||
|
|
||||||
|
if c.sentinel != nil { |
||||||
|
addr := c.getMasterAddr(ctx, c.sentinel) |
||||||
|
if addr != "" { |
||||||
|
return addr, nil |
||||||
|
} |
||||||
|
_ = c.closeSentinel() |
||||||
|
} |
||||||
|
|
||||||
|
for i, sentinelAddr := range c.sentinelAddrs { |
||||||
|
sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr)) |
||||||
|
|
||||||
|
masterAddr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result() |
||||||
|
if err != nil { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName master=%q failed: %s", |
||||||
|
c.opt.MasterName, err) |
||||||
|
_ = sentinel.Close() |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Push working sentinel to the top.
|
||||||
|
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0] |
||||||
|
c.setSentinel(ctx, sentinel) |
||||||
|
|
||||||
|
addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) |
||||||
|
return addr, nil |
||||||
|
} |
||||||
|
|
||||||
|
return "", errors.New("redis: all sentinels specified in configuration are unreachable") |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) slaveAddrs(ctx context.Context) ([]string, error) { |
||||||
|
c.mu.RLock() |
||||||
|
sentinel := c.sentinel |
||||||
|
c.mu.RUnlock() |
||||||
|
|
||||||
|
if sentinel != nil { |
||||||
|
addrs := c.getSlaveAddrs(ctx, sentinel) |
||||||
|
if len(addrs) > 0 { |
||||||
|
return addrs, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
c.mu.Lock() |
||||||
|
defer c.mu.Unlock() |
||||||
|
|
||||||
|
if c.sentinel != nil { |
||||||
|
addrs := c.getSlaveAddrs(ctx, c.sentinel) |
||||||
|
if len(addrs) > 0 { |
||||||
|
return addrs, nil |
||||||
|
} |
||||||
|
_ = c.closeSentinel() |
||||||
|
} |
||||||
|
|
||||||
|
for i, sentinelAddr := range c.sentinelAddrs { |
||||||
|
sentinel := NewSentinelClient(c.opt.sentinelOptions(sentinelAddr)) |
||||||
|
|
||||||
|
slaves, err := sentinel.Slaves(ctx, c.opt.MasterName).Result() |
||||||
|
if err != nil { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: Slaves master=%q failed: %s", |
||||||
|
c.opt.MasterName, err) |
||||||
|
_ = sentinel.Close() |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Push working sentinel to the top.
|
||||||
|
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0] |
||||||
|
c.setSentinel(ctx, sentinel) |
||||||
|
|
||||||
|
addrs := parseSlaveAddrs(slaves) |
||||||
|
return addrs, nil |
||||||
|
} |
||||||
|
|
||||||
|
return []string{}, errors.New("redis: all sentinels specified in configuration are unreachable") |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) getMasterAddr(ctx context.Context, sentinel *SentinelClient) string { |
||||||
|
addr, err := sentinel.GetMasterAddrByName(ctx, c.opt.MasterName).Result() |
||||||
|
if err != nil { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: GetMasterAddrByName name=%q failed: %s", |
||||||
|
c.opt.MasterName, err) |
||||||
|
return "" |
||||||
|
} |
||||||
|
return net.JoinHostPort(addr[0], addr[1]) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) getSlaveAddrs(ctx context.Context, sentinel *SentinelClient) []string { |
||||||
|
addrs, err := sentinel.Slaves(ctx, c.opt.MasterName).Result() |
||||||
|
if err != nil { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: Slaves name=%q failed: %s", |
||||||
|
c.opt.MasterName, err) |
||||||
|
return []string{} |
||||||
|
} |
||||||
|
return parseSlaveAddrs(addrs) |
||||||
|
} |
||||||
|
|
||||||
|
func parseSlaveAddrs(addrs []interface{}) []string { |
||||||
|
nodes := make([]string, 0, len(addrs)) |
||||||
|
|
||||||
|
for _, node := range addrs { |
||||||
|
ip := "" |
||||||
|
port := "" |
||||||
|
flags := []string{} |
||||||
|
lastkey := "" |
||||||
|
isDown := false |
||||||
|
|
||||||
|
for _, key := range node.([]interface{}) { |
||||||
|
switch lastkey { |
||||||
|
case "ip": |
||||||
|
ip = key.(string) |
||||||
|
case "port": |
||||||
|
port = key.(string) |
||||||
|
case "flags": |
||||||
|
flags = strings.Split(key.(string), ",") |
||||||
|
} |
||||||
|
lastkey = key.(string) |
||||||
|
} |
||||||
|
|
||||||
|
for _, flag := range flags { |
||||||
|
switch flag { |
||||||
|
case "s_down", "o_down", "disconnected": |
||||||
|
isDown = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if !isDown { |
||||||
|
nodes = append(nodes, net.JoinHostPort(ip, port)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nodes |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) trySwitchMaster(ctx context.Context, addr string) { |
||||||
|
c.mu.RLock() |
||||||
|
currentAddr := c._masterAddr |
||||||
|
c.mu.RUnlock() |
||||||
|
|
||||||
|
if addr == currentAddr { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
c.mu.Lock() |
||||||
|
defer c.mu.Unlock() |
||||||
|
|
||||||
|
if addr == c._masterAddr { |
||||||
|
return |
||||||
|
} |
||||||
|
c._masterAddr = addr |
||||||
|
|
||||||
|
internal.Logger.Printf(ctx, "sentinel: new master=%q addr=%q", |
||||||
|
c.opt.MasterName, addr) |
||||||
|
if c.onFailover != nil { |
||||||
|
c.onFailover(ctx, addr) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) setSentinel(ctx context.Context, sentinel *SentinelClient) { |
||||||
|
if c.sentinel != nil { |
||||||
|
panic("not reached") |
||||||
|
} |
||||||
|
c.sentinel = sentinel |
||||||
|
c.discoverSentinels(ctx) |
||||||
|
|
||||||
|
c.pubsub = sentinel.Subscribe(ctx, "+switch-master", "+slave-reconf-done") |
||||||
|
go c.listen(c.pubsub) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) discoverSentinels(ctx context.Context) { |
||||||
|
sentinels, err := c.sentinel.Sentinels(ctx, c.opt.MasterName).Result() |
||||||
|
if err != nil { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: Sentinels master=%q failed: %s", c.opt.MasterName, err) |
||||||
|
return |
||||||
|
} |
||||||
|
for _, sentinel := range sentinels { |
||||||
|
vals := sentinel.([]interface{}) |
||||||
|
for i := 0; i < len(vals); i += 2 { |
||||||
|
key := vals[i].(string) |
||||||
|
if key == "name" { |
||||||
|
sentinelAddr := vals[i+1].(string) |
||||||
|
if !contains(c.sentinelAddrs, sentinelAddr) { |
||||||
|
internal.Logger.Printf(ctx, "sentinel: discovered new sentinel=%q for master=%q", |
||||||
|
sentinelAddr, c.opt.MasterName) |
||||||
|
c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *sentinelFailover) listen(pubsub *PubSub) { |
||||||
|
ctx := context.TODO() |
||||||
|
|
||||||
|
if c.onUpdate != nil { |
||||||
|
c.onUpdate(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
ch := pubsub.Channel() |
||||||
|
for msg := range ch { |
||||||
|
if msg.Channel == "+switch-master" { |
||||||
|
parts := strings.Split(msg.Payload, " ") |
||||||
|
if parts[0] != c.opt.MasterName { |
||||||
|
internal.Logger.Printf(pubsub.getContext(), "sentinel: ignore addr for master=%q", parts[0]) |
||||||
|
continue |
||||||
|
} |
||||||
|
addr := net.JoinHostPort(parts[3], parts[4]) |
||||||
|
c.trySwitchMaster(pubsub.getContext(), addr) |
||||||
|
} |
||||||
|
|
||||||
|
if c.onUpdate != nil { |
||||||
|
c.onUpdate(ctx) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func contains(slice []string, str string) bool { |
||||||
|
for _, s := range slice { |
||||||
|
if s == str { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewFailoverClusterClient returns a client that supports routing read-only commands
|
||||||
|
// to a slave node.
|
||||||
|
func NewFailoverClusterClient(failoverOpt *FailoverOptions) *ClusterClient { |
||||||
|
sentinelAddrs := make([]string, len(failoverOpt.SentinelAddrs)) |
||||||
|
copy(sentinelAddrs, failoverOpt.SentinelAddrs) |
||||||
|
|
||||||
|
failover := &sentinelFailover{ |
||||||
|
opt: failoverOpt, |
||||||
|
sentinelAddrs: sentinelAddrs, |
||||||
|
} |
||||||
|
|
||||||
|
opt := failoverOpt.clusterOptions() |
||||||
|
opt.ClusterSlots = func(ctx context.Context) ([]ClusterSlot, error) { |
||||||
|
masterAddr, err := failover.MasterAddr(ctx) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
nodes := []ClusterNode{{ |
||||||
|
Addr: masterAddr, |
||||||
|
}} |
||||||
|
|
||||||
|
slaveAddrs, err := failover.slaveAddrs(ctx) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
for _, slaveAddr := range slaveAddrs { |
||||||
|
nodes = append(nodes, ClusterNode{ |
||||||
|
Addr: slaveAddr, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
slots := []ClusterSlot{ |
||||||
|
{ |
||||||
|
Start: 0, |
||||||
|
End: 16383, |
||||||
|
Nodes: nodes, |
||||||
|
}, |
||||||
|
} |
||||||
|
return slots, nil |
||||||
|
} |
||||||
|
|
||||||
|
c := NewClusterClient(opt) |
||||||
|
|
||||||
|
failover.mu.Lock() |
||||||
|
failover.onUpdate = func(ctx context.Context) { |
||||||
|
c.ReloadState(ctx) |
||||||
|
} |
||||||
|
failover.mu.Unlock() |
||||||
|
|
||||||
|
return c |
||||||
|
} |
51
vendor/github.com/go-redis/redis/v7/tx.go → vendor/github.com/go-redis/redis/v8/tx.go
generated
vendored
51
vendor/github.com/go-redis/redis/v7/tx.go → vendor/github.com/go-redis/redis/v8/tx.go
generated
vendored
@ -0,0 +1,23 @@ |
|||||||
|
.DS_Store |
||||||
|
Thumbs.db |
||||||
|
|
||||||
|
.tools/ |
||||||
|
.idea/ |
||||||
|
.vscode/ |
||||||
|
*.iml |
||||||
|
*.so |
||||||
|
coverage.* |
||||||
|
|
||||||
|
gen/ |
||||||
|
|
||||||
|
/example/grpc/client/client |
||||||
|
/example/grpc/server/server |
||||||
|
/example/http/client/client |
||||||
|
/example/http/server/server |
||||||
|
/example/jaeger/jaeger |
||||||
|
/example/namedtracer/namedtracer |
||||||
|
/example/opencensus/opencensus |
||||||
|
/example/prometheus/prometheus |
||||||
|
/example/prom-collector/prom-collector |
||||||
|
/example/zipkin/zipkin |
||||||
|
/example/otel-collector/otel-collector |
@ -0,0 +1,3 @@ |
|||||||
|
[submodule "opentelemetry-proto"] |
||||||
|
path = exporters/otlp/internal/opentelemetry-proto |
||||||
|
url = https://github.com/open-telemetry/opentelemetry-proto |
@ -0,0 +1,32 @@ |
|||||||
|
# See https://github.com/golangci/golangci-lint#config-file |
||||||
|
run: |
||||||
|
issues-exit-code: 1 #Default |
||||||
|
tests: true #Default |
||||||
|
|
||||||
|
linters: |
||||||
|
enable: |
||||||
|
- misspell |
||||||
|
- goimports |
||||||
|
- golint |
||||||
|
- gofmt |
||||||
|
|
||||||
|
issues: |
||||||
|
exclude-rules: |
||||||
|
# helpers in tests often (rightfully) pass a *testing.T as their first argument |
||||||
|
- path: _test\.go |
||||||
|
text: "context.Context should be the first parameter of a function" |
||||||
|
linters: |
||||||
|
- golint |
||||||
|
# Yes, they are, but it's okay in a test |
||||||
|
- path: _test\.go |
||||||
|
text: "exported func.*returns unexported type.*which can be annoying to use" |
||||||
|
linters: |
||||||
|
- golint |
||||||
|
|
||||||
|
linters-settings: |
||||||
|
misspell: |
||||||
|
locale: US |
||||||
|
ignore-words: |
||||||
|
- cancelled |
||||||
|
goimports: |
||||||
|
local-prefixes: go.opentelemetry.io |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,17 @@ |
|||||||
|
##################################################### |
||||||
|
# |
||||||
|
# List of approvers for this repository |
||||||
|
# |
||||||
|
##################################################### |
||||||
|
# |
||||||
|
# Learn about membership in OpenTelemetry community: |
||||||
|
# https://github.com/open-telemetry/community/blob/master/community-membership.md |
||||||
|
# |
||||||
|
# |
||||||
|
# Learn about CODEOWNERS file format: |
||||||
|
# https://help.github.com/en/articles/about-code-owners |
||||||
|
# |
||||||
|
|
||||||
|
* @jmacd @lizthegrey @MrAlias @Aneurysm9 @evantorrie @XSAM @dashpole |
||||||
|
|
||||||
|
CODEOWNERS @MrAlias @Aneurysm9 |
@ -0,0 +1,374 @@ |
|||||||
|
# Contributing to opentelemetry-go |
||||||
|
|
||||||
|
The Go special interest group (SIG) meets regularly. See the |
||||||
|
OpenTelemetry |
||||||
|
[community](https://github.com/open-telemetry/community#golang-sdk) |
||||||
|
repo for information on this and other language SIGs. |
||||||
|
|
||||||
|
See the [public meeting |
||||||
|
notes](https://docs.google.com/document/d/1A63zSWX0x2CyCK_LoNhmQC4rqhLpYXJzXbEPDUQ2n6w/edit#heading=h.9tngw7jdwd6b) |
||||||
|
for a summary description of past meetings. To request edit access, |
||||||
|
join the meeting or get in touch on |
||||||
|
[Gitter](https://gitter.im/open-telemetry/opentelemetry-go). |
||||||
|
|
||||||
|
## Development |
||||||
|
|
||||||
|
You can view and edit the source code by cloning this repository: |
||||||
|
|
||||||
|
```bash |
||||||
|
git clone https://github.com/open-telemetry/opentelemetry-go.git |
||||||
|
``` |
||||||
|
|
||||||
|
Run `make test` to run the tests instead of `go test`. |
||||||
|
|
||||||
|
There are some generated files checked into the repo. To make sure |
||||||
|
that the generated files are up-to-date, run `make` (or `make |
||||||
|
precommit` - the `precommit` target is the default). |
||||||
|
|
||||||
|
The `precommit` target also fixes the formatting of the code and |
||||||
|
checks the status of the go module files. |
||||||
|
|
||||||
|
If after running `make precommit` the output of `git status` contains |
||||||
|
`nothing to commit, working tree clean` then it means that everything |
||||||
|
is up-to-date and properly formatted. |
||||||
|
|
||||||
|
## Pull Requests |
||||||
|
|
||||||
|
### How to Send Pull Requests |
||||||
|
|
||||||
|
Everyone is welcome to contribute code to `opentelemetry-go` via |
||||||
|
GitHub pull requests (PRs). |
||||||
|
|
||||||
|
To create a new PR, fork the project in GitHub and clone the upstream |
||||||
|
repo: |
||||||
|
|
||||||
|
```sh |
||||||
|
$ go get -d go.opentelemetry.io/otel |
||||||
|
``` |
||||||
|
|
||||||
|
(This may print some warning about "build constraints exclude all Go |
||||||
|
files", just ignore it.) |
||||||
|
|
||||||
|
This will put the project in `${GOPATH}/src/go.opentelemetry.io/otel`. You |
||||||
|
can alternatively use `git` directly with: |
||||||
|
|
||||||
|
```sh |
||||||
|
$ git clone https://github.com/open-telemetry/opentelemetry-go |
||||||
|
``` |
||||||
|
|
||||||
|
(Note that `git clone` is *not* using the `go.opentelemetry.io/otel` name - |
||||||
|
that name is a kind of a redirector to GitHub that `go get` can |
||||||
|
understand, but `git` does not.) |
||||||
|
|
||||||
|
This would put the project in the `opentelemetry-go` directory in |
||||||
|
current working directory. |
||||||
|
|
||||||
|
Enter the newly created directory and add your fork as a new remote: |
||||||
|
|
||||||
|
```sh |
||||||
|
$ git remote add <YOUR_FORK> git@github.com:<YOUR_GITHUB_USERNAME>/opentelemetry-go |
||||||
|
``` |
||||||
|
|
||||||
|
Check out a new branch, make modifications, run linters and tests, update |
||||||
|
`CHANGELOG.md`, and push the branch to your fork: |
||||||
|
|
||||||
|
```sh |
||||||
|
$ git checkout -b <YOUR_BRANCH_NAME> |
||||||
|
# edit files |
||||||
|
# update changelog |
||||||
|
$ make precommit |
||||||
|
$ git add -p |
||||||
|
$ git commit |
||||||
|
$ git push <YOUR_FORK> <YOUR_BRANCH_NAME> |
||||||
|
``` |
||||||
|
|
||||||
|
Open a pull request against the main `opentelemetry-go` repo. Be sure to add the pull |
||||||
|
request ID to the entry you added to `CHANGELOG.md`. |
||||||
|
|
||||||
|
### How to Receive Comments |
||||||
|
|
||||||
|
* If the PR is not ready for review, please put `[WIP]` in the title, |
||||||
|
tag it as `work-in-progress`, or mark it as |
||||||
|
[`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). |
||||||
|
* Make sure CLA is signed and CI is clear. |
||||||
|
|
||||||
|
### How to Get PRs Merged |
||||||
|
|
||||||
|
A PR is considered to be **ready to merge** when: |
||||||
|
|
||||||
|
* It has received two approvals from Collaborators/Maintainers (at |
||||||
|
different companies). This is not enforced through technical means |
||||||
|
and a PR may be **ready to merge** with a single approval if the change |
||||||
|
and its approach have been discussed and consensus reached. |
||||||
|
* Major feedbacks are resolved. |
||||||
|
* It has been open for review for at least one working day. This gives |
||||||
|
people reasonable time to review. |
||||||
|
* Trivial changes (typo, cosmetic, doc, etc.) do not have to wait for |
||||||
|
one day and may be merged with a single Maintainer's approval. |
||||||
|
* `CHANGELOG.md` has been updated to reflect what has been |
||||||
|
added, changed, removed, or fixed. |
||||||
|
* Urgent fix can take exception as long as it has been actively |
||||||
|
communicated. |
||||||
|
|
||||||
|
Any Maintainer can merge the PR once it is **ready to merge**. |
||||||
|
|
||||||
|
## Design Choices |
||||||
|
|
||||||
|
As with other OpenTelemetry clients, opentelemetry-go follows the |
||||||
|
[opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification). |
||||||
|
|
||||||
|
It's especially valuable to read through the [library |
||||||
|
guidelines](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/library-guidelines.md). |
||||||
|
|
||||||
|
### Focus on Capabilities, Not Structure Compliance |
||||||
|
|
||||||
|
OpenTelemetry is an evolving specification, one where the desires and |
||||||
|
use cases are clear, but the method to satisfy those uses cases are |
||||||
|
not. |
||||||
|
|
||||||
|
As such, Contributions should provide functionality and behavior that |
||||||
|
conforms to the specification, but the interface and structure is |
||||||
|
flexible. |
||||||
|
|
||||||
|
It is preferable to have contributions follow the idioms of the |
||||||
|
language rather than conform to specific API names or argument |
||||||
|
patterns in the spec. |
||||||
|
|
||||||
|
For a deeper discussion, see: |
||||||
|
https://github.com/open-telemetry/opentelemetry-specification/issues/165 |
||||||
|
|
||||||
|
## Style Guide |
||||||
|
|
||||||
|
One of the primary goals of this project is that it is actually used by |
||||||
|
developers. With this goal in mind the project strives to build |
||||||
|
user-friendly and idiomatic Go code adhering to the Go community's best |
||||||
|
practices. |
||||||
|
|
||||||
|
For a non-comprehensive but foundational overview of these best practices |
||||||
|
the [Effective Go](https://golang.org/doc/effective_go.html) documentation |
||||||
|
is an excellent starting place. |
||||||
|
|
||||||
|
As a convenience for developers building this project the `make precommit` |
||||||
|
will format, lint, validate, and in some cases fix the changes you plan to |
||||||
|
submit. This check will need to pass for your changes to be able to be |
||||||
|
merged. |
||||||
|
|
||||||
|
In addition to idiomatic Go, the project has adopted certain standards for |
||||||
|
implementations of common patterns. These standards should be followed as a |
||||||
|
default, and if they are not followed documentation needs to be included as |
||||||
|
to the reasons why. |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
When creating an instantiation function for a complex `struct` it is useful |
||||||
|
to allow variable number of options to be applied. However, the strong type |
||||||
|
system of Go restricts the function design options. There are a few ways to |
||||||
|
solve this problem, but we have landed on the following design. |
||||||
|
|
||||||
|
#### `config` |
||||||
|
|
||||||
|
Configuration should be held in a `struct` named `config`, or prefixed with |
||||||
|
specific type name this Configuration applies to if there are multiple |
||||||
|
`config` in the package. This `struct` must contain configuration options. |
||||||
|
|
||||||
|
```go |
||||||
|
// config contains configuration options for a thing. |
||||||
|
type config struct { |
||||||
|
// options ... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
In general the `config` `struct` will not need to be used externally to the |
||||||
|
package and should be unexported. If, however, it is expected that the user |
||||||
|
will likely want to build custom options for the configuration, the `config` |
||||||
|
should be exported. Please, include in the documentation for the `config` |
||||||
|
how the user can extend the configuration. |
||||||
|
|
||||||
|
It is important that `config` are not shared across package boundaries. |
||||||
|
Meaning a `config` from one package should not be directly used by another. |
||||||
|
|
||||||
|
Optionally, it is common to include a `newConfig` function (with the same |
||||||
|
naming scheme). This function wraps any defaults setting and looping over |
||||||
|
all options to create a configured `config`. |
||||||
|
|
||||||
|
```go |
||||||
|
// newConfig returns an appropriately configured config. |
||||||
|
func newConfig([]Option) config { |
||||||
|
// Set default values for config. |
||||||
|
config := config{/* […] */} |
||||||
|
for _, option := range options { |
||||||
|
option.Apply(&config) |
||||||
|
} |
||||||
|
// Preform any validation here. |
||||||
|
return config |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
If validation of the `config` options is also preformed this can return an |
||||||
|
error as well that is expected to be handled by the instantiation function |
||||||
|
or propagated to the user. |
||||||
|
|
||||||
|
Given the design goal of not having the user need to work with the `config`, |
||||||
|
the `newConfig` function should also be unexported. |
||||||
|
|
||||||
|
#### `Option` |
||||||
|
|
||||||
|
To set the value of the options a `config` contains, a corresponding |
||||||
|
`Option` interface type should be used. |
||||||
|
|
||||||
|
```go |
||||||
|
type Option interface { |
||||||
|
Apply(*config) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The name of the interface should be prefixed in the same way the |
||||||
|
corresponding `config` is (if at all). |
||||||
|
|
||||||
|
#### Options |
||||||
|
|
||||||
|
All user configurable options for a `config` must have a related unexported |
||||||
|
implementation of the `Option` interface and an exported configuration |
||||||
|
function that wraps this implementation. |
||||||
|
|
||||||
|
The wrapping function name should be prefixed with `With*` (or in the |
||||||
|
special case of a boolean options `Without*`) and should have the following |
||||||
|
function signature. |
||||||
|
|
||||||
|
```go |
||||||
|
func With*(…) Option { … } |
||||||
|
``` |
||||||
|
|
||||||
|
##### `bool` Options |
||||||
|
|
||||||
|
```go |
||||||
|
type defaultFalseOption bool |
||||||
|
|
||||||
|
func (o defaultFalseOption) Apply(c *config) { |
||||||
|
c.Bool = bool(o) |
||||||
|
} |
||||||
|
|
||||||
|
// WithOption sets a T* to have an option included. |
||||||
|
func WithOption() Option { |
||||||
|
return defaultFalseOption(true) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
```go |
||||||
|
type defaultTrueOption bool |
||||||
|
|
||||||
|
func (o defaultTrueOption) Apply(c *config) { |
||||||
|
c.Bool = bool(o) |
||||||
|
} |
||||||
|
|
||||||
|
// WithoutOption sets a T* to have Bool option excluded. |
||||||
|
func WithoutOption() Option { |
||||||
|
return defaultTrueOption(false) |
||||||
|
} |
||||||
|
```` |
||||||
|
|
||||||
|
##### Declared Type Options |
||||||
|
|
||||||
|
```go |
||||||
|
type myTypeOption struct { |
||||||
|
MyType MyType |
||||||
|
} |
||||||
|
|
||||||
|
func (o myTypeOption) Apply(c *config) { |
||||||
|
c.MyType = o.MyType |
||||||
|
} |
||||||
|
|
||||||
|
// WithMyType sets T* to have include MyType. |
||||||
|
func WithMyType(t MyType) Option { |
||||||
|
return myTypeOption{t} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Instantiation |
||||||
|
|
||||||
|
Using this configuration pattern to configure instantiation with a `New*` |
||||||
|
function. |
||||||
|
|
||||||
|
```go |
||||||
|
func NewT*(options ...Option) T* {…} |
||||||
|
``` |
||||||
|
|
||||||
|
Any required parameters can be declared before the variadic `options`. |
||||||
|
|
||||||
|
#### Dealing with Overlap |
||||||
|
|
||||||
|
Sometimes there are multiple complex `struct` that share common |
||||||
|
configuration and also have distinct configuration. To avoid repeated |
||||||
|
portions of `config`s, a common `config` can be used with the union of |
||||||
|
options being handled with the `Option` interface. |
||||||
|
|
||||||
|
For example. |
||||||
|
|
||||||
|
```go |
||||||
|
// config holds options for all animals. |
||||||
|
type config struct { |
||||||
|
Weight float64 |
||||||
|
Color string |
||||||
|
MaxAltitude float64 |
||||||
|
} |
||||||
|
|
||||||
|
// DogOption apply Dog specific options. |
||||||
|
type DogOption interface { |
||||||
|
ApplyDog(*config) |
||||||
|
} |
||||||
|
|
||||||
|
// BirdOption apply Bird specific options. |
||||||
|
type BirdOption interface { |
||||||
|
ApplyBird(*config) |
||||||
|
} |
||||||
|
|
||||||
|
// Option apply options for all animals. |
||||||
|
type Option interface { |
||||||
|
BirdOption |
||||||
|
DogOption |
||||||
|
} |
||||||
|
|
||||||
|
type weightOption float64 |
||||||
|
func (o weightOption) ApplyDog(c *config) { c.Weight = float64(o) } |
||||||
|
func (o weightOption) ApplyBird(c *config) { c.Weight = float64(o) } |
||||||
|
func WithWeight(w float64) Option { return weightOption(w) } |
||||||
|
|
||||||
|
type furColorOption string |
||||||
|
func (o furColorOption) ApplyDog(c *config) { c.Color = string(o) } |
||||||
|
func WithFurColor(c string) DogOption { return furColorOption(c) } |
||||||
|
|
||||||
|
type maxAltitudeOption float64 |
||||||
|
func (o maxAltitudeOption) ApplyBird(c *config) { c.MaxAltitude = float64(o) } |
||||||
|
func WithMaxAltitude(a float64) BirdOption { return maxAltitudeOption(a) } |
||||||
|
|
||||||
|
func NewDog(name string, o ...DogOption) Dog {…} |
||||||
|
func NewBird(name string, o ...BirdOption) Bird {…} |
||||||
|
``` |
||||||
|
|
||||||
|
### Interface Type |
||||||
|
|
||||||
|
To allow other developers to better comprehend the code, it is important |
||||||
|
to ensure it is sufficiently documented. One simple measure that contributes |
||||||
|
to this aim is self-documenting by naming method parameters. Therefore, |
||||||
|
where appropriate, methods of every exported interface type should have |
||||||
|
their parameters appropriately named. |
||||||
|
|
||||||
|
## Approvers and Maintainers |
||||||
|
|
||||||
|
Approvers: |
||||||
|
|
||||||
|
- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb |
||||||
|
- [Evan Torrie](https://github.com/evantorrie), Verizon Media |
||||||
|
- [Josh MacDonald](https://github.com/jmacd), LightStep |
||||||
|
- [Sam Xie](https://github.com/XSAM) |
||||||
|
- [David Ashpole](https://github.com/dashpole), Google |
||||||
|
|
||||||
|
Maintainers: |
||||||
|
|
||||||
|
- [Anthony Mirabella](https://github.com/Aneurysm9), Centene |
||||||
|
- [Tyler Yahn](https://github.com/MrAlias), New Relic |
||||||
|
|
||||||
|
### Become an Approver or a Maintainer |
||||||
|
|
||||||
|
See the [community membership document in OpenTelemetry community |
||||||
|
repo](https://github.com/open-telemetry/community/blob/master/community-membership.md). |
@ -0,0 +1,201 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work. |
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following |
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||||
|
replaced with your own identifying information. (Don't include |
||||||
|
the brackets!) The text should be enclosed in the appropriate |
||||||
|
comment syntax for the file format. We also recommend that a |
||||||
|
file or class name and description of purpose be included on the |
||||||
|
same "printed page" as the copyright notice for easier |
||||||
|
identification within third-party archives. |
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner] |
||||||
|
|
||||||
|
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. |
@ -0,0 +1,177 @@ |
|||||||
|
# Copyright The OpenTelemetry Authors
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
EXAMPLES := $(shell ./get_main_pkgs.sh ./example)
|
||||||
|
TOOLS_MOD_DIR := ./internal/tools
|
||||||
|
|
||||||
|
# All source code and documents. Used in spell check.
|
||||||
|
ALL_DOCS := $(shell find . -name '*.md' -type f | sort)
|
||||||
|
# All directories with go.mod files related to opentelemetry library. Used for building, testing and linting.
|
||||||
|
ALL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(shell find . -type f -name 'go.mod' -exec dirname {} \; | egrep -v '^./example' | sort)) $(shell find ./example -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||||
|
ALL_COVERAGE_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | egrep -v '^./example|^$(TOOLS_MOD_DIR)' | sort)
|
||||||
|
|
||||||
|
# Mac OS Catalina 10.5.x doesn't support 386. Hence skip 386 test
|
||||||
|
SKIP_386_TEST = false
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Darwin) |
||||||
|
SW_VERS := $(shell sw_vers -productVersion)
|
||||||
|
ifeq ($(shell echo $(SW_VERS) | egrep '^(10.1[5-9]|1[1-9]|[2-9])'), $(SW_VERS))
|
||||||
|
SKIP_386_TEST = true
|
||||||
|
endif
|
||||||
|
endif |
||||||
|
|
||||||
|
GOTEST_MIN = go test -timeout 30s
|
||||||
|
GOTEST = $(GOTEST_MIN) -race
|
||||||
|
GOTEST_WITH_COVERAGE = $(GOTEST) -coverprofile=coverage.out -covermode=atomic -coverpkg=./...
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := precommit
|
||||||
|
|
||||||
|
.PHONY: precommit |
||||||
|
|
||||||
|
TOOLS_DIR := $(abspath ./.tools)
|
||||||
|
|
||||||
|
$(TOOLS_DIR)/golangci-lint: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go |
||||||
|
cd $(TOOLS_MOD_DIR) && \
|
||||||
|
go build -o $(TOOLS_DIR)/golangci-lint github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
|
||||||
|
$(TOOLS_DIR)/misspell: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go |
||||||
|
cd $(TOOLS_MOD_DIR) && \
|
||||||
|
go build -o $(TOOLS_DIR)/misspell github.com/client9/misspell/cmd/misspell
|
||||||
|
|
||||||
|
$(TOOLS_DIR)/stringer: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go |
||||||
|
cd $(TOOLS_MOD_DIR) && \
|
||||||
|
go build -o $(TOOLS_DIR)/stringer golang.org/x/tools/cmd/stringer
|
||||||
|
|
||||||
|
$(TOOLS_DIR)/gojq: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go |
||||||
|
cd $(TOOLS_MOD_DIR) && \
|
||||||
|
go build -o $(TOOLS_DIR)/gojq github.com/itchyny/gojq/cmd/gojq
|
||||||
|
|
||||||
|
precommit: dependabot-check license-check generate build lint examples test-benchmarks test |
||||||
|
|
||||||
|
.PHONY: test-with-coverage |
||||||
|
test-with-coverage: |
||||||
|
set -e; \
|
||||||
|
printf "" > coverage.txt; \
|
||||||
|
for dir in $(ALL_COVERAGE_MOD_DIRS); do \
|
||||||
|
echo "go test ./... + coverage in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
$(GOTEST_WITH_COVERAGE) ./... && \
|
||||||
|
go tool cover -html=coverage.out -o coverage.html); \
|
||||||
|
[ -f "$${dir}/coverage.out" ] && cat "$${dir}/coverage.out" >> coverage.txt; \
|
||||||
|
done; \
|
||||||
|
sed -i.bak -e '2,$$ { /^mode: /d; }' coverage.txt
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: ci |
||||||
|
ci: precommit check-clean-work-tree test-with-coverage test-386 |
||||||
|
|
||||||
|
.PHONY: check-clean-work-tree |
||||||
|
check-clean-work-tree: |
||||||
|
@if ! git diff --quiet; then \
|
||||||
|
echo; \
|
||||||
|
echo 'Working tree is not clean, did you forget to run "make precommit"?'; \
|
||||||
|
echo; \
|
||||||
|
git status; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: build |
||||||
|
build: |
||||||
|
# TODO: Fix this on windows.
|
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "compiling all packages in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
go build ./... && \
|
||||||
|
go test -run xxxxxMatchNothingxxxxx ./... >/dev/null); \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: test |
||||||
|
test: |
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "go test ./... + race in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
$(GOTEST) ./...); \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: test-386 |
||||||
|
test-386: |
||||||
|
if [ $(SKIP_386_TEST) = true ] ; then \
|
||||||
|
echo "skipping the test for GOARCH 386 as it is not supported on the current OS"; \
|
||||||
|
else \
|
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "go test ./... GOARCH 386 in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
GOARCH=386 $(GOTEST_MIN) ./...); \
|
||||||
|
done; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: examples |
||||||
|
examples: |
||||||
|
@set -e; for ex in $(EXAMPLES); do \
|
||||||
|
echo "Building $${ex}"; \
|
||||||
|
(cd "$${ex}" && \
|
||||||
|
go build .); \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: test-benchmarks |
||||||
|
test-benchmarks: |
||||||
|
@set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "test benchmarks in $${dir}"; \
|
||||||
|
(cd "$${dir}" && go test -test.benchtime=1ms -run=NONE -bench=. ./...) > /dev/null; \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: lint |
||||||
|
lint: $(TOOLS_DIR)/golangci-lint $(TOOLS_DIR)/misspell |
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "golangci-lint in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
$(TOOLS_DIR)/golangci-lint run --fix && \
|
||||||
|
$(TOOLS_DIR)/golangci-lint run); \
|
||||||
|
done
|
||||||
|
$(TOOLS_DIR)/misspell -w $(ALL_DOCS)
|
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS) $(TOOLS_MOD_DIR); do \
|
||||||
|
echo "go mod tidy in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
go mod tidy); \
|
||||||
|
done
|
||||||
|
|
||||||
|
generate: $(TOOLS_DIR)/stringer |
||||||
|
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
|
||||||
|
echo "running generators in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
PATH="$(TOOLS_DIR):$${PATH}" go generate ./...); \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: license-check |
||||||
|
license-check: |
||||||
|
@licRes=$$(for f in $$(find . -type f \( -iname '*.go' -o -iname '*.sh' \) ! -path './vendor/*' ! -path './exporters/otlp/internal/opentelemetry-proto/*') ; do \
|
||||||
|
awk '/Copyright The OpenTelemetry Authors|generated|GENERATED/ && NR<=3 { found=1; next } END { if (!found) print FILENAME }' $$f; \
|
||||||
|
done); \
|
||||||
|
if [ -n "$${licRes}" ]; then \
|
||||||
|
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: dependabot-check |
||||||
|
dependabot-check: |
||||||
|
@result=$$( \
|
||||||
|
for f in $$( find . -type f -name go.mod -exec dirname {} \; | sed 's/^.\/\?/\//' ); \
|
||||||
|
do grep -q "$$f" .github/dependabot.yml \
|
||||||
|
|| echo "$$f"; \
|
||||||
|
done; \
|
||||||
|
); \
|
||||||
|
if [ -n "$$result" ]; then \
|
||||||
|
echo "missing go.mod dependabot check:"; echo "$$result"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
@ -0,0 +1,129 @@ |
|||||||
|
# -*- mode: makefile; -*- |
||||||
|
# Copyright The OpenTelemetry Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
# |
||||||
|
# This Makefile.proto has rules to generate go code for otlp |
||||||
|
# exporter. It does it by copying the proto files from |
||||||
|
# `exporters/otlp/internal/opentelemetry-proto` (which is a |
||||||
|
# submodule that needs to be checked out) into `gen/proto`, changing |
||||||
|
# the go_package option to a valid string, generating the go files and |
||||||
|
# finally copying the files into the module. The files are not |
||||||
|
# generated in place, because protoc generates a too-deep directory |
||||||
|
# structure. |
||||||
|
# |
||||||
|
# Currently, all the generated code is in |
||||||
|
# `exporters/otlp/internal/opentelemetry-proto-gen`. |
||||||
|
# |
||||||
|
# Prereqs: wget (for downloading the zip file with protoc binary), |
||||||
|
# unzip (for unpacking the archive), rsync (for copying back the |
||||||
|
# generated files). |
||||||
|
|
||||||
|
PROTOC_VERSION := 3.14.0 |
||||||
|
|
||||||
|
TOOLS_DIR := $(abspath ./.tools) |
||||||
|
TOOLS_MOD_DIR := ./internal/tools |
||||||
|
PROTOBUF_VERSION := v1 |
||||||
|
OTEL_PROTO_SUBMODULE := exporters/otlp/internal/opentelemetry-proto |
||||||
|
GEN_TEMP_DIR := gen |
||||||
|
SUBMODULE_PROTO_FILES := $(wildcard $(OTEL_PROTO_SUBMODULE)/opentelemetry/proto/*/$(PROTOBUF_VERSION)/*.proto) $(wildcard $(OTEL_PROTO_SUBMODULE)/opentelemetry/proto/collector/*/$(PROTOBUF_VERSION)/*.proto) |
||||||
|
|
||||||
|
ifeq ($(strip $(SUBMODULE_PROTO_FILES)),) |
||||||
|
$(error Submodule at $(OTEL_PROTO_SUBMODULE) is not checked out, use "git submodule update --init") |
||||||
|
endif |
||||||
|
|
||||||
|
PROTOBUF_GEN_DIR := exporters/otlp/internal/opentelemetry-proto-gen |
||||||
|
PROTOBUF_TEMP_DIR := $(GEN_TEMP_DIR)/pb-go |
||||||
|
PROTO_SOURCE_DIR := $(GEN_TEMP_DIR)/proto |
||||||
|
SOURCE_PROTO_FILES := $(subst $(OTEL_PROTO_SUBMODULE),$(PROTO_SOURCE_DIR),$(SUBMODULE_PROTO_FILES)) |
||||||
|
|
||||||
|
.DEFAULT_GOAL := protobuf |
||||||
|
|
||||||
|
UNAME_S := $(shell uname -s) |
||||||
|
UNAME_M := $(shell uname -m) |
||||||
|
|
||||||
|
ifeq ($(UNAME_S),Linux) |
||||||
|
|
||||||
|
PROTOC_OS := linux |
||||||
|
PROTOC_ARCH := $(UNAME_M) |
||||||
|
|
||||||
|
else ifeq ($(UNAME_S),Darwin) |
||||||
|
|
||||||
|
PROTOC_OS := osx |
||||||
|
PROTOC_ARCH := x86_64 |
||||||
|
|
||||||
|
endif |
||||||
|
|
||||||
|
PROTOC_ZIP_URL := https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(PROTOC_OS)-$(PROTOC_ARCH).zip |
||||||
|
|
||||||
|
$(TOOLS_DIR)/PROTOC_$(PROTOC_VERSION): |
||||||
|
@rm -f "$(TOOLS_DIR)"/PROTOC_* && \ |
||||||
|
touch "$@" |
||||||
|
|
||||||
|
# Depend on a versioned file (like PROTOC_3.14.0), so when version |
||||||
|
# gets bumped, we will depend on a nonexistent file and thus download |
||||||
|
# a newer version. |
||||||
|
$(TOOLS_DIR)/protoc/bin/protoc: $(TOOLS_DIR)/PROTOC_$(PROTOC_VERSION) |
||||||
|
echo "Fetching protoc $(PROTOC_VERSION)" && \ |
||||||
|
rm -rf $(TOOLS_DIR)/protoc && \ |
||||||
|
wget -O $(TOOLS_DIR)/protoc.zip $(PROTOC_ZIP_URL) && \ |
||||||
|
unzip $(TOOLS_DIR)/protoc.zip -d $(TOOLS_DIR)/protoc-tmp && \ |
||||||
|
rm $(TOOLS_DIR)/protoc.zip && \ |
||||||
|
touch $(TOOLS_DIR)/protoc-tmp/bin/protoc && \ |
||||||
|
mv $(TOOLS_DIR)/protoc-tmp $(TOOLS_DIR)/protoc |
||||||
|
|
||||||
|
$(TOOLS_DIR)/protoc-gen-gogofast: $(TOOLS_MOD_DIR)/go.mod $(TOOLS_MOD_DIR)/go.sum $(TOOLS_MOD_DIR)/tools.go |
||||||
|
cd $(TOOLS_MOD_DIR) && \ |
||||||
|
go build -o $(TOOLS_DIR)/protoc-gen-gogofast github.com/gogo/protobuf/protoc-gen-gogofast && \ |
||||||
|
go mod tidy |
||||||
|
|
||||||
|
# Return a sed expression for replacing the go_package option in proto |
||||||
|
# file with a one that's valid for us. |
||||||
|
# |
||||||
|
# Example: $(call get-sed-expr,$(PROTOBUF_GEN_DIR)) |
||||||
|
define get-sed-expr |
||||||
|
's,go_package = "github.com/open-telemetry/opentelemetry-proto/gen/go,go_package = "go.opentelemetry.io/otel/$(1),' |
||||||
|
endef |
||||||
|
|
||||||
|
.PHONY: protobuf |
||||||
|
protobuf: protobuf-source gen-protobuf copy-protobufs |
||||||
|
|
||||||
|
.PHONY: protobuf-source |
||||||
|
protobuf-source: $(SOURCE_PROTO_FILES) |
||||||
|
|
||||||
|
# This copies proto files from submodule into $(PROTO_SOURCE_DIR), |
||||||
|
# thus satisfying the $(SOURCE_PROTO_FILES) prerequisite. The copies |
||||||
|
# have their package name replaced by go.opentelemetry.io/otel. |
||||||
|
$(PROTO_SOURCE_DIR)/%.proto: $(OTEL_PROTO_SUBMODULE)/%.proto |
||||||
|
@ \ |
||||||
|
mkdir -p $(@D); \ |
||||||
|
sed -e $(call get-sed-expr,$(PROTOBUF_GEN_DIR)) "$<" >"$@.tmp"; \ |
||||||
|
mv "$@.tmp" "$@" |
||||||
|
|
||||||
|
.PHONY: gen-protobuf |
||||||
|
gen-protobuf: $(SOURCE_PROTO_FILES) $(TOOLS_DIR)/protoc-gen-gogofast $(TOOLS_DIR)/protoc/bin/protoc |
||||||
|
@ \ |
||||||
|
mkdir -p "$(PROTOBUF_TEMP_DIR)"; \ |
||||||
|
set -e; for f in $^; do \ |
||||||
|
if [[ "$${f}" == $(TOOLS_DIR)/* ]]; then continue; fi; \ |
||||||
|
echo "protoc $${f#"$(PROTO_SOURCE_DIR)/"}"; \ |
||||||
|
PATH="$(TOOLS_DIR):$${PATH}" $(TOOLS_DIR)/protoc/bin/protoc --proto_path="$(PROTO_SOURCE_DIR)" --gogofast_out="plugins=grpc:$(PROTOBUF_TEMP_DIR)" "$${f}"; \ |
||||||
|
done |
||||||
|
|
||||||
|
.PHONY: copy-protobufs |
||||||
|
copy-protobufs: |
||||||
|
@rsync -a $(PROTOBUF_TEMP_DIR)/go.opentelemetry.io/otel/exporters . |
||||||
|
|
||||||
|
.PHONY: clean |
||||||
|
clean: |
||||||
|
rm -rf $(GEN_TEMP_DIR) |
@ -0,0 +1,71 @@ |
|||||||
|
# OpenTelemetry-Go |
||||||
|
|
||||||
|
[![CI](https://github.com/open-telemetry/opentelemetry-go/workflows/ci/badge.svg)](https://github.com/open-telemetry/opentelemetry-go/actions?query=workflow%3Aci+branch%3Amaster) |
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel)](https://pkg.go.dev/go.opentelemetry.io/otel) |
||||||
|
[![Go Report Card](https://goreportcard.com/badge/go.opentelemetry.io/otel)](https://goreportcard.com/report/go.opentelemetry.io/otel) |
||||||
|
[![Gitter](https://badges.gitter.im/open-telemetry/opentelemetry-go.svg)](https://gitter.im/open-telemetry/opentelemetry-go?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
||||||
|
|
||||||
|
The Go [OpenTelemetry](https://opentelemetry.io/) implementation. |
||||||
|
|
||||||
|
## Project Status |
||||||
|
|
||||||
|
**Warning**: this project is currently in a pre-GA phase. Backwards |
||||||
|
incompatible changes may be introduced in subsequent minor version releases as |
||||||
|
we work to track the evolving OpenTelemetry specification and user feedback. |
||||||
|
|
||||||
|
Our progress towards a GA release candidate is tracked in [this project |
||||||
|
board](https://github.com/orgs/open-telemetry/projects/5). This release |
||||||
|
candidate will follow semantic versioning and will be released with a major |
||||||
|
version greater than zero. |
||||||
|
|
||||||
|
Progress and status specific to this repository is tracked in our local |
||||||
|
[project boards](https://github.com/open-telemetry/opentelemetry-go/projects) |
||||||
|
and |
||||||
|
[milestones](https://github.com/open-telemetry/opentelemetry-go/milestones). |
||||||
|
|
||||||
|
Project versioning information and stability guarantees can be found in the |
||||||
|
[versioning documentation](./VERSIONING.md). |
||||||
|
|
||||||
|
## Getting Started |
||||||
|
|
||||||
|
You can find a getting started guide on [opentelemetry.io](https://opentelemetry.io/docs/go/getting-started/). |
||||||
|
|
||||||
|
OpenTelemetry's goal is to provide a single set of APIs to capture distributed |
||||||
|
traces and metrics from your application and send them to an observability |
||||||
|
platform. This project allows you to do just that for applications written in |
||||||
|
Go. There are two steps to this process: instrument your application, and |
||||||
|
configure an exporter. |
||||||
|
|
||||||
|
### Instrumentation |
||||||
|
|
||||||
|
To start capturing distributed traces and metric events from your application |
||||||
|
it first needs to be instrumented. The easiest way to do this is by using an |
||||||
|
instrumentation library for your code. Be sure to check out [the officially |
||||||
|
supported instrumentation |
||||||
|
libraries](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/master/instrumentation). |
||||||
|
|
||||||
|
If you need to extend the telemetry an instrumentation library provides or want |
||||||
|
to build your own instrumentation for your application directly you will need |
||||||
|
to use the |
||||||
|
[go.opentelemetry.io/otel/api](https://pkg.go.dev/go.opentelemetry.io/otel/api) |
||||||
|
package. The included [examples](./example/) are a good way to see some |
||||||
|
practical uses of this process. |
||||||
|
|
||||||
|
### Export |
||||||
|
|
||||||
|
Now that your application is instrumented to collect telemetry, it needs an |
||||||
|
export pipeline to send that telemetry to an observability platform. |
||||||
|
|
||||||
|
You can find officially supported exporters [here](./exporters/) and in the |
||||||
|
companion [contrib |
||||||
|
repository](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/master/exporters/metric). |
||||||
|
Additionally, there are many vendor specific or 3rd party exporters for |
||||||
|
OpenTelemetry. These exporters are broken down by |
||||||
|
[trace](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/export/trace?tab=importedby) |
||||||
|
and |
||||||
|
[metric](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/export/metric?tab=importedby) |
||||||
|
support. |
||||||
|
|
||||||
|
## Contributing |
||||||
|
|
||||||
|
See the [contributing documentation](CONTRIBUTING.md). |
@ -0,0 +1,81 @@ |
|||||||
|
# Release Process |
||||||
|
|
||||||
|
## Pre-Release |
||||||
|
|
||||||
|
Update go.mod for submodules to depend on the new release which will happen in the next step. |
||||||
|
|
||||||
|
1. Run the pre-release script. It creates a branch `pre_release_<new tag>` that will contain all release changes. |
||||||
|
|
||||||
|
``` |
||||||
|
./pre_release.sh -t <new tag> |
||||||
|
``` |
||||||
|
|
||||||
|
2. Verify the changes. |
||||||
|
|
||||||
|
``` |
||||||
|
git diff master |
||||||
|
``` |
||||||
|
|
||||||
|
This should have changed the version for all modules to be `<new tag>`. |
||||||
|
|
||||||
|
3. Update the [Changelog](./CHANGELOG.md). |
||||||
|
- Make sure all relevant changes for this release are included and are in language that non-contributors to the project can understand. |
||||||
|
To verify this, you can look directly at the commits since the `<last tag>`. |
||||||
|
|
||||||
|
``` |
||||||
|
git --no-pager log --pretty=oneline "<last tag>..HEAD" |
||||||
|
``` |
||||||
|
|
||||||
|
- Move all the `Unreleased` changes into a new section following the title scheme (`[<new tag>] - <date of release>`). |
||||||
|
- Update all the appropriate links at the bottom. |
||||||
|
|
||||||
|
4. Push the changes to upstream and create a Pull Request on GitHub. |
||||||
|
Be sure to include the curated changes from the [Changelog](./CHANGELOG.md) in the description. |
||||||
|
|
||||||
|
|
||||||
|
## Tag |
||||||
|
|
||||||
|
Once the Pull Request with all the version changes has been approved and merged it is time to tag the merged commit. |
||||||
|
|
||||||
|
***IMPORTANT***: It is critical you use the same tag that you used in the Pre-Release step! |
||||||
|
Failure to do so will leave things in a broken state. |
||||||
|
|
||||||
|
***IMPORTANT***: [There is currently no way to remove an incorrectly tagged version of a Go module](https://github.com/golang/go/issues/34189). |
||||||
|
It is critical you make sure the version you push upstream is correct. |
||||||
|
[Failure to do so will lead to minor emergencies and tough to work around](https://github.com/open-telemetry/opentelemetry-go/issues/331). |
||||||
|
|
||||||
|
1. Run the tag.sh script using the `<commit-hash>` of the commit on the master branch for the merged Pull Request. |
||||||
|
|
||||||
|
``` |
||||||
|
./tag.sh <new tag> <commit-hash> |
||||||
|
``` |
||||||
|
|
||||||
|
2. Push tags to the upstream remote (not your fork: `github.com/open-telemetry/opentelemetry-go.git`). |
||||||
|
Make sure you push all sub-modules as well. |
||||||
|
|
||||||
|
``` |
||||||
|
git push upstream <new tag> |
||||||
|
git push upstream <submodules-path/new tag> |
||||||
|
... |
||||||
|
``` |
||||||
|
|
||||||
|
## Release |
||||||
|
|
||||||
|
Finally create a Release for the new `<new tag>` on GitHub. |
||||||
|
The release body should include all the release notes from the Changelog for this release. |
||||||
|
Additionally, the `tag.sh` script generates commit logs since last release which can be used to supplement the release notes. |
||||||
|
|
||||||
|
## Verify Examples |
||||||
|
|
||||||
|
After releasing verify that examples build outside of the repository. |
||||||
|
|
||||||
|
``` |
||||||
|
./verify_examples.sh |
||||||
|
``` |
||||||
|
|
||||||
|
The script copies examples into a different directory removes any `replace` declarations in `go.mod` and builds them. |
||||||
|
This ensures they build with the published release, not the local copy. |
||||||
|
|
||||||
|
## Contrib Repository |
||||||
|
|
||||||
|
Once verified be sure to [make a release for the `contrib` repository](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/master/RELEASING.md) that uses this release. |
@ -0,0 +1,217 @@ |
|||||||
|
# Versioning |
||||||
|
|
||||||
|
This document describes the versioning policy for this repository. This policy |
||||||
|
is designed so the following goals can be achieved. |
||||||
|
|
||||||
|
**Users are provided a codebase of value that is stable and secure.** |
||||||
|
|
||||||
|
## Policy |
||||||
|
|
||||||
|
* Versioning of this project will be idiomatic of a Go project using [Go |
||||||
|
modules](https://github.com/golang/go/wiki/Modules). |
||||||
|
* [Semantic import |
||||||
|
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning) |
||||||
|
will be used. |
||||||
|
* Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html). |
||||||
|
* If a module is version `v2` or higher, the major version of the module |
||||||
|
must be included as a `/vN` at the end of the module paths used in |
||||||
|
`go.mod` files (e.g., `module go.opentelemetry.io/otel/v2`, `require |
||||||
|
go.opentelemetry.io/otel/v2 v2.0.1`) and in the package import path |
||||||
|
(e.g., `import "go.opentelemetry.io/otel/v2/trace"`). This includes the |
||||||
|
paths used in `go get` commands (e.g., `go get |
||||||
|
go.opentelemetry.io/otel/v2@v2.0.1`. Note there is both a `/v2` and a |
||||||
|
`@v2.0.1` in that example. One way to think about it is that the module |
||||||
|
name now includes the `/v2`, so include `/v2` whenever you are using the |
||||||
|
module name). |
||||||
|
* If a module is version `v0` or `v1`, do not include the major version in |
||||||
|
either the module path or the import path. |
||||||
|
* Modules will be used to encapsulate signals and components. |
||||||
|
* Experimental modules still under active development will be versioned at |
||||||
|
`v0` to imply the stability guarantee defined by |
||||||
|
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4). |
||||||
|
|
||||||
|
> Major version zero (0.y.z) is for initial development. Anything MAY |
||||||
|
> change at any time. The public API SHOULD NOT be considered stable. |
||||||
|
|
||||||
|
* Mature modules for which we guarantee a stable public API will be versioned |
||||||
|
with a major version greater than `v0`. |
||||||
|
* The decision to make a module stable will be made on a case-by-case |
||||||
|
basis by the maintainers of this project. |
||||||
|
* Experimental modules will start their versioning at `v0.0.0` and will |
||||||
|
increment their minor version when backwards incompatible changes are |
||||||
|
released and increment their patch version when backwards compatible |
||||||
|
changes are released. |
||||||
|
* All stable modules that use the same major version number will use the |
||||||
|
same entire version number. |
||||||
|
* Stable modules may be released with an incremented minor or patch |
||||||
|
version even though that module has not been changed, but rather so |
||||||
|
that it will remain at the same version as other stable modules that |
||||||
|
did undergo change. |
||||||
|
* When an experimental module becomes stable a new stable module version |
||||||
|
will be released and will include this now stable module. The new |
||||||
|
stable module version will be an increment of the minor version number |
||||||
|
and will be applied to all existing stable modules as well as the newly |
||||||
|
stable module being released. |
||||||
|
* Versioning of the associated [contrib |
||||||
|
repository](https://github.com/open-telemetry/opentelemetry-go-contrib) of |
||||||
|
this project will be idiomatic of a Go project using [Go |
||||||
|
modules](https://github.com/golang/go/wiki/Modules). |
||||||
|
* [Semantic import |
||||||
|
versioning](https://github.com/golang/go/wiki/Modules#semantic-import-versioning) |
||||||
|
will be used. |
||||||
|
* Versions will comply with [semver 2.0](https://semver.org/spec/v2.0.0.html). |
||||||
|
* If a module is version `v2` or higher, the |
||||||
|
major version of the module must be included as a `/vN` at the end of the |
||||||
|
module paths used in `go.mod` files (e.g., `module |
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2`, `require |
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2 v2.0.1`) and in the |
||||||
|
package import path (e.g., `import |
||||||
|
"go.opentelemetry.io/contrib/instrumentation/host/v2"`). This includes |
||||||
|
the paths used in `go get` commands (e.g., `go get |
||||||
|
go.opentelemetry.io/contrib/instrumentation/host/v2@v2.0.1`. Note there |
||||||
|
is both a `/v2` and a `@v2.0.1` in that example. One way to think about |
||||||
|
it is that the module name now includes the `/v2`, so include `/v2` |
||||||
|
whenever you are using the module name). |
||||||
|
* If a module is version `v0` or `v1`, do not include the major version |
||||||
|
in either the module path or the import path. |
||||||
|
* In addition to public APIs, telemetry produced by stable instrumentation |
||||||
|
will remain stable and backwards compatible. This is to avoid breaking |
||||||
|
alerts and dashboard. |
||||||
|
* Modules will be used to encapsulate instrumentation, detectors, exporters, |
||||||
|
propagators, and any other independent sets of related components. |
||||||
|
* Experimental modules still under active development will be versioned at |
||||||
|
`v0` to imply the stability guarantee defined by |
||||||
|
[semver](https://semver.org/spec/v2.0.0.html#spec-item-4). |
||||||
|
|
||||||
|
> Major version zero (0.y.z) is for initial development. Anything MAY |
||||||
|
> change at any time. The public API SHOULD NOT be considered stable. |
||||||
|
|
||||||
|
* Mature modules for which we guarantee a stable public API and telemetry will |
||||||
|
be versioned with a major version greater than `v0`. |
||||||
|
* Experimental modules will start their versioning at `v0.0.0` and will |
||||||
|
increment their minor version when backwards incompatible changes are |
||||||
|
released and increment their patch version when backwards compatible |
||||||
|
changes are released. |
||||||
|
* Stable contrib modules cannot depend on experimental modules from this |
||||||
|
project. |
||||||
|
* All stable contrib modules of the same major version with this project |
||||||
|
will use the same entire version as this project. |
||||||
|
* Stable modules may be released with an incremented minor or patch |
||||||
|
version even though that module's code has not been changed. Instead |
||||||
|
the only change that will have been included is to have updated that |
||||||
|
modules dependency on this project's stable APIs. |
||||||
|
* When an experimental module in contrib becomes stable a new stable |
||||||
|
module version will be released and will include this now stable |
||||||
|
module. The new stable module version will be an increment of the minor |
||||||
|
version number and will be applied to all existing stable contrib |
||||||
|
modules, this project's modules, and the newly stable module being |
||||||
|
released. |
||||||
|
* Contrib modules will be kept up to date with this project's releases. |
||||||
|
* Due to the dependency contrib modules will implicitly have on this |
||||||
|
project's modules the release of stable contrib modules to match the |
||||||
|
released version number will be staggered after this project's release. |
||||||
|
There is no explicit time guarantee for how long after this projects |
||||||
|
release the contrib release will be. Effort should be made to keep them |
||||||
|
as close in time as possible. |
||||||
|
* No additional stable release in this project can be made until the |
||||||
|
contrib repository has a matching stable release. |
||||||
|
* No release can be made in the contrib repository after this project's |
||||||
|
stable release except for a stable release of the contrib repository. |
||||||
|
* GitHub releases will be made for all releases. |
||||||
|
* Go modules will be made available at Go package mirrors. |
||||||
|
|
||||||
|
## Example Versioning Lifecycle |
||||||
|
|
||||||
|
To better understand the implementation of the above policy the following |
||||||
|
example is provided. This project is simplified to include only the following |
||||||
|
modules and their versions: |
||||||
|
|
||||||
|
* `otel`: `v0.14.0` |
||||||
|
* `otel/trace`: `v0.14.0` |
||||||
|
* `otel/metric`: `v0.14.0` |
||||||
|
* `otel/baggage`: `v0.14.0` |
||||||
|
* `otel/sdk/trace`: `v0.14.0` |
||||||
|
* `otel/sdk/metric`: `v0.14.0` |
||||||
|
|
||||||
|
These modules have been developed to a point where the `otel/trace`, |
||||||
|
`otel/baggage`, and `otel/sdk/trace` modules have reached a point that they |
||||||
|
should be considered for a stable release. The `otel/metric` and |
||||||
|
`otel/sdk/metric` are still under active development and the `otel` module |
||||||
|
depends on both `otel/trace` and `otel/metric`. |
||||||
|
|
||||||
|
The `otel` package is refactored to remove its dependencies on `otel/metric` so |
||||||
|
it can be released as stable as well. With that done the following release |
||||||
|
candidates are made: |
||||||
|
|
||||||
|
* `otel`: `v1.0.0-rc.1` |
||||||
|
* `otel/trace`: `v1.0.0-rc.1` |
||||||
|
* `otel/baggage`: `v1.0.0-rc.1` |
||||||
|
* `otel/sdk/trace`: `v1.0.0-rc.1` |
||||||
|
|
||||||
|
The `otel/metric` and `otel/sdk/metric` modules remain at `v0.14.0`. |
||||||
|
|
||||||
|
A few minor issues are discovered in the `otel/trace` package. These issues are |
||||||
|
resolved with some minor, but backwards incompatible, changes and are released |
||||||
|
as a second release candidate: |
||||||
|
|
||||||
|
* `otel`: `v1.0.0-rc.2` |
||||||
|
* `otel/trace`: `v1.0.0-rc.2` |
||||||
|
* `otel/baggage`: `v1.0.0-rc.2` |
||||||
|
* `otel/sdk/trace`: `v1.0.0-rc.2` |
||||||
|
|
||||||
|
Notice that all module version numbers are incremented to adhere to our |
||||||
|
versioning policy. |
||||||
|
|
||||||
|
After these release candidates have been evaluated to satisfaction, they are |
||||||
|
released as version `v1.0.0`. |
||||||
|
|
||||||
|
* `otel`: `v1.0.0` |
||||||
|
* `otel/trace`: `v1.0.0` |
||||||
|
* `otel/baggage`: `v1.0.0` |
||||||
|
* `otel/sdk/trace`: `v1.0.0` |
||||||
|
|
||||||
|
Since both the `go` utility and the Go module system support [the semantic |
||||||
|
versioning definition of |
||||||
|
precedence](https://semver.org/spec/v2.0.0.html#spec-item-11), this release |
||||||
|
will correctly be interpreted as the successor to the previous release |
||||||
|
candidates. |
||||||
|
|
||||||
|
Active development of this project continues. The `otel/metric` module now has |
||||||
|
backwards incompatible changes to its API that need to be released and the |
||||||
|
`otel/baggage` module has a minor bug fix that needs to be released. The |
||||||
|
following release is made: |
||||||
|
|
||||||
|
* `otel`: `v1.0.1` |
||||||
|
* `otel/trace`: `v1.0.1` |
||||||
|
* `otel/metric`: `v0.15.0` |
||||||
|
* `otel/baggage`: `v1.0.1` |
||||||
|
* `otel/sdk/trace`: `v1.0.1` |
||||||
|
* `otel/sdk/metric`: `v0.15.0` |
||||||
|
|
||||||
|
Notice that, again, all stable module versions are incremented in unison and |
||||||
|
the `otel/sdk/metric` package, which depends on the `otel/metric` package, also |
||||||
|
bumped its version. This bump of the `otel/sdk/metric` package makes sense |
||||||
|
given their coupling, though it is not explicitly required by our versioning |
||||||
|
policy. |
||||||
|
|
||||||
|
As we progress, the `otel/metric` and `otel/sdk/metric` packages have reached a |
||||||
|
point where they should be evaluated for stability. The `otel` module is |
||||||
|
reintegrated with the `otel/metric` package and the following release is made: |
||||||
|
|
||||||
|
* `otel`: `v1.1.0-rc.1` |
||||||
|
* `otel/trace`: `v1.1.0-rc.1` |
||||||
|
* `otel/metric`: `v1.1.0-rc.1` |
||||||
|
* `otel/baggage`: `v1.1.0-rc.1` |
||||||
|
* `otel/sdk/trace`: `v1.1.0-rc.1` |
||||||
|
* `otel/sdk/metric`: `v1.1.0-rc.1` |
||||||
|
|
||||||
|
All the modules are evaluated and determined to a viable stable release. They |
||||||
|
are then released as version `v1.1.0` (the minor version is incremented to |
||||||
|
indicate the addition of new signal). |
||||||
|
|
||||||
|
* `otel`: `v1.1.0` |
||||||
|
* `otel/trace`: `v1.1.0` |
||||||
|
* `otel/metric`: `v1.1.0` |
||||||
|
* `otel/baggage`: `v1.1.0` |
||||||
|
* `otel/sdk/trace`: `v1.1.0` |
||||||
|
* `otel/sdk/metric`: `v1.1.0` |
@ -0,0 +1,106 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 codes // import "go.opentelemetry.io/otel/codes"
|
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// Unset is the default status code.
|
||||||
|
Unset Code = 0 |
||||||
|
// Error indicates the operation contains an error.
|
||||||
|
Error Code = 1 |
||||||
|
// Ok indicates operation has been validated by an Application developers
|
||||||
|
// or Operator to have completed successfully, or contain no error.
|
||||||
|
Ok Code = 2 |
||||||
|
|
||||||
|
maxCode = 3 |
||||||
|
) |
||||||
|
|
||||||
|
// Code is an 32-bit representation of a status state.
|
||||||
|
type Code uint32 |
||||||
|
|
||||||
|
var codeToStr = map[Code]string{ |
||||||
|
Unset: "Unset", |
||||||
|
Error: "Error", |
||||||
|
Ok: "Ok", |
||||||
|
} |
||||||
|
|
||||||
|
var strToCode = map[string]Code{ |
||||||
|
`"Unset"`: Unset, |
||||||
|
`"Error"`: Error, |
||||||
|
`"Ok"`: Ok, |
||||||
|
} |
||||||
|
|
||||||
|
// String returns the Code as a string.
|
||||||
|
func (c Code) String() string { |
||||||
|
return codeToStr[c] |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals b into the Code.
|
||||||
|
//
|
||||||
|
// This is based on the functionality in the gRPC codes package:
|
||||||
|
// https://github.com/grpc/grpc-go/blob/bb64fee312b46ebee26be43364a7a966033521b1/codes/codes.go#L218-L244
|
||||||
|
func (c *Code) UnmarshalJSON(b []byte) error { |
||||||
|
// From json.Unmarshaler: By convention, to approximate the behavior of
|
||||||
|
// Unmarshal itself, Unmarshalers implement UnmarshalJSON([]byte("null")) as
|
||||||
|
// a no-op.
|
||||||
|
if string(b) == "null" { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if c == nil { |
||||||
|
return fmt.Errorf("nil receiver passed to UnmarshalJSON") |
||||||
|
} |
||||||
|
|
||||||
|
var x interface{} |
||||||
|
if err := json.Unmarshal(b, &x); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
switch x.(type) { |
||||||
|
case string: |
||||||
|
if jc, ok := strToCode[string(b)]; ok { |
||||||
|
*c = jc |
||||||
|
return nil |
||||||
|
} |
||||||
|
return fmt.Errorf("invalid code: %q", string(b)) |
||||||
|
case float64: |
||||||
|
if ci, err := strconv.ParseUint(string(b), 10, 32); err == nil { |
||||||
|
if ci >= maxCode { |
||||||
|
return fmt.Errorf("invalid code: %q", ci) |
||||||
|
} |
||||||
|
|
||||||
|
*c = Code(ci) |
||||||
|
return nil |
||||||
|
} |
||||||
|
return fmt.Errorf("invalid code: %q", string(b)) |
||||||
|
default: |
||||||
|
return fmt.Errorf("invalid code: %q", string(b)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalJSON returns c as the JSON encoding of c.
|
||||||
|
func (c *Code) MarshalJSON() ([]byte, error) { |
||||||
|
if c == nil { |
||||||
|
return []byte("null"), nil |
||||||
|
} |
||||||
|
str, ok := codeToStr[*c] |
||||||
|
if !ok { |
||||||
|
return nil, fmt.Errorf("invalid code: %d", *c) |
||||||
|
} |
||||||
|
return []byte(fmt.Sprintf("%q", str)), nil |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 codes defines the canonical error codes used by OpenTelemetry. |
||||||
|
|
||||||
|
This package is currently in a pre-GA phase. Backwards incompatible changes |
||||||
|
may be introduced in subsequent minor version releases as we work to track |
||||||
|
the evolving OpenTelemetry specification and user feedback. |
||||||
|
|
||||||
|
It conforms to [the OpenTelemetry |
||||||
|
specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#statuscanonicalcode).
|
||||||
|
*/ |
||||||
|
package codes // import "go.opentelemetry.io/otel/codes"
|
@ -0,0 +1,38 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 otel provides global access to the OpenTelemetry API. The subpackages of |
||||||
|
the otel package provide an implementation of the OpenTelemetry API. |
||||||
|
|
||||||
|
This package is currently in a pre-GA phase. Backwards incompatible changes |
||||||
|
may be introduced in subsequent minor version releases as we work to track the |
||||||
|
evolving OpenTelemetry specification and user feedback. |
||||||
|
|
||||||
|
The provided API is used to instrument code and measure data about that code's |
||||||
|
performance and operation. The measured data, by default, is not processed or |
||||||
|
transmitted anywhere. An implementation of the OpenTelemetry SDK, like the |
||||||
|
default SDK implementation (go.opentelemetry.io/otel/sdk), and associated |
||||||
|
exporters are used to process and transport this data. |
||||||
|
|
||||||
|
To read the getting started guide, see https://opentelemetry.io/docs/go/getting-started/.
|
||||||
|
|
||||||
|
To read more about tracing, see go.opentelemetry.io/otel/trace. |
||||||
|
|
||||||
|
To read more about metrics, see go.opentelemetry.io/otel/metric. |
||||||
|
|
||||||
|
To read more about propagation, see go.opentelemetry.io/otel/propagation and |
||||||
|
go.opentelemetry.io/otel/baggage. |
||||||
|
*/ |
||||||
|
package otel // import "go.opentelemetry.io/otel"
|
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 otel // import "go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
// ErrorHandler handles irremediable events.
|
||||||
|
type ErrorHandler interface { |
||||||
|
// Handle handles any error deemed irremediable by an OpenTelemetry
|
||||||
|
// component.
|
||||||
|
Handle(error) |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
# Copyright The OpenTelemetry Authors |
||||||
|
# |
||||||
|
# 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. |
||||||
|
|
||||||
|
set -euo pipefail |
||||||
|
|
||||||
|
top_dir='.' |
||||||
|
if [[ $# -gt 0 ]]; then |
||||||
|
top_dir="${1}" |
||||||
|
fi |
||||||
|
|
||||||
|
p=$(pwd) |
||||||
|
mod_dirs=() |
||||||
|
|
||||||
|
# Note `mapfile` does not exist in older bash versions: |
||||||
|
# https://stackoverflow.com/questions/41475261/need-alternative-to-readarray-mapfile-for-script-on-older-version-of-bash |
||||||
|
|
||||||
|
while IFS= read -r line; do |
||||||
|
mod_dirs+=("$line") |
||||||
|
done < <(find "${top_dir}" -type f -name 'go.mod' -exec dirname {} \; | sort) |
||||||
|
|
||||||
|
for mod_dir in "${mod_dirs[@]}"; do |
||||||
|
cd "${mod_dir}" |
||||||
|
|
||||||
|
while IFS= read -r line; do |
||||||
|
echo ".${line#${p}}" |
||||||
|
done < <(go list --find -f '{{.Name}}|{{.Dir}}' ./... | grep '^main|' | cut -f 2- -d '|') |
||||||
|
cd "${p}" |
||||||
|
done |
@ -0,0 +1,8 @@ |
|||||||
|
module go.opentelemetry.io/otel |
||||||
|
|
||||||
|
go 1.14 |
||||||
|
|
||||||
|
require ( |
||||||
|
github.com/google/go-cmp v0.5.4 |
||||||
|
github.com/stretchr/testify v1.6.1 |
||||||
|
) |
@ -0,0 +1,15 @@ |
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= |
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= |
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= |
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
@ -0,0 +1,89 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 otel // import "go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"sync" |
||||||
|
"sync/atomic" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// globalErrorHandler provides an ErrorHandler that can be used
|
||||||
|
// throughout an OpenTelemetry instrumented project. When a user
|
||||||
|
// specified ErrorHandler is registered (`SetErrorHandler`) all calls to
|
||||||
|
// `Handle` and will be delegated to the registered ErrorHandler.
|
||||||
|
globalErrorHandler = &loggingErrorHandler{ |
||||||
|
l: log.New(os.Stderr, "", log.LstdFlags), |
||||||
|
} |
||||||
|
|
||||||
|
// delegateErrorHandlerOnce ensures that a user provided ErrorHandler is
|
||||||
|
// only ever registered once.
|
||||||
|
delegateErrorHandlerOnce sync.Once |
||||||
|
|
||||||
|
// Comiple time check that loggingErrorHandler implements ErrorHandler.
|
||||||
|
_ ErrorHandler = (*loggingErrorHandler)(nil) |
||||||
|
) |
||||||
|
|
||||||
|
// loggingErrorHandler logs all errors to STDERR.
|
||||||
|
type loggingErrorHandler struct { |
||||||
|
delegate atomic.Value |
||||||
|
|
||||||
|
l *log.Logger |
||||||
|
} |
||||||
|
|
||||||
|
// setDelegate sets the ErrorHandler delegate if one is not already set.
|
||||||
|
func (h *loggingErrorHandler) setDelegate(d ErrorHandler) { |
||||||
|
if h.delegate.Load() != nil { |
||||||
|
// Delegate already registered
|
||||||
|
return |
||||||
|
} |
||||||
|
h.delegate.Store(d) |
||||||
|
} |
||||||
|
|
||||||
|
// Handle implements ErrorHandler.
|
||||||
|
func (h *loggingErrorHandler) Handle(err error) { |
||||||
|
if d := h.delegate.Load(); d != nil { |
||||||
|
d.(ErrorHandler).Handle(err) |
||||||
|
return |
||||||
|
} |
||||||
|
h.l.Print(err) |
||||||
|
} |
||||||
|
|
||||||
|
// GetErrorHandler returns the global ErrorHandler instance. If no ErrorHandler
|
||||||
|
// instance has been set (`SetErrorHandler`), the default ErrorHandler which
|
||||||
|
// logs errors to STDERR is returned.
|
||||||
|
func GetErrorHandler() ErrorHandler { |
||||||
|
return globalErrorHandler |
||||||
|
} |
||||||
|
|
||||||
|
// SetErrorHandler sets the global ErrorHandler to be h.
|
||||||
|
func SetErrorHandler(h ErrorHandler) { |
||||||
|
delegateErrorHandlerOnce.Do(func() { |
||||||
|
current := GetErrorHandler() |
||||||
|
if current == h { |
||||||
|
return |
||||||
|
} |
||||||
|
if internalHandler, ok := current.(*loggingErrorHandler); ok { |
||||||
|
internalHandler.setDelegate(h) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// Handle is a convience function for ErrorHandler().Handle(err)
|
||||||
|
func Handle(err error) { |
||||||
|
GetErrorHandler().Handle(err) |
||||||
|
} |
@ -0,0 +1,338 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 baggage provides types and functions to manage W3C Baggage.
|
||||||
|
package baggage |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/label" |
||||||
|
) |
||||||
|
|
||||||
|
type rawMap map[label.Key]label.Value |
||||||
|
type keySet map[label.Key]struct{} |
||||||
|
|
||||||
|
// Map is an immutable storage for correlations.
|
||||||
|
type Map struct { |
||||||
|
m rawMap |
||||||
|
} |
||||||
|
|
||||||
|
// MapUpdate contains information about correlation changes to be
|
||||||
|
// made.
|
||||||
|
type MapUpdate struct { |
||||||
|
// DropSingleK contains a single key to be dropped from
|
||||||
|
// correlations. Use this to avoid an overhead of a slice
|
||||||
|
// allocation if there is only one key to drop.
|
||||||
|
DropSingleK label.Key |
||||||
|
// DropMultiK contains all the keys to be dropped from
|
||||||
|
// correlations.
|
||||||
|
DropMultiK []label.Key |
||||||
|
|
||||||
|
// SingleKV contains a single key-value pair to be added to
|
||||||
|
// correlations. Use this to avoid an overhead of a slice
|
||||||
|
// allocation if there is only one key-value pair to add.
|
||||||
|
SingleKV label.KeyValue |
||||||
|
// MultiKV contains all the key-value pairs to be added to
|
||||||
|
// correlations.
|
||||||
|
MultiKV []label.KeyValue |
||||||
|
} |
||||||
|
|
||||||
|
func newMap(raw rawMap) Map { |
||||||
|
return Map{ |
||||||
|
m: raw, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// NewEmptyMap creates an empty correlations map.
|
||||||
|
func NewEmptyMap() Map { |
||||||
|
return newMap(nil) |
||||||
|
} |
||||||
|
|
||||||
|
// NewMap creates a map with the contents of the update applied. In
|
||||||
|
// this function, having an update with DropSingleK or DropMultiK
|
||||||
|
// makes no sense - those fields are effectively ignored.
|
||||||
|
func NewMap(update MapUpdate) Map { |
||||||
|
return NewEmptyMap().Apply(update) |
||||||
|
} |
||||||
|
|
||||||
|
// Apply creates a copy of the map with the contents of the update
|
||||||
|
// applied. Apply will first drop the keys from DropSingleK and
|
||||||
|
// DropMultiK, then add key-value pairs from SingleKV and MultiKV.
|
||||||
|
func (m Map) Apply(update MapUpdate) Map { |
||||||
|
delSet, addSet := getModificationSets(update) |
||||||
|
mapSize := getNewMapSize(m.m, delSet, addSet) |
||||||
|
|
||||||
|
r := make(rawMap, mapSize) |
||||||
|
for k, v := range m.m { |
||||||
|
// do not copy items we want to drop
|
||||||
|
if _, ok := delSet[k]; ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
// do not copy items we would overwrite
|
||||||
|
if _, ok := addSet[k]; ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
r[k] = v |
||||||
|
} |
||||||
|
if update.SingleKV.Key.Defined() { |
||||||
|
r[update.SingleKV.Key] = update.SingleKV.Value |
||||||
|
} |
||||||
|
for _, kv := range update.MultiKV { |
||||||
|
r[kv.Key] = kv.Value |
||||||
|
} |
||||||
|
if len(r) == 0 { |
||||||
|
r = nil |
||||||
|
} |
||||||
|
return newMap(r) |
||||||
|
} |
||||||
|
|
||||||
|
func getModificationSets(update MapUpdate) (delSet, addSet keySet) { |
||||||
|
deletionsCount := len(update.DropMultiK) |
||||||
|
if update.DropSingleK.Defined() { |
||||||
|
deletionsCount++ |
||||||
|
} |
||||||
|
if deletionsCount > 0 { |
||||||
|
delSet = make(map[label.Key]struct{}, deletionsCount) |
||||||
|
for _, k := range update.DropMultiK { |
||||||
|
delSet[k] = struct{}{} |
||||||
|
} |
||||||
|
if update.DropSingleK.Defined() { |
||||||
|
delSet[update.DropSingleK] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
additionsCount := len(update.MultiKV) |
||||||
|
if update.SingleKV.Key.Defined() { |
||||||
|
additionsCount++ |
||||||
|
} |
||||||
|
if additionsCount > 0 { |
||||||
|
addSet = make(map[label.Key]struct{}, additionsCount) |
||||||
|
for _, k := range update.MultiKV { |
||||||
|
addSet[k.Key] = struct{}{} |
||||||
|
} |
||||||
|
if update.SingleKV.Key.Defined() { |
||||||
|
addSet[update.SingleKV.Key] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func getNewMapSize(m rawMap, delSet, addSet keySet) int { |
||||||
|
mapSizeDiff := 0 |
||||||
|
for k := range addSet { |
||||||
|
if _, ok := m[k]; !ok { |
||||||
|
mapSizeDiff++ |
||||||
|
} |
||||||
|
} |
||||||
|
for k := range delSet { |
||||||
|
if _, ok := m[k]; ok { |
||||||
|
if _, inAddSet := addSet[k]; !inAddSet { |
||||||
|
mapSizeDiff-- |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return len(m) + mapSizeDiff |
||||||
|
} |
||||||
|
|
||||||
|
// Value gets a value from correlations map and returns a boolean
|
||||||
|
// value indicating whether the key exist in the map.
|
||||||
|
func (m Map) Value(k label.Key) (label.Value, bool) { |
||||||
|
value, ok := m.m[k] |
||||||
|
return value, ok |
||||||
|
} |
||||||
|
|
||||||
|
// HasValue returns a boolean value indicating whether the key exist
|
||||||
|
// in the map.
|
||||||
|
func (m Map) HasValue(k label.Key) bool { |
||||||
|
_, has := m.Value(k) |
||||||
|
return has |
||||||
|
} |
||||||
|
|
||||||
|
// Len returns a length of the map.
|
||||||
|
func (m Map) Len() int { |
||||||
|
return len(m.m) |
||||||
|
} |
||||||
|
|
||||||
|
// Foreach calls a passed callback once on each key-value pair until
|
||||||
|
// all the key-value pairs of the map were iterated or the callback
|
||||||
|
// returns false, whichever happens first.
|
||||||
|
func (m Map) Foreach(f func(label.KeyValue) bool) { |
||||||
|
for k, v := range m.m { |
||||||
|
if !f(label.KeyValue{ |
||||||
|
Key: k, |
||||||
|
Value: v, |
||||||
|
}) { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type correlationsType struct{} |
||||||
|
|
||||||
|
// SetHookFunc describes a type of a callback that is called when
|
||||||
|
// storing baggage in the context.
|
||||||
|
type SetHookFunc func(context.Context) context.Context |
||||||
|
|
||||||
|
// GetHookFunc describes a type of a callback that is called when
|
||||||
|
// getting baggage from the context.
|
||||||
|
type GetHookFunc func(context.Context, Map) Map |
||||||
|
|
||||||
|
// value under this key is either of type Map or correlationsData
|
||||||
|
var correlationsKey = &correlationsType{} |
||||||
|
|
||||||
|
type correlationsData struct { |
||||||
|
m Map |
||||||
|
setHook SetHookFunc |
||||||
|
getHook GetHookFunc |
||||||
|
} |
||||||
|
|
||||||
|
func (d correlationsData) isHookless() bool { |
||||||
|
return d.setHook == nil && d.getHook == nil |
||||||
|
} |
||||||
|
|
||||||
|
type hookKind int |
||||||
|
|
||||||
|
const ( |
||||||
|
hookKindSet hookKind = iota |
||||||
|
hookKindGet |
||||||
|
) |
||||||
|
|
||||||
|
func (d *correlationsData) overrideHook(kind hookKind, setHook SetHookFunc, getHook GetHookFunc) { |
||||||
|
switch kind { |
||||||
|
case hookKindSet: |
||||||
|
d.setHook = setHook |
||||||
|
case hookKindGet: |
||||||
|
d.getHook = getHook |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ContextWithSetHook installs a hook function that will be invoked
|
||||||
|
// every time ContextWithMap is called. To avoid unnecessary callback
|
||||||
|
// invocations (recursive or not), the callback can temporarily clear
|
||||||
|
// the hooks from the context with the ContextWithNoHooks function.
|
||||||
|
//
|
||||||
|
// Note that NewContext also calls ContextWithMap, so the hook will be
|
||||||
|
// invoked.
|
||||||
|
//
|
||||||
|
// Passing nil SetHookFunc creates a context with no set hook to call.
|
||||||
|
//
|
||||||
|
// This function should not be used by applications or libraries. It
|
||||||
|
// is mostly for interoperation with other observability APIs.
|
||||||
|
func ContextWithSetHook(ctx context.Context, hook SetHookFunc) context.Context { |
||||||
|
return contextWithHook(ctx, hookKindSet, hook, nil) |
||||||
|
} |
||||||
|
|
||||||
|
// ContextWithGetHook installs a hook function that will be invoked
|
||||||
|
// every time MapFromContext is called. To avoid unnecessary callback
|
||||||
|
// invocations (recursive or not), the callback can temporarily clear
|
||||||
|
// the hooks from the context with the ContextWithNoHooks function.
|
||||||
|
//
|
||||||
|
// Note that NewContext also calls MapFromContext, so the hook will be
|
||||||
|
// invoked.
|
||||||
|
//
|
||||||
|
// Passing nil GetHookFunc creates a context with no get hook to call.
|
||||||
|
//
|
||||||
|
// This function should not be used by applications or libraries. It
|
||||||
|
// is mostly for interoperation with other observability APIs.
|
||||||
|
func ContextWithGetHook(ctx context.Context, hook GetHookFunc) context.Context { |
||||||
|
return contextWithHook(ctx, hookKindGet, nil, hook) |
||||||
|
} |
||||||
|
|
||||||
|
func contextWithHook(ctx context.Context, kind hookKind, setHook SetHookFunc, getHook GetHookFunc) context.Context { |
||||||
|
switch v := ctx.Value(correlationsKey).(type) { |
||||||
|
case correlationsData: |
||||||
|
v.overrideHook(kind, setHook, getHook) |
||||||
|
if v.isHookless() { |
||||||
|
return context.WithValue(ctx, correlationsKey, v.m) |
||||||
|
} |
||||||
|
return context.WithValue(ctx, correlationsKey, v) |
||||||
|
case Map: |
||||||
|
return contextWithOneHookAndMap(ctx, kind, setHook, getHook, v) |
||||||
|
default: |
||||||
|
m := NewEmptyMap() |
||||||
|
return contextWithOneHookAndMap(ctx, kind, setHook, getHook, m) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func contextWithOneHookAndMap(ctx context.Context, kind hookKind, setHook SetHookFunc, getHook GetHookFunc, m Map) context.Context { |
||||||
|
d := correlationsData{m: m} |
||||||
|
d.overrideHook(kind, setHook, getHook) |
||||||
|
if d.isHookless() { |
||||||
|
return ctx |
||||||
|
} |
||||||
|
return context.WithValue(ctx, correlationsKey, d) |
||||||
|
} |
||||||
|
|
||||||
|
// ContextWithNoHooks creates a context with all the hooks
|
||||||
|
// disabled. Also returns old set and get hooks. This function can be
|
||||||
|
// used to temporarily clear the context from hooks and then reinstate
|
||||||
|
// them by calling ContextWithSetHook and ContextWithGetHook functions
|
||||||
|
// passing the hooks returned by this function.
|
||||||
|
//
|
||||||
|
// This function should not be used by applications or libraries. It
|
||||||
|
// is mostly for interoperation with other observability APIs.
|
||||||
|
func ContextWithNoHooks(ctx context.Context) (context.Context, SetHookFunc, GetHookFunc) { |
||||||
|
switch v := ctx.Value(correlationsKey).(type) { |
||||||
|
case correlationsData: |
||||||
|
return context.WithValue(ctx, correlationsKey, v.m), v.setHook, v.getHook |
||||||
|
default: |
||||||
|
return ctx, nil, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ContextWithMap returns a context with the Map entered into it.
|
||||||
|
func ContextWithMap(ctx context.Context, m Map) context.Context { |
||||||
|
switch v := ctx.Value(correlationsKey).(type) { |
||||||
|
case correlationsData: |
||||||
|
v.m = m |
||||||
|
ctx = context.WithValue(ctx, correlationsKey, v) |
||||||
|
if v.setHook != nil { |
||||||
|
ctx = v.setHook(ctx) |
||||||
|
} |
||||||
|
return ctx |
||||||
|
default: |
||||||
|
return context.WithValue(ctx, correlationsKey, m) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ContextWithNoCorrelationData returns a context stripped of correlation
|
||||||
|
// data.
|
||||||
|
func ContextWithNoCorrelationData(ctx context.Context) context.Context { |
||||||
|
return context.WithValue(ctx, correlationsKey, nil) |
||||||
|
} |
||||||
|
|
||||||
|
// NewContext returns a context with the map from passed context
|
||||||
|
// updated with the passed key-value pairs.
|
||||||
|
func NewContext(ctx context.Context, keyvalues ...label.KeyValue) context.Context { |
||||||
|
return ContextWithMap(ctx, MapFromContext(ctx).Apply(MapUpdate{ |
||||||
|
MultiKV: keyvalues, |
||||||
|
})) |
||||||
|
} |
||||||
|
|
||||||
|
// MapFromContext gets the current Map from a Context.
|
||||||
|
func MapFromContext(ctx context.Context) Map { |
||||||
|
switch v := ctx.Value(correlationsKey).(type) { |
||||||
|
case correlationsData: |
||||||
|
if v.getHook != nil { |
||||||
|
return v.getHook(ctx, v.m) |
||||||
|
} |
||||||
|
return v.m |
||||||
|
case Map: |
||||||
|
return v |
||||||
|
default: |
||||||
|
return NewEmptyMap() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,348 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 global |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"sync" |
||||||
|
"sync/atomic" |
||||||
|
"unsafe" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/label" |
||||||
|
"go.opentelemetry.io/otel/metric" |
||||||
|
"go.opentelemetry.io/otel/metric/number" |
||||||
|
"go.opentelemetry.io/otel/metric/registry" |
||||||
|
) |
||||||
|
|
||||||
|
// This file contains the forwarding implementation of MeterProvider used as
|
||||||
|
// the default global instance. Metric events using instruments provided by
|
||||||
|
// this implementation are no-ops until the first Meter implementation is set
|
||||||
|
// as the global provider.
|
||||||
|
//
|
||||||
|
// The implementation here uses Mutexes to maintain a list of active Meters in
|
||||||
|
// the MeterProvider and Instruments in each Meter, under the assumption that
|
||||||
|
// these interfaces are not performance-critical.
|
||||||
|
//
|
||||||
|
// We have the invariant that setDelegate() will be called before a new
|
||||||
|
// MeterProvider implementation is registered as the global provider. Mutexes
|
||||||
|
// in the MeterProvider and Meters ensure that each instrument has a delegate
|
||||||
|
// before the global provider is set.
|
||||||
|
//
|
||||||
|
// Bound instrument operations are implemented by delegating to the
|
||||||
|
// instrument after it is registered, with a sync.Once initializer to
|
||||||
|
// protect against races with Release().
|
||||||
|
//
|
||||||
|
// Metric uniqueness checking is implemented by calling the exported
|
||||||
|
// methods of the api/metric/registry package.
|
||||||
|
|
||||||
|
type meterKey struct { |
||||||
|
Name, Version string |
||||||
|
} |
||||||
|
|
||||||
|
type meterProvider struct { |
||||||
|
delegate metric.MeterProvider |
||||||
|
|
||||||
|
// lock protects `delegate` and `meters`.
|
||||||
|
lock sync.Mutex |
||||||
|
|
||||||
|
// meters maintains a unique entry for every named Meter
|
||||||
|
// that has been registered through the global instance.
|
||||||
|
meters map[meterKey]*meterEntry |
||||||
|
} |
||||||
|
|
||||||
|
type meterImpl struct { |
||||||
|
delegate unsafe.Pointer // (*metric.MeterImpl)
|
||||||
|
|
||||||
|
lock sync.Mutex |
||||||
|
syncInsts []*syncImpl |
||||||
|
asyncInsts []*asyncImpl |
||||||
|
} |
||||||
|
|
||||||
|
type meterEntry struct { |
||||||
|
unique metric.MeterImpl |
||||||
|
impl meterImpl |
||||||
|
} |
||||||
|
|
||||||
|
type instrument struct { |
||||||
|
descriptor metric.Descriptor |
||||||
|
} |
||||||
|
|
||||||
|
type syncImpl struct { |
||||||
|
delegate unsafe.Pointer // (*metric.SyncImpl)
|
||||||
|
|
||||||
|
instrument |
||||||
|
} |
||||||
|
|
||||||
|
type asyncImpl struct { |
||||||
|
delegate unsafe.Pointer // (*metric.AsyncImpl)
|
||||||
|
|
||||||
|
instrument |
||||||
|
|
||||||
|
runner metric.AsyncRunner |
||||||
|
} |
||||||
|
|
||||||
|
// SyncImpler is implemented by all of the sync metric
|
||||||
|
// instruments.
|
||||||
|
type SyncImpler interface { |
||||||
|
SyncImpl() metric.SyncImpl |
||||||
|
} |
||||||
|
|
||||||
|
// AsyncImpler is implemented by all of the async
|
||||||
|
// metric instruments.
|
||||||
|
type AsyncImpler interface { |
||||||
|
AsyncImpl() metric.AsyncImpl |
||||||
|
} |
||||||
|
|
||||||
|
type syncHandle struct { |
||||||
|
delegate unsafe.Pointer // (*metric.BoundInstrumentImpl)
|
||||||
|
|
||||||
|
inst *syncImpl |
||||||
|
labels []label.KeyValue |
||||||
|
|
||||||
|
initialize sync.Once |
||||||
|
} |
||||||
|
|
||||||
|
var _ metric.MeterProvider = &meterProvider{} |
||||||
|
var _ metric.MeterImpl = &meterImpl{} |
||||||
|
var _ metric.InstrumentImpl = &syncImpl{} |
||||||
|
var _ metric.BoundSyncImpl = &syncHandle{} |
||||||
|
var _ metric.AsyncImpl = &asyncImpl{} |
||||||
|
|
||||||
|
func (inst *instrument) Descriptor() metric.Descriptor { |
||||||
|
return inst.descriptor |
||||||
|
} |
||||||
|
|
||||||
|
// MeterProvider interface and delegation
|
||||||
|
|
||||||
|
func newMeterProvider() *meterProvider { |
||||||
|
return &meterProvider{ |
||||||
|
meters: map[meterKey]*meterEntry{}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *meterProvider) setDelegate(provider metric.MeterProvider) { |
||||||
|
p.lock.Lock() |
||||||
|
defer p.lock.Unlock() |
||||||
|
|
||||||
|
p.delegate = provider |
||||||
|
for key, entry := range p.meters { |
||||||
|
entry.impl.setDelegate(key.Name, key.Version, provider) |
||||||
|
} |
||||||
|
p.meters = nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *meterProvider) Meter(instrumentationName string, opts ...metric.MeterOption) metric.Meter { |
||||||
|
p.lock.Lock() |
||||||
|
defer p.lock.Unlock() |
||||||
|
|
||||||
|
if p.delegate != nil { |
||||||
|
return p.delegate.Meter(instrumentationName, opts...) |
||||||
|
} |
||||||
|
|
||||||
|
key := meterKey{ |
||||||
|
Name: instrumentationName, |
||||||
|
Version: metric.NewMeterConfig(opts...).InstrumentationVersion, |
||||||
|
} |
||||||
|
entry, ok := p.meters[key] |
||||||
|
if !ok { |
||||||
|
entry = &meterEntry{} |
||||||
|
entry.unique = registry.NewUniqueInstrumentMeterImpl(&entry.impl) |
||||||
|
p.meters[key] = entry |
||||||
|
|
||||||
|
} |
||||||
|
return metric.WrapMeterImpl(entry.unique, key.Name, metric.WithInstrumentationVersion(key.Version)) |
||||||
|
} |
||||||
|
|
||||||
|
// Meter interface and delegation
|
||||||
|
|
||||||
|
func (m *meterImpl) setDelegate(name, version string, provider metric.MeterProvider) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
d := new(metric.MeterImpl) |
||||||
|
*d = provider.Meter(name, metric.WithInstrumentationVersion(version)).MeterImpl() |
||||||
|
m.delegate = unsafe.Pointer(d) |
||||||
|
|
||||||
|
for _, inst := range m.syncInsts { |
||||||
|
inst.setDelegate(*d) |
||||||
|
} |
||||||
|
m.syncInsts = nil |
||||||
|
for _, obs := range m.asyncInsts { |
||||||
|
obs.setDelegate(*d) |
||||||
|
} |
||||||
|
m.asyncInsts = nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *meterImpl) NewSyncInstrument(desc metric.Descriptor) (metric.SyncImpl, error) { |
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
if meterPtr := (*metric.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil { |
||||||
|
return (*meterPtr).NewSyncInstrument(desc) |
||||||
|
} |
||||||
|
|
||||||
|
inst := &syncImpl{ |
||||||
|
instrument: instrument{ |
||||||
|
descriptor: desc, |
||||||
|
}, |
||||||
|
} |
||||||
|
m.syncInsts = append(m.syncInsts, inst) |
||||||
|
return inst, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Synchronous delegation
|
||||||
|
|
||||||
|
func (inst *syncImpl) setDelegate(d metric.MeterImpl) { |
||||||
|
implPtr := new(metric.SyncImpl) |
||||||
|
|
||||||
|
var err error |
||||||
|
*implPtr, err = d.NewSyncInstrument(inst.descriptor) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
// TODO: There is no standard way to deliver this error to the user.
|
||||||
|
// See https://github.com/open-telemetry/opentelemetry-go/issues/514
|
||||||
|
// Note that the default SDK will not generate any errors yet, this is
|
||||||
|
// only for added safety.
|
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
atomic.StorePointer(&inst.delegate, unsafe.Pointer(implPtr)) |
||||||
|
} |
||||||
|
|
||||||
|
func (inst *syncImpl) Implementation() interface{} { |
||||||
|
if implPtr := (*metric.SyncImpl)(atomic.LoadPointer(&inst.delegate)); implPtr != nil { |
||||||
|
return (*implPtr).Implementation() |
||||||
|
} |
||||||
|
return inst |
||||||
|
} |
||||||
|
|
||||||
|
func (inst *syncImpl) Bind(labels []label.KeyValue) metric.BoundSyncImpl { |
||||||
|
if implPtr := (*metric.SyncImpl)(atomic.LoadPointer(&inst.delegate)); implPtr != nil { |
||||||
|
return (*implPtr).Bind(labels) |
||||||
|
} |
||||||
|
return &syncHandle{ |
||||||
|
inst: inst, |
||||||
|
labels: labels, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (bound *syncHandle) Unbind() { |
||||||
|
bound.initialize.Do(func() {}) |
||||||
|
|
||||||
|
implPtr := (*metric.BoundSyncImpl)(atomic.LoadPointer(&bound.delegate)) |
||||||
|
|
||||||
|
if implPtr == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
(*implPtr).Unbind() |
||||||
|
} |
||||||
|
|
||||||
|
// Async delegation
|
||||||
|
|
||||||
|
func (m *meterImpl) NewAsyncInstrument( |
||||||
|
desc metric.Descriptor, |
||||||
|
runner metric.AsyncRunner, |
||||||
|
) (metric.AsyncImpl, error) { |
||||||
|
|
||||||
|
m.lock.Lock() |
||||||
|
defer m.lock.Unlock() |
||||||
|
|
||||||
|
if meterPtr := (*metric.MeterImpl)(atomic.LoadPointer(&m.delegate)); meterPtr != nil { |
||||||
|
return (*meterPtr).NewAsyncInstrument(desc, runner) |
||||||
|
} |
||||||
|
|
||||||
|
inst := &asyncImpl{ |
||||||
|
instrument: instrument{ |
||||||
|
descriptor: desc, |
||||||
|
}, |
||||||
|
runner: runner, |
||||||
|
} |
||||||
|
m.asyncInsts = append(m.asyncInsts, inst) |
||||||
|
return inst, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (obs *asyncImpl) Implementation() interface{} { |
||||||
|
if implPtr := (*metric.AsyncImpl)(atomic.LoadPointer(&obs.delegate)); implPtr != nil { |
||||||
|
return (*implPtr).Implementation() |
||||||
|
} |
||||||
|
return obs |
||||||
|
} |
||||||
|
|
||||||
|
func (obs *asyncImpl) setDelegate(d metric.MeterImpl) { |
||||||
|
implPtr := new(metric.AsyncImpl) |
||||||
|
|
||||||
|
var err error |
||||||
|
*implPtr, err = d.NewAsyncInstrument(obs.descriptor, obs.runner) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
// TODO: There is no standard way to deliver this error to the user.
|
||||||
|
// See https://github.com/open-telemetry/opentelemetry-go/issues/514
|
||||||
|
// Note that the default SDK will not generate any errors yet, this is
|
||||||
|
// only for added safety.
|
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
atomic.StorePointer(&obs.delegate, unsafe.Pointer(implPtr)) |
||||||
|
} |
||||||
|
|
||||||
|
// Metric updates
|
||||||
|
|
||||||
|
func (m *meterImpl) RecordBatch(ctx context.Context, labels []label.KeyValue, measurements ...metric.Measurement) { |
||||||
|
if delegatePtr := (*metric.MeterImpl)(atomic.LoadPointer(&m.delegate)); delegatePtr != nil { |
||||||
|
(*delegatePtr).RecordBatch(ctx, labels, measurements...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (inst *syncImpl) RecordOne(ctx context.Context, number number.Number, labels []label.KeyValue) { |
||||||
|
if instPtr := (*metric.SyncImpl)(atomic.LoadPointer(&inst.delegate)); instPtr != nil { |
||||||
|
(*instPtr).RecordOne(ctx, number, labels) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Bound instrument initialization
|
||||||
|
|
||||||
|
func (bound *syncHandle) RecordOne(ctx context.Context, number number.Number) { |
||||||
|
instPtr := (*metric.SyncImpl)(atomic.LoadPointer(&bound.inst.delegate)) |
||||||
|
if instPtr == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
var implPtr *metric.BoundSyncImpl |
||||||
|
bound.initialize.Do(func() { |
||||||
|
implPtr = new(metric.BoundSyncImpl) |
||||||
|
*implPtr = (*instPtr).Bind(bound.labels) |
||||||
|
atomic.StorePointer(&bound.delegate, unsafe.Pointer(implPtr)) |
||||||
|
}) |
||||||
|
if implPtr == nil { |
||||||
|
implPtr = (*metric.BoundSyncImpl)(atomic.LoadPointer(&bound.delegate)) |
||||||
|
} |
||||||
|
// This may still be nil if instrument was created and bound
|
||||||
|
// without a delegate, then the instrument was set to have a
|
||||||
|
// delegate and unbound.
|
||||||
|
if implPtr == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
(*implPtr).RecordOne(ctx, number) |
||||||
|
} |
||||||
|
|
||||||
|
func AtomicFieldOffsets() map[string]uintptr { |
||||||
|
return map[string]uintptr{ |
||||||
|
"meterProvider.delegate": unsafe.Offsetof(meterProvider{}.delegate), |
||||||
|
"meterImpl.delegate": unsafe.Offsetof(meterImpl{}.delegate), |
||||||
|
"syncImpl.delegate": unsafe.Offsetof(syncImpl{}.delegate), |
||||||
|
"asyncImpl.delegate": unsafe.Offsetof(asyncImpl{}.delegate), |
||||||
|
"syncHandle.delegate": unsafe.Offsetof(syncHandle{}.delegate), |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 global |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/propagation" |
||||||
|
) |
||||||
|
|
||||||
|
// textMapPropagator is a default TextMapPropagator that delegates calls to a
|
||||||
|
// registered delegate if one is set, otherwise it defaults to delegating the
|
||||||
|
// calls to a the default no-op propagation.TextMapPropagator.
|
||||||
|
type textMapPropagator struct { |
||||||
|
mtx sync.Mutex |
||||||
|
once sync.Once |
||||||
|
delegate propagation.TextMapPropagator |
||||||
|
noop propagation.TextMapPropagator |
||||||
|
} |
||||||
|
|
||||||
|
// Compile-time guarantee that textMapPropagator implements the
|
||||||
|
// propagation.TextMapPropagator interface.
|
||||||
|
var _ propagation.TextMapPropagator = (*textMapPropagator)(nil) |
||||||
|
|
||||||
|
func newTextMapPropagator() *textMapPropagator { |
||||||
|
return &textMapPropagator{ |
||||||
|
noop: propagation.NewCompositeTextMapPropagator(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// SetDelegate sets a delegate propagation.TextMapPropagator that all calls are
|
||||||
|
// forwarded to. Delegation can only be performed once, all subsequent calls
|
||||||
|
// perform no delegation.
|
||||||
|
func (p *textMapPropagator) SetDelegate(delegate propagation.TextMapPropagator) { |
||||||
|
if delegate == nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
p.mtx.Lock() |
||||||
|
p.once.Do(func() { p.delegate = delegate }) |
||||||
|
p.mtx.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
// effectiveDelegate returns the current delegate of p if one is set,
|
||||||
|
// otherwise the default noop TextMapPropagator is returned. This method
|
||||||
|
// can be called concurrently.
|
||||||
|
func (p *textMapPropagator) effectiveDelegate() propagation.TextMapPropagator { |
||||||
|
p.mtx.Lock() |
||||||
|
defer p.mtx.Unlock() |
||||||
|
if p.delegate != nil { |
||||||
|
return p.delegate |
||||||
|
} |
||||||
|
return p.noop |
||||||
|
} |
||||||
|
|
||||||
|
// Inject set cross-cutting concerns from the Context into the carrier.
|
||||||
|
func (p *textMapPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) { |
||||||
|
p.effectiveDelegate().Inject(ctx, carrier) |
||||||
|
} |
||||||
|
|
||||||
|
// Extract reads cross-cutting concerns from the carrier into a Context.
|
||||||
|
func (p *textMapPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context { |
||||||
|
return p.effectiveDelegate().Extract(ctx, carrier) |
||||||
|
} |
||||||
|
|
||||||
|
// Fields returns the keys whose values are set with Inject.
|
||||||
|
func (p *textMapPropagator) Fields() []string { |
||||||
|
return p.effectiveDelegate().Fields() |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 global |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
"sync/atomic" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/metric" |
||||||
|
"go.opentelemetry.io/otel/propagation" |
||||||
|
"go.opentelemetry.io/otel/trace" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
tracerProviderHolder struct { |
||||||
|
tp trace.TracerProvider |
||||||
|
} |
||||||
|
|
||||||
|
meterProviderHolder struct { |
||||||
|
mp metric.MeterProvider |
||||||
|
} |
||||||
|
|
||||||
|
propagatorsHolder struct { |
||||||
|
tm propagation.TextMapPropagator |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
globalTracer = defaultTracerValue() |
||||||
|
globalMeter = defaultMeterValue() |
||||||
|
globalPropagators = defaultPropagatorsValue() |
||||||
|
|
||||||
|
delegateMeterOnce sync.Once |
||||||
|
delegateTraceOnce sync.Once |
||||||
|
delegateTextMapPropagatorOnce sync.Once |
||||||
|
) |
||||||
|
|
||||||
|
// TracerProvider is the internal implementation for global.TracerProvider.
|
||||||
|
func TracerProvider() trace.TracerProvider { |
||||||
|
return globalTracer.Load().(tracerProviderHolder).tp |
||||||
|
} |
||||||
|
|
||||||
|
// SetTracerProvider is the internal implementation for global.SetTracerProvider.
|
||||||
|
func SetTracerProvider(tp trace.TracerProvider) { |
||||||
|
delegateTraceOnce.Do(func() { |
||||||
|
current := TracerProvider() |
||||||
|
if current == tp { |
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid TracerProvider, the global instance cannot be reinstalled") |
||||||
|
} else if def, ok := current.(*tracerProvider); ok { |
||||||
|
def.setDelegate(tp) |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
globalTracer.Store(tracerProviderHolder{tp: tp}) |
||||||
|
} |
||||||
|
|
||||||
|
// MeterProvider is the internal implementation for global.MeterProvider.
|
||||||
|
func MeterProvider() metric.MeterProvider { |
||||||
|
return globalMeter.Load().(meterProviderHolder).mp |
||||||
|
} |
||||||
|
|
||||||
|
// SetMeterProvider is the internal implementation for global.SetMeterProvider.
|
||||||
|
func SetMeterProvider(mp metric.MeterProvider) { |
||||||
|
delegateMeterOnce.Do(func() { |
||||||
|
current := MeterProvider() |
||||||
|
|
||||||
|
if current == mp { |
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid MeterProvider, the global instance cannot be reinstalled") |
||||||
|
} else if def, ok := current.(*meterProvider); ok { |
||||||
|
def.setDelegate(mp) |
||||||
|
} |
||||||
|
}) |
||||||
|
globalMeter.Store(meterProviderHolder{mp: mp}) |
||||||
|
} |
||||||
|
|
||||||
|
// TextMapPropagator is the internal implementation for global.TextMapPropagator.
|
||||||
|
func TextMapPropagator() propagation.TextMapPropagator { |
||||||
|
return globalPropagators.Load().(propagatorsHolder).tm |
||||||
|
} |
||||||
|
|
||||||
|
// SetTextMapPropagator is the internal implementation for global.SetTextMapPropagator.
|
||||||
|
func SetTextMapPropagator(p propagation.TextMapPropagator) { |
||||||
|
// For the textMapPropagator already returned by TextMapPropagator
|
||||||
|
// delegate to p.
|
||||||
|
delegateTextMapPropagatorOnce.Do(func() { |
||||||
|
if current := TextMapPropagator(); current == p { |
||||||
|
// Setting the provider to the prior default is nonsense, panic.
|
||||||
|
// Panic is acceptable because we are likely still early in the
|
||||||
|
// process lifetime.
|
||||||
|
panic("invalid TextMapPropagator, the global instance cannot be reinstalled") |
||||||
|
} else if def, ok := current.(*textMapPropagator); ok { |
||||||
|
def.SetDelegate(p) |
||||||
|
} |
||||||
|
}) |
||||||
|
// Return p when subsequent calls to TextMapPropagator are made.
|
||||||
|
globalPropagators.Store(propagatorsHolder{tm: p}) |
||||||
|
} |
||||||
|
|
||||||
|
func defaultTracerValue() *atomic.Value { |
||||||
|
v := &atomic.Value{} |
||||||
|
v.Store(tracerProviderHolder{tp: &tracerProvider{}}) |
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
func defaultMeterValue() *atomic.Value { |
||||||
|
v := &atomic.Value{} |
||||||
|
v.Store(meterProviderHolder{mp: newMeterProvider()}) |
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
func defaultPropagatorsValue() *atomic.Value { |
||||||
|
v := &atomic.Value{} |
||||||
|
v.Store(propagatorsHolder{tm: newTextMapPropagator()}) |
||||||
|
return v |
||||||
|
} |
||||||
|
|
||||||
|
// ResetForTest restores the initial global state, for testing purposes.
|
||||||
|
func ResetForTest() { |
||||||
|
globalTracer = defaultTracerValue() |
||||||
|
globalMeter = defaultMeterValue() |
||||||
|
globalPropagators = defaultPropagatorsValue() |
||||||
|
delegateMeterOnce = sync.Once{} |
||||||
|
delegateTraceOnce = sync.Once{} |
||||||
|
delegateTextMapPropagatorOnce = sync.Once{} |
||||||
|
} |
@ -0,0 +1,128 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 global |
||||||
|
|
||||||
|
/* |
||||||
|
This file contains the forwarding implementation of the TracerProvider used as |
||||||
|
the default global instance. Prior to initialization of an SDK, Tracers |
||||||
|
returned by the global TracerProvider will provide no-op functionality. This |
||||||
|
means that all Span created prior to initialization are no-op Spans. |
||||||
|
|
||||||
|
Once an SDK has been initialized, all provided no-op Tracers are swapped for |
||||||
|
Tracers provided by the SDK defined TracerProvider. However, any Span started |
||||||
|
prior to this initialization does not change its behavior. Meaning, the Span |
||||||
|
remains a no-op Span. |
||||||
|
|
||||||
|
The implementation to track and swap Tracers locks all new Tracer creation |
||||||
|
until the swap is complete. This assumes that this operation is not |
||||||
|
performance-critical. If that assumption is incorrect, be sure to configure an |
||||||
|
SDK prior to any Tracer creation. |
||||||
|
*/ |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/internal/trace/noop" |
||||||
|
"go.opentelemetry.io/otel/trace" |
||||||
|
) |
||||||
|
|
||||||
|
// tracerProvider is a placeholder for a configured SDK TracerProvider.
|
||||||
|
//
|
||||||
|
// All TracerProvider functionality is forwarded to a delegate once
|
||||||
|
// configured.
|
||||||
|
type tracerProvider struct { |
||||||
|
mtx sync.Mutex |
||||||
|
tracers []*tracer |
||||||
|
|
||||||
|
delegate trace.TracerProvider |
||||||
|
} |
||||||
|
|
||||||
|
// Compile-time guarantee that tracerProvider implements the TracerProvider
|
||||||
|
// interface.
|
||||||
|
var _ trace.TracerProvider = &tracerProvider{} |
||||||
|
|
||||||
|
// setDelegate configures p to delegate all TracerProvider functionality to
|
||||||
|
// provider.
|
||||||
|
//
|
||||||
|
// All Tracers provided prior to this function call are switched out to be
|
||||||
|
// Tracers provided by provider.
|
||||||
|
//
|
||||||
|
// Delegation only happens on the first call to this method. All subsequent
|
||||||
|
// calls result in no delegation changes.
|
||||||
|
func (p *tracerProvider) setDelegate(provider trace.TracerProvider) { |
||||||
|
if p.delegate != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
p.mtx.Lock() |
||||||
|
defer p.mtx.Unlock() |
||||||
|
|
||||||
|
p.delegate = provider |
||||||
|
for _, t := range p.tracers { |
||||||
|
t.setDelegate(provider) |
||||||
|
} |
||||||
|
|
||||||
|
p.tracers = nil |
||||||
|
} |
||||||
|
|
||||||
|
// Tracer implements TracerProvider.
|
||||||
|
func (p *tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer { |
||||||
|
p.mtx.Lock() |
||||||
|
defer p.mtx.Unlock() |
||||||
|
|
||||||
|
if p.delegate != nil { |
||||||
|
return p.delegate.Tracer(name, opts...) |
||||||
|
} |
||||||
|
|
||||||
|
t := &tracer{name: name, opts: opts} |
||||||
|
p.tracers = append(p.tracers, t) |
||||||
|
return t |
||||||
|
} |
||||||
|
|
||||||
|
// tracer is a placeholder for a trace.Tracer.
|
||||||
|
//
|
||||||
|
// All Tracer functionality is forwarded to a delegate once configured.
|
||||||
|
// Otherwise, all functionality is forwarded to a NoopTracer.
|
||||||
|
type tracer struct { |
||||||
|
once sync.Once |
||||||
|
name string |
||||||
|
opts []trace.TracerOption |
||||||
|
|
||||||
|
delegate trace.Tracer |
||||||
|
} |
||||||
|
|
||||||
|
// Compile-time guarantee that tracer implements the trace.Tracer interface.
|
||||||
|
var _ trace.Tracer = &tracer{} |
||||||
|
|
||||||
|
// setDelegate configures t to delegate all Tracer functionality to Tracers
|
||||||
|
// created by provider.
|
||||||
|
//
|
||||||
|
// All subsequent calls to the Tracer methods will be passed to the delegate.
|
||||||
|
//
|
||||||
|
// Delegation only happens on the first call to this method. All subsequent
|
||||||
|
// calls result in no delegation changes.
|
||||||
|
func (t *tracer) setDelegate(provider trace.TracerProvider) { |
||||||
|
t.once.Do(func() { t.delegate = provider.Tracer(t.name, t.opts...) }) |
||||||
|
} |
||||||
|
|
||||||
|
// Start implements trace.Tracer by forwarding the call to t.delegate if
|
||||||
|
// set, otherwise it forwards the call to a NoopTracer.
|
||||||
|
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanOption) (context.Context, trace.Span) { |
||||||
|
if t.delegate != nil { |
||||||
|
return t.delegate.Start(ctx, name, opts...) |
||||||
|
} |
||||||
|
return noop.Tracer.Start(ctx, name, opts...) |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
// Copyright The OpenTelemetry Authors
|
||||||
|
//
|
||||||
|
// 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 internal |
||||||
|
|
||||||
|
import ( |
||||||
|
"math" |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
func BoolToRaw(b bool) uint64 { |
||||||
|
if b { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func RawToBool(r uint64) bool { |
||||||
|
return r != 0 |
||||||
|
} |
||||||
|
|
||||||
|
func Int64ToRaw(i int64) uint64 { |
||||||
|
return uint64(i) |
||||||
|
} |
||||||
|
|
||||||
|
func RawToInt64(r uint64) int64 { |
||||||
|
return int64(r) |
||||||
|
} |
||||||
|
|
||||||
|
func Uint64ToRaw(u uint64) uint64 { |
||||||
|
return u |
||||||
|
} |
||||||
|
|
||||||
|
func RawToUint64(r uint64) uint64 { |
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
func Float64ToRaw(f float64) uint64 { |
||||||
|
return math.Float64bits(f) |
||||||
|
} |
||||||
|
|
||||||
|
func RawToFloat64(r uint64) float64 { |
||||||
|
return math.Float64frombits(r) |
||||||
|
} |
||||||
|
|
||||||
|
func Int32ToRaw(i int32) uint64 { |
||||||
|
return uint64(i) |
||||||
|
} |
||||||
|
|
||||||
|
func RawToInt32(r uint64) int32 { |
||||||
|
return int32(r) |
||||||
|
} |
||||||
|
|
||||||
|
func Uint32ToRaw(u uint32) uint64 { |
||||||
|
return uint64(u) |
||||||
|
} |
||||||
|
|
||||||
|
func RawToUint32(r uint64) uint32 { |
||||||
|
return uint32(r) |
||||||
|
} |
||||||
|
|
||||||
|
func Float32ToRaw(f float32) uint64 { |
||||||
|
return Uint32ToRaw(math.Float32bits(f)) |
||||||
|
} |
||||||
|
|
||||||
|
func RawToFloat32(r uint64) float32 { |
||||||
|
return math.Float32frombits(RawToUint32(r)) |
||||||
|
} |
||||||
|
|
||||||
|
func RawPtrToFloat64Ptr(r *uint64) *float64 { |
||||||
|
return (*float64)(unsafe.Pointer(r)) |
||||||
|
} |
||||||
|
|
||||||
|
func RawPtrToInt64Ptr(r *uint64) *int64 { |
||||||
|
return (*int64)(unsafe.Pointer(r)) |
||||||
|
} |
||||||
|
|
||||||
|
func RawPtrToUint64Ptr(r *uint64) *uint64 { |
||||||
|
return r |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue