[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 |
||||
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 |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"strings" |
||||
|
||||
"github.com/go-redis/redis/v8/internal/rand" |
||||
) |
||||
|
||||
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 |
||||
} |
330
vendor/github.com/go-redis/redis/v7/redis.go → vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
330
vendor/github.com/go-redis/redis/v7/redis.go → vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
349
vendor/github.com/go-redis/redis/v7/ring.go → vendor/github.com/go-redis/redis/v8/ring.go
generated
vendored
349
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