Initial commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.idea/**
|
||||||
|
.vscode/**
|
||||||
|
.vstags
|
||||||
|
data/config.json
|
||||||
|
aleesa-misc-go
|
||||||
20
LICENSE.txt
Normal file
20
LICENSE.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2022 Sergei Fedosoff <eleksir@gmail.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.
|
||||||
112
README
Normal file
112
README
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
aleesa-misc-go
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
Основной роутер команд для aleesa-bot. Все протокольные плагины шлют команды сюда, а отсюда команды разлетаются в
|
||||||
|
модули-исполнители и ответ от исполнителей идёт обратно заказчику.
|
||||||
|
Работает по протоколу Redis pubsub.
|
||||||
|
|
||||||
|
|
||||||
|
Механика работы
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
Роутер подписан на канал misc (тот, который указан в поле channel конфига).
|
||||||
|
|
||||||
|
Модули, реализующие "клиентские протоколы" - telegram, irc, jabber и проч, подписаны на канал своего протокола.
|
||||||
|
Входящие сообщения они получают только через этот канал.
|
||||||
|
|
||||||
|
Модули, реализующие иную функциональность также подписаны каждый на свой канал. Кроме того, для реализации
|
||||||
|
дополнительных синхронных возможностей, они могут слушать например http запросы на отдельных - ip:портах. Но это уже
|
||||||
|
модуль-специфичная настройка.
|
||||||
|
|
||||||
|
Формат протокола - это json с полями:
|
||||||
|
from - модуль (по сути, redis-канал), в который надо ответить
|
||||||
|
chatid - чат, из которого прилетело сообщение
|
||||||
|
userid - пользователь, на чью фразу мы отвечаем, или пользователь, для которого предназначается ответ
|
||||||
|
message - текст сообщения
|
||||||
|
plugin - первоначальный источник (по сути, redis-канал) сообщения.
|
||||||
|
mode - public или private
|
||||||
|
|
||||||
|
misc - объект с дополнительными полями:
|
||||||
|
|
||||||
|
misc->answer - 0 если ответ не требуется и 1 если ответ требуется (использован в модуле craniac), значение
|
||||||
|
по-умолчанию 1, если поля нет.
|
||||||
|
|
||||||
|
misc->bot_nick - ник бота. Типа как, для разных чятов он может быть разный. (Использовано в модуле craniac.) Если
|
||||||
|
поле отсутствует, проверка на обращение к боту по нику не происходит.
|
||||||
|
|
||||||
|
misc->csign - command sign - значок, которым предваряется команда в чятике. Исторически сложилось так, что для
|
||||||
|
разных ботов этот символ был разный, например, eggdrop использовал точку, а sulci - восклицательный
|
||||||
|
знак. По-умолчанию, aleesa-bot вездепроставляет восклицательный знак, если во входящем сообщении
|
||||||
|
такой настройки нету. Изначально настройка задаётся в конфиге чат-модуля (aleesa-telegram,
|
||||||
|
например).
|
||||||
|
|
||||||
|
misc->fwd_cnt - количество пересылок сообщения. Значение по-умолчанию 1. Предполагается, что при пересылке
|
||||||
|
сообщения, модуль будет инкрементировать это значение и заодно проверять, что оно не превышает
|
||||||
|
настройку forwards_max в конфиге или 5, если в конфиге такой настройки нету.
|
||||||
|
|
||||||
|
misc->good_morning - флажок признака сообщения-фразы дня, которая по идее осылается в ~8 часов по мск. Используется в
|
||||||
|
модуле phrases.
|
||||||
|
|
||||||
|
misc->msg_format - Использовать форматирование в сообщении. На текущий момент этот флажок выставляется в модуле
|
||||||
|
telegram и благополучно проходит через модуль misc, далее попадает в модуль phrases, и, наконец,
|
||||||
|
возвращается в модуль telegram, чтобы выходное сообщение было отослано как отформатированное.
|
||||||
|
|
||||||
|
misc->username - имя пользователя, по которому к нему надо обратиться в ответе (используется в модуле games). Для
|
||||||
|
telegram это фактически ссылка на пользователя, как правило с его firstname и lastname (или
|
||||||
|
@username, если firstname и lastname у него нету), чтобы его можно было захайлатить.
|
||||||
|
|
||||||
|
|
||||||
|
Как это собрать?
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
Понадобится golang версии 1.16.8 или более новая (на предыдущих версиях возможно собирётся, но я не тестировал).
|
||||||
|
go.mod и go.sum актуальны для 1.17.7.
|
||||||
|
|
||||||
|
Сам процесс сборки простой, достаточно запустить:
|
||||||
|
CGO_ENABLED=0 go build
|
||||||
|
|
||||||
|
и через некоторое время мы получаем бинарник aleesa-misc-go. Это и есть наше приложение.
|
||||||
|
|
||||||
|
|
||||||
|
Как это запустить?
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
Приложение тестировалось на alpine linux x86_64 3.12, но должно работать на любом (более свежем) linux-е.
|
||||||
|
|
||||||
|
После создания data/config.json из data/config_sample.json, можно запускать приложение.
|
||||||
|
|
||||||
|
Предполагается, что конфиг написан в формате human json (https://hjson.github.io), который допускает коментарии и
|
||||||
|
некоторые другие вольности в форматировании json-а. Конфиг ищется последовательно по следующим путям по принципу "кто
|
||||||
|
первый того и тапки":
|
||||||
|
~/.aleesa-misc-go.json
|
||||||
|
~/aleesa-misc-go.json
|
||||||
|
/etc/aleesa-misc-go.json
|
||||||
|
data/config.json
|
||||||
|
|
||||||
|
В каталоге contrib находится скрипт для alpine linux, системы инициализации openrc. Этот скрипт достаточно положить в
|
||||||
|
/etc/init.d/aleesa-misc-go. После чего скопировать бинарник в каталог /var/lib/aleesa-misc-go, положить конфиг в одну
|
||||||
|
из дефолтных локаций (тестировалась /var/lib/aleesa-misc-go/data/config.json) и после этого сервис запускается
|
||||||
|
командой:
|
||||||
|
service aleesa-misc-go start
|
||||||
|
|
||||||
|
В автозагрузку его можно прописать через команду:
|
||||||
|
rc-update add aleesa-misc-go default
|
||||||
|
|
||||||
|
|
||||||
|
Nota Bene
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
По механике, в процессе работы мы форкаем горутинки на каждое входящее сообщение, особо не контроллируя этот процесс.
|
||||||
|
Предполагается, что на этот роутер не будет большой нагрузки, даже если будет несколько ботов на входе - ограничения
|
||||||
|
телеграма и других мессенджеров не должны дать возможности развить достаточно большой message rate, чтобы об это
|
||||||
|
споткнуться.
|
||||||
|
|
||||||
|
Формально, более удачным было бы иметь автоматический пул воркеров-горутинок и раскидывать им через channel-ы
|
||||||
|
сообщения. Внутри каждой горутинки можно было бы повиcнуть в бесконечном цикле на канале входящих сообщений и так и
|
||||||
|
работать. Но на практике оно того не стОит.
|
||||||
|
|
||||||
|
|
||||||
|
Nota Bene #2
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
Go не поддерживает системный вызов fork() из-за чего демонизация программ на гошке средствами самой гошки - это в
|
||||||
|
некотором роде проблема. Решается она с помощью утилиты daemonize (https://github.com/bmc/daemonize).
|
||||||
|
|
||||||
|
Соответствено, если для запуска этой программы предполагается использовать возможности демонизации, то можно
|
||||||
|
воспользоваться этой программой.
|
||||||
|
|
||||||
|
В alpine linux есть средства openrc для супервайзинга foreground-сервисов.
|
||||||
20
contrib/aleesa-misc-go.openrc
Normal file
20
contrib/aleesa-misc-go.openrc
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
name="$SVCNAME"
|
||||||
|
command="/var/lib/$SVCNAME/$SVCNAME"
|
||||||
|
supervisor="supervise-daemon"
|
||||||
|
pidfile="/run/$SVCNAME/$SVCNAME.pid"
|
||||||
|
command_user=nobody:nogroup
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need net redis
|
||||||
|
use dns
|
||||||
|
after redis
|
||||||
|
}
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
mkdir -p /var/log/aleesa-misc-go
|
||||||
|
chown -R nobody:nogroup /var/log/aleesa-misc-go
|
||||||
|
mkdir -p /run/aleesa-misc-go
|
||||||
|
chown -R nobody:nogroup /run/aleesa-misc-go
|
||||||
|
}
|
||||||
37
data/config_sample.json
Normal file
37
data/config_sample.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
# localhost if not set
|
||||||
|
"server" : "redis",
|
||||||
|
|
||||||
|
# 6379 if not set
|
||||||
|
"port" : 6379,
|
||||||
|
|
||||||
|
# info if not set
|
||||||
|
"loglevel" : "info",
|
||||||
|
|
||||||
|
# If not set defaults to stdout
|
||||||
|
"log" : "/var/log/aleesa-misc-go/aleesa-misc-go.log",
|
||||||
|
|
||||||
|
# Must be set to redis pubsub channel where chat client daemons sending messages
|
||||||
|
"channel" : "misc",
|
||||||
|
|
||||||
|
# List of redis pubsub channels where aleesa-misc-go forwards messages
|
||||||
|
"forward_channels" : {
|
||||||
|
# games if not set
|
||||||
|
"games" : "games",
|
||||||
|
|
||||||
|
# phrases if not set
|
||||||
|
"phrases" : "phrases",
|
||||||
|
|
||||||
|
# webapp if not set
|
||||||
|
"webapp" : "webapp",
|
||||||
|
|
||||||
|
# craniac if not set
|
||||||
|
"craniac" : "craniac"
|
||||||
|
},
|
||||||
|
|
||||||
|
# Must be set - it is default command prefix (it is also set for each chat client daemon)
|
||||||
|
"csign" : "!",
|
||||||
|
|
||||||
|
# Will be set to 5 if not set
|
||||||
|
"forwards_max" : 5
|
||||||
|
}
|
||||||
14
go.mod
Normal file
14
go.mod
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module aleesa-misc-go
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-redis/redis/v8 v8.11.4
|
||||||
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hjson/hjson-go v3.1.0+incompatible
|
||||||
|
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||||
|
github.com/onsi/gomega v1.18.1 // indirect
|
||||||
|
)
|
||||||
117
go.sum
Normal file
117
go.sum
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/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/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/hjson/hjson-go v3.1.0+incompatible h1:DY/9yE8ey8Zv22bY+mHV1uk2yRy0h8tKhZ77hEdi0Aw=
|
||||||
|
github.com/hjson/hjson-go v3.1.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
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.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
|
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.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
|
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/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
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-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/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/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/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
457
main.go
Normal file
457
main.go
Normal file
@@ -0,0 +1,457 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Конфиг
|
||||||
|
type myConfig struct {
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
Channel string `json:"channel,omitempty"`
|
||||||
|
ForwardChannels struct {
|
||||||
|
Games string `json:"games,omitempty"`
|
||||||
|
Phrases string `json:"phrases,omitempty"`
|
||||||
|
Webapp string `json:"webapp,omitempty"`
|
||||||
|
Craniac string `json:"craniac,omitempty"`
|
||||||
|
} `json:"forward_channels"`
|
||||||
|
Csign string `json:"csign,omitempty"`
|
||||||
|
ForwardsMax int64 `json:"forwards_max,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Входящее сообщение из pubsub-канала redis-ки
|
||||||
|
type rMsg struct {
|
||||||
|
From string `json:"from,omitempty"`
|
||||||
|
Chatid int64 `json:"chatid,omitempty"`
|
||||||
|
Userid int64 `json:"userid,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Plugin string `json:"plugin,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
Misc struct {
|
||||||
|
Answer int64 `json:"answer,omitempty"`
|
||||||
|
Botnick string `json:"bot_nick,omitempty"`
|
||||||
|
Csign string `json:"csign,omitempty"`
|
||||||
|
Fwdcnt int64 `json:"fwd_cnt,omitempty"`
|
||||||
|
GoodMorning int64 `json:"good_morning,omitempty"`
|
||||||
|
Msgformat int64 `json:"msg_format,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
} `json:"Misc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходящее сообщение в pubsub-канал redis-ки
|
||||||
|
type sMsg struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
Chatid int64 `json:"chatid"`
|
||||||
|
Userid int64 `json:"userid"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Plugin string `json:"plugin"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Misc struct {
|
||||||
|
Answer int64 `json:"answer"`
|
||||||
|
Botnick string `json:"bot_nick"`
|
||||||
|
Csign string `json:"csign"`
|
||||||
|
Fwdcnt int64 `json:"fwd_cnt"`
|
||||||
|
GoodMorning int64 `json:"good_morning"`
|
||||||
|
Msgformat int64 `json:"msg_format"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"misc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config - это у нас глобальная штука :)
|
||||||
|
var config myConfig
|
||||||
|
|
||||||
|
// To break circular message forwarding we must set some sane default, it can be overridden via config
|
||||||
|
var forwardMax int64 = 5
|
||||||
|
|
||||||
|
// Дефолтный конфиг клиента-редиски
|
||||||
|
var redisClient *redis.Client
|
||||||
|
|
||||||
|
// Горутинка, которая парсит json-чики прилетевшие из REDIS-ки
|
||||||
|
func msgParser(ctx context.Context, msg string) {
|
||||||
|
var sendTo = "craniac"
|
||||||
|
var j rMsg
|
||||||
|
|
||||||
|
log.Debugf("Incomming raw json: %s", msg)
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||||
|
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate our j
|
||||||
|
if exist := j.From; exist == "" {
|
||||||
|
log.Warnf("Incorrect msg from redis, no from field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist := j.Chatid; exist == 0 {
|
||||||
|
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist := j.Userid; exist == 0 {
|
||||||
|
log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist := j.Message; exist == "" {
|
||||||
|
log.Warnf("Incorrect msg from redis, no message field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist := j.Plugin; exist == "" {
|
||||||
|
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist := j.Mode; exist == "" {
|
||||||
|
log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
|
||||||
|
// j.Misc.Botnick тоже можно не передавать, тогда будет записана пустая строка
|
||||||
|
// j.Misc.Csign если нам его не передали, возьмём значение из конфига
|
||||||
|
if exist := j.Misc.Csign; exist == "" {
|
||||||
|
j.Misc.Csign = config.Csign
|
||||||
|
}
|
||||||
|
|
||||||
|
// j.Misc.Fwdcnt если нам его не передали, то будет 0
|
||||||
|
if exist := j.Misc.Fwdcnt; exist == 0 {
|
||||||
|
j.Misc.Fwdcnt = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// j.Misc.GoodMorning может быть быть 1 или 0, по-умолчанию 0
|
||||||
|
// j.Misc.Msgformat может быть быть 1 или 0, по-умолчанию 0
|
||||||
|
// j.Misc.Username можно не передавать, тогда будет пустая строка
|
||||||
|
|
||||||
|
// Отвалидировались, теперь вернёмся к нашим баранам.
|
||||||
|
|
||||||
|
// Если у нас циклическая пересылка сообщения, попробуем её тут разорвать, отбросив сообщение
|
||||||
|
if j.Misc.Fwdcnt > config.ForwardsMax {
|
||||||
|
log.Warnf("Discarding msg with fwd_cnt exceeding max value: %s", msg)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
j.Misc.Fwdcnt++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Классифицирем входящие сообщения. Первым делом, попробуем определить команды
|
||||||
|
if j.Message[0:len(j.Misc.Csign)] == j.Misc.Csign {
|
||||||
|
// Может быть, это команда модуля phrases?
|
||||||
|
var done = false
|
||||||
|
var cmd = j.Message[len(j.Misc.Csign):]
|
||||||
|
cmds := []string{"friday", "пятница", "proverb", "пословица", "fortune", "фортунка", "f", "ф", "karma", "карма"}
|
||||||
|
|
||||||
|
for _, command := range cmds {
|
||||||
|
if cmd == command {
|
||||||
|
sendTo = "phrases"
|
||||||
|
|
||||||
|
// Костыль для кармы
|
||||||
|
if cmd == "karma" || cmd == "карма" {
|
||||||
|
j.Misc.Answer = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Не угадали? акей, как насчёт модуля webapp?
|
||||||
|
if !done {
|
||||||
|
cmds = []string{"buni", "anek", "анек", "анекдот", "cat", "кис", "drink", "праздник", "fox", "лис", "frog",
|
||||||
|
"лягушка", "horse", "лошадь", "лошадка", "monkeyuser", "rabbit", "bunny", "кролик", "snail", "улитка",
|
||||||
|
"owl", "сова", "сыч", "xkcd", "tits", "boobs", "tities", "boobies", "сиси", "сисечки", "butt", "booty",
|
||||||
|
"ass", "попа", "попка"}
|
||||||
|
|
||||||
|
for _, command := range cmds {
|
||||||
|
if cmd == command {
|
||||||
|
sendTo = "webapp"
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Не фортануло? может, повезёт с модулем games?
|
||||||
|
if !done {
|
||||||
|
cmds = []string{"dig", "копать", "fish", "fishing", "рыба", "рыбка", "рыбалка"}
|
||||||
|
|
||||||
|
for _, command := range cmds {
|
||||||
|
if cmd == command {
|
||||||
|
sendTo = "games"
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нет? Вдруг это комплексная команда?
|
||||||
|
if !done {
|
||||||
|
cmdLen := len(cmd)
|
||||||
|
|
||||||
|
cmds := []string{"w ", "п ", "погода ", "погодка ", "погадка ", "weather ", "karma ", "карма "}
|
||||||
|
|
||||||
|
for _, command := range cmds {
|
||||||
|
if cmdLen > len(command) && cmd[0:len(command)] == command {
|
||||||
|
if command == "karma " || command == "карма " {
|
||||||
|
sendTo = "phrases"
|
||||||
|
|
||||||
|
// Костыль для кармы
|
||||||
|
j.Misc.Answer = 1
|
||||||
|
} else {
|
||||||
|
sendTo = "webapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Попробуем выискать изменение кармы
|
||||||
|
msgLen := len(j.Message)
|
||||||
|
|
||||||
|
// ++ или -- на конце фразы - это наш кандидат
|
||||||
|
if msgLen > len("++") {
|
||||||
|
if j.Message[msgLen-len("--"):msgLen] == "--" || j.Message[msgLen-len("++"):msgLen] == "++" {
|
||||||
|
// Предполагается, что менять карму мы будем для одной фразы, то есть для 1 строки
|
||||||
|
if len(strings.Split(j.Message, "\n")) == 1 {
|
||||||
|
sendTo = "phrases"
|
||||||
|
|
||||||
|
// Костыль для кармы
|
||||||
|
j.Misc.Answer = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Настало время формировать json и засылать его в дальше
|
||||||
|
var message sMsg
|
||||||
|
message.From = j.From
|
||||||
|
message.Userid = j.Userid
|
||||||
|
message.Chatid = j.Chatid
|
||||||
|
message.Message = j.Message
|
||||||
|
message.Plugin = j.Plugin
|
||||||
|
message.Mode = j.Mode
|
||||||
|
message.Misc.Fwdcnt = j.Misc.Fwdcnt
|
||||||
|
message.Misc.Csign = j.Misc.Csign
|
||||||
|
message.Misc.Username = j.Misc.Username
|
||||||
|
message.Misc.Answer = j.Misc.Answer
|
||||||
|
message.Misc.Botnick = j.Misc.Botnick
|
||||||
|
message.Misc.Msgformat = j.Misc.Msgformat
|
||||||
|
message.Misc.GoodMorning = j.Misc.GoodMorning
|
||||||
|
|
||||||
|
data, err := json.Marshal(message)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to to serialize message for redis: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заталкиваем наш json в редиску
|
||||||
|
if err := redisClient.Publish(ctx, sendTo, data).Err(); err != nil {
|
||||||
|
log.Warnf("Unable to send data to redis channel %s: %s", sendTo, err)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Send msg to redis channel %s: %s", sendTo, string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет
|
||||||
|
func readConfig() {
|
||||||
|
configLoaded := false
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get current executable path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.aleesa-misc-go.json",
|
||||||
|
"~/aleesa-misc-go.json",
|
||||||
|
"/etc/aleesa-misc-go.json",
|
||||||
|
configJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 65535 {
|
||||||
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||||
|
var sampleConfig myConfig
|
||||||
|
var tmp map[string]interface{}
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpjson, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидируем значения из конфига
|
||||||
|
if sampleConfig.Server == "" {
|
||||||
|
sampleConfig.Server = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Port == 0 {
|
||||||
|
sampleConfig.Port = 6379
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Loglevel == "" {
|
||||||
|
sampleConfig.Loglevel = "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
// sampleConfig.Log = "" if not set
|
||||||
|
|
||||||
|
if sampleConfig.Channel == "" {
|
||||||
|
log.Errorf("Channel field in config file %s must be set", location)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Частичная проверка, ровно то, куда мы _точно_ щепрввляем сообщения исходя из бизнес-логики приложения
|
||||||
|
if sampleConfig.ForwardChannels.Games == "" {
|
||||||
|
sampleConfig.ForwardChannels.Games = "games"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.ForwardChannels.Phrases == "" {
|
||||||
|
sampleConfig.ForwardChannels.Phrases = "phrases"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.ForwardChannels.Webapp == "" {
|
||||||
|
sampleConfig.ForwardChannels.Webapp = "webapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.ForwardChannels.Craniac == "" {
|
||||||
|
sampleConfig.ForwardChannels.Craniac = "craniac"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Csign == "" {
|
||||||
|
log.Errorf("Csign field in config file %s must be set", location)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.ForwardsMax == 0 {
|
||||||
|
sampleConfig.ForwardsMax = forwardMax
|
||||||
|
}
|
||||||
|
|
||||||
|
config = sampleConfig
|
||||||
|
configLoaded = true
|
||||||
|
log.Infof("Using %s as config file", location)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !configLoaded {
|
||||||
|
log.Error("Config was not loaded! Refusing to start.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Производит некоторую инициализацию перед запуском main()
|
||||||
|
func init() {
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableQuote: true,
|
||||||
|
DisableLevelTruncation: false,
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
TimestampFormat: "2006-01-02 15:04:05",
|
||||||
|
})
|
||||||
|
|
||||||
|
readConfig()
|
||||||
|
|
||||||
|
// no panic, no trace
|
||||||
|
switch config.Loglevel {
|
||||||
|
case "fatal":
|
||||||
|
log.SetLevel(log.FatalLevel)
|
||||||
|
case "error":
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
case "warn":
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
case "info":
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
case "debug":
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
default:
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основная функция программы, не добавить и не убавить
|
||||||
|
func main() {
|
||||||
|
// Main context
|
||||||
|
var ctx = context.Background()
|
||||||
|
|
||||||
|
// Откроем лог и скормим его логгеру
|
||||||
|
if config.Log != "" {
|
||||||
|
logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to open log file %s: %s", config.Log, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetOutput(logfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Иницализируем клиента
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", config.Server, config.Port),
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Debugf("Lazy connect() to redis at %s:%d", config.Server, config.Port)
|
||||||
|
subscriber := redisClient.Subscribe(ctx, config.Channel)
|
||||||
|
|
||||||
|
// Обработчик событий от редиски
|
||||||
|
for {
|
||||||
|
msg, err := subscriber.ReceiveMessage(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Unable to connect to redis at %s:%d: %s", config.Server, config.Port, err)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go msgParser(ctx, msg.Payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
make.sh
Executable file
3
make.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
CGO_ENABLED=0 go build -ldflags="-s -w" -a -gcflags=all=-l
|
||||||
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2016 Caleb Spare
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
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.
|
||||||
69
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
69
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# xxhash
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
||||||
|
[](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
||||||
|
|
||||||
|
xxhash is a Go implementation of the 64-bit
|
||||||
|
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||||
|
high-quality hashing algorithm that is much faster than anything in the Go
|
||||||
|
standard library.
|
||||||
|
|
||||||
|
This package provides a straightforward API:
|
||||||
|
|
||||||
|
```
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
func Sum64String(s string) uint64
|
||||||
|
type Digest struct{ ... }
|
||||||
|
func New() *Digest
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Digest` type implements hash.Hash64. Its key methods are:
|
||||||
|
|
||||||
|
```
|
||||||
|
func (*Digest) Write([]byte) (int, error)
|
||||||
|
func (*Digest) WriteString(string) (int, error)
|
||||||
|
func (*Digest) Sum64() uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
This implementation provides a fast pure-Go implementation and an even faster
|
||||||
|
assembly implementation for amd64.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This package is in a module and the latest code is in version 2 of the module.
|
||||||
|
You need a version of Go with at least "minimal module compatibility" to use
|
||||||
|
github.com/cespare/xxhash/v2:
|
||||||
|
|
||||||
|
* 1.9.7+ for Go 1.9
|
||||||
|
* 1.10.3+ for Go 1.10
|
||||||
|
* Go 1.11 or later
|
||||||
|
|
||||||
|
I recommend using the latest release of Go.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||||
|
implementations of Sum64.
|
||||||
|
|
||||||
|
| input size | purego | asm |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 5 B | 979.66 MB/s | 1291.17 MB/s |
|
||||||
|
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
|
||||||
|
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
|
||||||
|
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
|
||||||
|
|
||||||
|
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
|
||||||
|
the following commands under Go 1.11.2:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projects using this package
|
||||||
|
|
||||||
|
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||||
|
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||||
|
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||||
|
- [FreeCache](https://github.com/coocood/freecache)
|
||||||
|
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
||||||
3
vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
Normal file
3
vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module github.com/cespare/xxhash/v2
|
||||||
|
|
||||||
|
go 1.11
|
||||||
0
vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
Normal file
0
vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
Normal file
235
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
235
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||||
|
// at http://cyan4973.github.io/xxHash/.
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prime1 uint64 = 11400714785074694791
|
||||||
|
prime2 uint64 = 14029467366897019727
|
||||||
|
prime3 uint64 = 1609587929392839161
|
||||||
|
prime4 uint64 = 9650029242287828579
|
||||||
|
prime5 uint64 = 2870177450012600261
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||||
|
// possible in the Go code is worth a small (but measurable) performance boost
|
||||||
|
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||||
|
// convenience in the Go code in a few places where we need to intentionally
|
||||||
|
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||||
|
// result overflows a uint64).
|
||||||
|
var (
|
||||||
|
prime1v = prime1
|
||||||
|
prime2v = prime2
|
||||||
|
prime3v = prime3
|
||||||
|
prime4v = prime4
|
||||||
|
prime5v = prime5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digest implements hash.Hash64.
|
||||||
|
type Digest struct {
|
||||||
|
v1 uint64
|
||||||
|
v2 uint64
|
||||||
|
v3 uint64
|
||||||
|
v4 uint64
|
||||||
|
total uint64
|
||||||
|
mem [32]byte
|
||||||
|
n int // how much of mem is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||||
|
func New() *Digest {
|
||||||
|
var d Digest
|
||||||
|
d.Reset()
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the Digest's state so that it can be reused.
|
||||||
|
func (d *Digest) Reset() {
|
||||||
|
d.v1 = prime1v + prime2
|
||||||
|
d.v2 = prime2
|
||||||
|
d.v3 = 0
|
||||||
|
d.v4 = -prime1v
|
||||||
|
d.total = 0
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 8 bytes.
|
||||||
|
func (d *Digest) Size() int { return 8 }
|
||||||
|
|
||||||
|
// BlockSize always returns 32 bytes.
|
||||||
|
func (d *Digest) BlockSize() int { return 32 }
|
||||||
|
|
||||||
|
// Write adds more data to d. It always returns len(b), nil.
|
||||||
|
func (d *Digest) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b)
|
||||||
|
d.total += uint64(n)
|
||||||
|
|
||||||
|
if d.n+n < 32 {
|
||||||
|
// This new data doesn't even fill the current block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.n += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.n > 0 {
|
||||||
|
// Finish off the partial block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||||
|
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||||
|
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||||
|
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||||
|
b = b[32-d.n:]
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 32 {
|
||||||
|
// One or more full blocks left.
|
||||||
|
nw := writeBlocks(d, b)
|
||||||
|
b = b[nw:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store any remaining partial block.
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum appends the current hash to b and returns the resulting slice.
|
||||||
|
func (d *Digest) Sum(b []byte) []byte {
|
||||||
|
s := d.Sum64()
|
||||||
|
return append(
|
||||||
|
b,
|
||||||
|
byte(s>>56),
|
||||||
|
byte(s>>48),
|
||||||
|
byte(s>>40),
|
||||||
|
byte(s>>32),
|
||||||
|
byte(s>>24),
|
||||||
|
byte(s>>16),
|
||||||
|
byte(s>>8),
|
||||||
|
byte(s),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns the current hash.
|
||||||
|
func (d *Digest) Sum64() uint64 {
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if d.total >= 32 {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = d.v3 + prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += d.total
|
||||||
|
|
||||||
|
i, end := 0, d.n
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(d.mem[i:i+8]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(d.mem[i:i+4])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for i < end {
|
||||||
|
h ^= uint64(d.mem[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magic = "xxh\x06"
|
||||||
|
marshaledSize = len(magic) + 8*5 + 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||||
|
func (d *Digest) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, 0, marshaledSize)
|
||||||
|
b = append(b, magic...)
|
||||||
|
b = appendUint64(b, d.v1)
|
||||||
|
b = appendUint64(b, d.v2)
|
||||||
|
b = appendUint64(b, d.v3)
|
||||||
|
b = appendUint64(b, d.v4)
|
||||||
|
b = appendUint64(b, d.total)
|
||||||
|
b = append(b, d.mem[:d.n]...)
|
||||||
|
b = b[:len(b)+len(d.mem)-d.n]
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||||
|
func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
||||||
|
return errors.New("xxhash: invalid hash state identifier")
|
||||||
|
}
|
||||||
|
if len(b) != marshaledSize {
|
||||||
|
return errors.New("xxhash: invalid hash state size")
|
||||||
|
}
|
||||||
|
b = b[len(magic):]
|
||||||
|
b, d.v1 = consumeUint64(b)
|
||||||
|
b, d.v2 = consumeUint64(b)
|
||||||
|
b, d.v3 = consumeUint64(b)
|
||||||
|
b, d.v4 = consumeUint64(b)
|
||||||
|
b, d.total = consumeUint64(b)
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = int(d.total % uint64(len(d.mem)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, x uint64) []byte {
|
||||||
|
var a [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(a[:], x)
|
||||||
|
return append(b, a[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeUint64(b []byte) ([]byte, uint64) {
|
||||||
|
x := u64(b)
|
||||||
|
return b[8:], x
|
||||||
|
}
|
||||||
|
|
||||||
|
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||||
|
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||||
|
|
||||||
|
func round(acc, input uint64) uint64 {
|
||||||
|
acc += input * prime2
|
||||||
|
acc = rol31(acc)
|
||||||
|
acc *= prime1
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRound(acc, val uint64) uint64 {
|
||||||
|
val = round(0, val)
|
||||||
|
acc ^= val
|
||||||
|
acc = acc*prime1 + prime4
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||||
|
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||||
|
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||||
|
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||||
|
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||||
|
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||||
|
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||||
|
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
||||||
13
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go
generated
vendored
Normal file
13
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func writeBlocks(d *Digest, b []byte) int
|
||||||
215
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
215
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Register allocation:
|
||||||
|
// AX h
|
||||||
|
// SI pointer to advance through b
|
||||||
|
// DX n
|
||||||
|
// BX loop end
|
||||||
|
// R8 v1, k1
|
||||||
|
// R9 v2
|
||||||
|
// R10 v3
|
||||||
|
// R11 v4
|
||||||
|
// R12 tmp
|
||||||
|
// R13 prime1v
|
||||||
|
// R14 prime2v
|
||||||
|
// DI prime4v
|
||||||
|
|
||||||
|
// round reads from and advances the buffer pointer in SI.
|
||||||
|
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||||
|
#define round(r) \
|
||||||
|
MOVQ (SI), R12 \
|
||||||
|
ADDQ $8, SI \
|
||||||
|
IMULQ R14, R12 \
|
||||||
|
ADDQ R12, r \
|
||||||
|
ROLQ $31, r \
|
||||||
|
IMULQ R13, r
|
||||||
|
|
||||||
|
// mergeRound applies a merge round on the two registers acc and val.
|
||||||
|
// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
|
||||||
|
#define mergeRound(acc, val) \
|
||||||
|
IMULQ R14, val \
|
||||||
|
ROLQ $31, val \
|
||||||
|
IMULQ R13, val \
|
||||||
|
XORQ val, acc \
|
||||||
|
IMULQ R13, acc \
|
||||||
|
ADDQ DI, acc
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||||
|
// Load fixed primes.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
MOVQ ·prime4v(SB), DI
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+0(FP), SI
|
||||||
|
MOVQ b_len+8(FP), DX
|
||||||
|
LEAQ (SI)(DX*1), BX
|
||||||
|
|
||||||
|
// The first loop limit will be len(b)-32.
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Check whether we have at least one block.
|
||||||
|
CMPQ DX, $32
|
||||||
|
JLT noBlocks
|
||||||
|
|
||||||
|
// Set up initial state (v1, v2, v3, v4).
|
||||||
|
MOVQ R13, R8
|
||||||
|
ADDQ R14, R8
|
||||||
|
MOVQ R14, R9
|
||||||
|
XORQ R10, R10
|
||||||
|
XORQ R11, R11
|
||||||
|
SUBQ R13, R11
|
||||||
|
|
||||||
|
// Loop until SI > BX.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
MOVQ R8, AX
|
||||||
|
ROLQ $1, AX
|
||||||
|
MOVQ R9, R12
|
||||||
|
ROLQ $7, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R10, R12
|
||||||
|
ROLQ $12, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R11, R12
|
||||||
|
ROLQ $18, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
|
||||||
|
mergeRound(AX, R8)
|
||||||
|
mergeRound(AX, R9)
|
||||||
|
mergeRound(AX, R10)
|
||||||
|
mergeRound(AX, R11)
|
||||||
|
|
||||||
|
JMP afterBlocks
|
||||||
|
|
||||||
|
noBlocks:
|
||||||
|
MOVQ ·prime5v(SB), AX
|
||||||
|
|
||||||
|
afterBlocks:
|
||||||
|
ADDQ DX, AX
|
||||||
|
|
||||||
|
// Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
|
||||||
|
ADDQ $24, BX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JG fourByte
|
||||||
|
|
||||||
|
wordLoop:
|
||||||
|
// Calculate k1.
|
||||||
|
MOVQ (SI), R8
|
||||||
|
ADDQ $8, SI
|
||||||
|
IMULQ R14, R8
|
||||||
|
ROLQ $31, R8
|
||||||
|
IMULQ R13, R8
|
||||||
|
|
||||||
|
XORQ R8, AX
|
||||||
|
ROLQ $27, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
ADDQ DI, AX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE wordLoop
|
||||||
|
|
||||||
|
fourByte:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ SI, BX
|
||||||
|
JG singles
|
||||||
|
|
||||||
|
MOVL (SI), R8
|
||||||
|
ADDQ $4, SI
|
||||||
|
IMULQ R13, R8
|
||||||
|
XORQ R8, AX
|
||||||
|
|
||||||
|
ROLQ $23, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
ADDQ ·prime3v(SB), AX
|
||||||
|
|
||||||
|
singles:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ SI, BX
|
||||||
|
JGE finalize
|
||||||
|
|
||||||
|
singlesLoop:
|
||||||
|
MOVBQZX (SI), R12
|
||||||
|
ADDQ $1, SI
|
||||||
|
IMULQ ·prime5v(SB), R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
ROLQ $11, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JL singlesLoop
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $33, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $29, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ ·prime3v(SB), AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $32, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
MOVQ AX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||||
|
// the d pointer.
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
||||||
|
// Load fixed primes needed for round.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+8(FP), SI
|
||||||
|
MOVQ b_len+16(FP), DX
|
||||||
|
LEAQ (SI)(DX*1), BX
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Load vN from d.
|
||||||
|
MOVQ d+0(FP), AX
|
||||||
|
MOVQ 0(AX), R8 // v1
|
||||||
|
MOVQ 8(AX), R9 // v2
|
||||||
|
MOVQ 16(AX), R10 // v3
|
||||||
|
MOVQ 24(AX), R11 // v4
|
||||||
|
|
||||||
|
// We don't need to check the loop condition here; this function is
|
||||||
|
// always called with at least one block of data to process.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
// Copy vN back to d.
|
||||||
|
MOVQ R8, 0(AX)
|
||||||
|
MOVQ R9, 8(AX)
|
||||||
|
MOVQ R10, 16(AX)
|
||||||
|
MOVQ R11, 24(AX)
|
||||||
|
|
||||||
|
// The number of bytes written is SI minus the old base pointer.
|
||||||
|
SUBQ b_base+8(FP), SI
|
||||||
|
MOVQ SI, ret+32(FP)
|
||||||
|
|
||||||
|
RET
|
||||||
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// +build !amd64 appengine !gc purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
func Sum64(b []byte) uint64 {
|
||||||
|
// A simpler version would be
|
||||||
|
// d := New()
|
||||||
|
// d.Write(b)
|
||||||
|
// return d.Sum64()
|
||||||
|
// but this is faster, particularly for small inputs.
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if n >= 32 {
|
||||||
|
v1 := prime1v + prime2
|
||||||
|
v2 := prime2
|
||||||
|
v3 := uint64(0)
|
||||||
|
v4 := -prime1v
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += uint64(n)
|
||||||
|
|
||||||
|
i, end := 0, len(b)
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for ; i < end; i++ {
|
||||||
|
h ^= uint64(b[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBlocks(d *Digest, b []byte) int {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
n := len(b)
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
||||||
|
return n - len(b)
|
||||||
|
}
|
||||||
15
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
15
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
return Sum64([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
return d.Write([]byte(s))
|
||||||
|
}
|
||||||
57
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
57
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
// This file encapsulates usage of unsafe.
|
||||||
|
// xxhash_safe.go contains the safe implementations.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// In the future it's possible that compiler optimizations will make these
|
||||||
|
// XxxString functions unnecessary by realizing that calls such as
|
||||||
|
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
|
||||||
|
// If that happens, even if we keep these functions they can be replaced with
|
||||||
|
// the trivial safe code.
|
||||||
|
|
||||||
|
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
|
||||||
|
//
|
||||||
|
// var b []byte
|
||||||
|
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||||
|
// bh.Len = len(s)
|
||||||
|
// bh.Cap = len(s)
|
||||||
|
//
|
||||||
|
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
|
||||||
|
// weight to this sequence of expressions that any function that uses it will
|
||||||
|
// not be inlined. Instead, the functions below use a different unsafe
|
||||||
|
// conversion designed to minimize the inliner weight and allow both to be
|
||||||
|
// inlined. There is also a test (TestInlining) which verifies that these are
|
||||||
|
// inlined.
|
||||||
|
//
|
||||||
|
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||||
|
return Sum64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
|
||||||
|
// d.Write always returns len(s), nil.
|
||||||
|
// Ignoring the return output and returning these fixed values buys a
|
||||||
|
// savings of 6 in the inliner's cost model.
|
||||||
|
return len(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
|
||||||
|
// of the first two words is the same as the layout of a string.
|
||||||
|
type sliceHeader struct {
|
||||||
|
s string
|
||||||
|
cap int
|
||||||
|
}
|
||||||
21
vendor/github.com/dgryski/go-rendezvous/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dgryski/go-rendezvous/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||||
79
vendor/github.com/dgryski/go-rendezvous/rdv.go
generated
vendored
Normal file
79
vendor/github.com/dgryski/go-rendezvous/rdv.go
generated
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
3
vendor/github.com/go-redis/redis/v8/.gitignore
generated
vendored
Normal file
3
vendor/github.com/go-redis/redis/v8/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.rdb
|
||||||
|
testdata/*/
|
||||||
|
.idea/
|
||||||
27
vendor/github.com/go-redis/redis/v8/.golangci.yml
generated
vendored
Normal file
27
vendor/github.com/go-redis/redis/v8/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
run:
|
||||||
|
concurrency: 8
|
||||||
|
deadline: 5m
|
||||||
|
tests: false
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- funlen
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- godox
|
||||||
|
- gosec
|
||||||
|
- maligned
|
||||||
|
- wsl
|
||||||
|
- gomnd
|
||||||
|
- goerr113
|
||||||
|
- exhaustive
|
||||||
|
- nestif
|
||||||
|
- nlreturn
|
||||||
|
- exhaustivestruct
|
||||||
|
- wrapcheck
|
||||||
|
- errorlint
|
||||||
|
- cyclop
|
||||||
|
- forcetypeassert
|
||||||
|
- forbidigo
|
||||||
4
vendor/github.com/go-redis/redis/v8/.prettierrc.yml
generated
vendored
Normal file
4
vendor/github.com/go-redis/redis/v8/.prettierrc.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
semi: false
|
||||||
|
singleQuote: true
|
||||||
|
proseWrap: always
|
||||||
|
printWidth: 100
|
||||||
149
vendor/github.com/go-redis/redis/v8/CHANGELOG.md
generated
vendored
Normal file
149
vendor/github.com/go-redis/redis/v8/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
## [8.11.4](https://github.com/go-redis/redis/compare/v8.11.3...v8.11.4) (2021-10-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add acl auth support for sentinels ([f66582f](https://github.com/go-redis/redis/commit/f66582f44f3dc3a4705a5260f982043fde4aa634))
|
||||||
|
* add Cmd.{String,Int,Float,Bool}Slice helpers and an example ([5d3d293](https://github.com/go-redis/redis/commit/5d3d293cc9c60b90871e2420602001463708ce24))
|
||||||
|
* add SetVal method for each command ([168981d](https://github.com/go-redis/redis/commit/168981da2d84ee9e07d15d3e74d738c162e264c4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## v8.11
|
||||||
|
|
||||||
|
- Remove OpenTelemetry metrics.
|
||||||
|
- Supports more redis commands and options.
|
||||||
|
|
||||||
|
## v8.10
|
||||||
|
|
||||||
|
- Removed extra OpenTelemetry spans from go-redis core. Now go-redis instrumentation only adds a
|
||||||
|
single span with a Redis command (instead of 4 spans). There are multiple reasons behind this
|
||||||
|
decision:
|
||||||
|
|
||||||
|
- Traces become smaller and less noisy.
|
||||||
|
- It may be costly to process those 3 extra spans for each query.
|
||||||
|
- go-redis no longer depends on OpenTelemetry.
|
||||||
|
|
||||||
|
Eventually we hope to replace the information that we no longer collect with OpenTelemetry
|
||||||
|
Metrics.
|
||||||
|
|
||||||
|
## v8.9
|
||||||
|
|
||||||
|
- Changed `PubSub.Channel` to only rely on `Ping` result. You can now use `WithChannelSize`,
|
||||||
|
`WithChannelHealthCheckInterval`, and `WithChannelSendTimeout` to override default settings.
|
||||||
|
|
||||||
|
## v8.8
|
||||||
|
|
||||||
|
- To make updating easier, extra modules now have the same version as go-redis does. That means that
|
||||||
|
you need to update your imports:
|
||||||
|
|
||||||
|
```
|
||||||
|
github.com/go-redis/redis/extra/redisotel -> github.com/go-redis/redis/extra/redisotel/v8
|
||||||
|
github.com/go-redis/redis/extra/rediscensus -> github.com/go-redis/redis/extra/rediscensus/v8
|
||||||
|
```
|
||||||
|
|
||||||
|
## v8.5
|
||||||
|
|
||||||
|
- [knadh](https://github.com/knadh) contributed long-awaited ability to scan Redis Hash into a
|
||||||
|
struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := rdb.HGetAll(ctx, "hash").Scan(&data)
|
||||||
|
|
||||||
|
err := rdb.MGet(ctx, "key1", "key2").Scan(&data)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Please check [redismock](https://github.com/go-redis/redismock) by
|
||||||
|
[monkey92t](https://github.com/monkey92t) if you are looking for mocking Redis Client.
|
||||||
|
|
||||||
|
## v8
|
||||||
|
|
||||||
|
- All commands require `context.Context` as a first argument, e.g. `rdb.Ping(ctx)`. If you are not
|
||||||
|
using `context.Context` yet, the simplest option is to define global package variable
|
||||||
|
`var ctx = context.TODO()` and use it when `ctx` is required.
|
||||||
|
|
||||||
|
- Full support for `context.Context` canceling.
|
||||||
|
|
||||||
|
- Added `redis.NewFailoverClusterClient` that supports routing read-only commands to a slave node.
|
||||||
|
|
||||||
|
- Added `redisext.OpenTemetryHook` that adds
|
||||||
|
[Redis OpenTelemetry instrumentation](https://redis.uptrace.dev/tracing/).
|
||||||
|
|
||||||
|
- Redis slow log support.
|
||||||
|
|
||||||
|
- Ring uses Rendezvous Hashing by default which provides better distribution. You need to move
|
||||||
|
existing keys to a new location or keys will be inaccessible / lost. To use old hashing scheme:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/golang/groupcache/consistenthash"
|
||||||
|
|
||||||
|
ring := redis.NewRing(&redis.RingOptions{
|
||||||
|
NewConsistentHash: func() {
|
||||||
|
return consistenthash.New(100, crc32.ChecksumIEEE)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ClusterOptions.MaxRedirects` default value is changed from 8 to 3.
|
||||||
|
- `Options.MaxRetries` default value is changed from 0 to 3.
|
||||||
|
|
||||||
|
- `Cluster.ForEachNode` is renamed to `ForEachShard` for consistency with `Ring`.
|
||||||
|
|
||||||
|
## v7.3
|
||||||
|
|
||||||
|
- New option `Options.Username` which causes client to use `AuthACL`. Be aware if your connection
|
||||||
|
URL contains username.
|
||||||
|
|
||||||
|
## 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
|
||||||
25
vendor/github.com/go-redis/redis/v8/LICENSE
generated
vendored
Normal file
25
vendor/github.com/go-redis/redis/v8/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013 The github.com/go-redis/redis Authors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
35
vendor/github.com/go-redis/redis/v8/Makefile
generated
vendored
Normal file
35
vendor/github.com/go-redis/redis/v8/Makefile
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
PACKAGE_DIRS := $(shell find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||||
|
|
||||||
|
test: testdeps
|
||||||
|
go test ./...
|
||||||
|
go test ./... -short -race
|
||||||
|
go test ./... -run=NONE -bench=. -benchmem
|
||||||
|
env GOOS=linux GOARCH=386 go test ./...
|
||||||
|
go vet
|
||||||
|
|
||||||
|
testdeps: testdata/redis/src/redis-server
|
||||||
|
|
||||||
|
bench: testdeps
|
||||||
|
go test ./... -test.run=NONE -test.bench=. -test.benchmem
|
||||||
|
|
||||||
|
.PHONY: all test testdeps bench
|
||||||
|
|
||||||
|
testdata/redis:
|
||||||
|
mkdir -p $@
|
||||||
|
wget -qO- https://download.redis.io/releases/redis-6.2.5.tar.gz | tar xvz --strip-components=1 -C $@
|
||||||
|
|
||||||
|
testdata/redis/src/redis-server: testdata/redis
|
||||||
|
cd $< && make all
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w -s ./
|
||||||
|
goimports -w -local github.com/go-redis/redis ./
|
||||||
|
|
||||||
|
go_mod_tidy:
|
||||||
|
go get -u && go mod tidy
|
||||||
|
set -e; for dir in $(PACKAGE_DIRS); do \
|
||||||
|
echo "go mod tidy in $${dir}"; \
|
||||||
|
(cd "$${dir}" && \
|
||||||
|
go get -u && \
|
||||||
|
go mod tidy); \
|
||||||
|
done
|
||||||
178
vendor/github.com/go-redis/redis/v8/README.md
generated
vendored
Normal file
178
vendor/github.com/go-redis/redis/v8/README.md
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://uptrace.dev/?utm_source=gh-redis&utm_campaign=gh-redis-banner1">
|
||||||
|
<img src="https://raw.githubusercontent.com/uptrace/roadmap/master/banner1.png" alt="All-in-one tool to optimize performance and monitor errors & logs">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Redis client for Golang
|
||||||
|
|
||||||
|

|
||||||
|
[](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc)
|
||||||
|
[](https://redis.uptrace.dev/)
|
||||||
|
[](https://discord.gg/rWtp5Aj)
|
||||||
|
|
||||||
|
- To ask questions, join [Discord](https://discord.gg/rWtp5Aj) or use
|
||||||
|
[Discussions](https://github.com/go-redis/redis/discussions).
|
||||||
|
- [Newsletter](https://blog.uptrace.dev/pages/newsletter.html) to get latest updates.
|
||||||
|
- [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)
|
||||||
|
|
||||||
|
Other projects you may like:
|
||||||
|
|
||||||
|
- [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite.
|
||||||
|
- [treemux](https://github.com/vmihailenco/treemux) - high-speed, flexible, tree-based HTTP router
|
||||||
|
for Go.
|
||||||
|
|
||||||
|
## 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 defined 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
Thanks to all the people who already contributed!
|
||||||
|
|
||||||
|
<a href="https://github.com/go-redis/redis/graphs/contributors">
|
||||||
|
<img src="https://contributors-img.web.app/image?repo=go-redis/redis" />
|
||||||
|
</a>
|
||||||
15
vendor/github.com/go-redis/redis/v8/RELEASING.md
generated
vendored
Normal file
15
vendor/github.com/go-redis/redis/v8/RELEASING.md
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Releasing
|
||||||
|
|
||||||
|
1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
TAG=v1.0.0 ./scripts/release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open a pull request and wait for the build to finish.
|
||||||
|
|
||||||
|
3. Merge the pull request and run `tag.sh` to create tags for packages:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
TAG=v1.0.0 ./scripts/tag.sh
|
||||||
|
```
|
||||||
1750
vendor/github.com/go-redis/redis/v8/cluster.go
generated
vendored
Normal file
1750
vendor/github.com/go-redis/redis/v8/cluster.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
109
vendor/github.com/go-redis/redis/v8/cluster_commands.go
generated
vendored
Normal file
109
vendor/github.com/go-redis/redis/v8/cluster_commands.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd {
|
||||||
|
cmd := NewIntCmd(ctx, "dbsize")
|
||||||
|
_ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
cmd.val = size
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd {
|
||||||
|
cmd := NewStringCmd(ctx, "script", "load", script)
|
||||||
|
_ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||||
|
val, err := shard.ScriptLoad(ctx, script).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
if cmd.Val() == "" {
|
||||||
|
cmd.val = val
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cmd.SetErr(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd {
|
||||||
|
cmd := NewStatusCmd(ctx, "script", "flush")
|
||||||
|
_ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||||
|
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||||
|
return shard.ScriptFlush(ctx).Err()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cmd.SetErr(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd {
|
||||||
|
args := make([]interface{}, 2+len(hashes))
|
||||||
|
args[0] = "script"
|
||||||
|
args[1] = "exists"
|
||||||
|
for i, hash := range hashes {
|
||||||
|
args[2+i] = hash
|
||||||
|
}
|
||||||
|
cmd := NewBoolSliceCmd(ctx, args...)
|
||||||
|
|
||||||
|
result := make([]bool, len(hashes))
|
||||||
|
for i := range result {
|
||||||
|
result[i] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error {
|
||||||
|
mu := &sync.Mutex{}
|
||||||
|
err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error {
|
||||||
|
val, err := shard.ScriptExists(ctx, hashes...).Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
for i, v := range val {
|
||||||
|
result[i] = result[i] && v
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cmd.SetErr(err)
|
||||||
|
} else {
|
||||||
|
cmd.val = result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
3472
vendor/github.com/go-redis/redis/v8/command.go
generated
vendored
Normal file
3472
vendor/github.com/go-redis/redis/v8/command.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3429
vendor/github.com/go-redis/redis/v8/commands.go
generated
vendored
Normal file
3429
vendor/github.com/go-redis/redis/v8/commands.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
vendor/github.com/go-redis/redis/v8/doc.go
generated
vendored
Normal file
4
vendor/github.com/go-redis/redis/v8/doc.go
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package redis implements a Redis client.
|
||||||
|
*/
|
||||||
|
package redis
|
||||||
144
vendor/github.com/go-redis/redis/v8/error.go
generated
vendored
Normal file
144
vendor/github.com/go-redis/redis/v8/error.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
"github.com/go-redis/redis/v8/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrClosed performs any operation on the closed client will return this error.
|
||||||
|
var ErrClosed = pool.ErrClosed
|
||||||
|
|
||||||
|
type Error interface {
|
||||||
|
error
|
||||||
|
|
||||||
|
// RedisError is a no-op function but
|
||||||
|
// serves to distinguish types that are Redis
|
||||||
|
// errors from ordinary errors: a type is a
|
||||||
|
// Redis error if it has a RedisError method.
|
||||||
|
RedisError()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Error = proto.RedisError("")
|
||||||
|
|
||||||
|
func shouldRetry(err error, retryTimeout bool) bool {
|
||||||
|
switch err {
|
||||||
|
case io.EOF, io.ErrUnexpectedEOF:
|
||||||
|
return true
|
||||||
|
case nil, context.Canceled, context.DeadlineExceeded:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := err.(timeoutError); ok {
|
||||||
|
if v.Timeout() {
|
||||||
|
return retryTimeout
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
if s == "ERR max number of clients reached" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "LOADING ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "READONLY ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "TRYAGAIN ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRedisError(err error) bool {
|
||||||
|
_, ok := err.(proto.RedisError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBadConn(err error, allowTimeout bool, addr string) bool {
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return false
|
||||||
|
case context.Canceled, context.DeadlineExceeded:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRedisError(err) {
|
||||||
|
switch {
|
||||||
|
case isReadOnlyError(err):
|
||||||
|
// Close connections in read only state in case domain addr is used
|
||||||
|
// and domain resolves to a different Redis Server. See #790.
|
||||||
|
return true
|
||||||
|
case isMovedSameConnAddr(err, addr):
|
||||||
|
// Close connections when we are asked to move to the same addr
|
||||||
|
// of the connection. Force a DNS resolution when all connections
|
||||||
|
// of the pool are recycled
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowTimeout {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
return !netErr.Temporary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMovedError(err error) (moved bool, ask bool, addr string) {
|
||||||
|
if !isRedisError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(s, "MOVED "):
|
||||||
|
moved = true
|
||||||
|
case strings.HasPrefix(s, "ASK "):
|
||||||
|
ask = true
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ind := strings.LastIndex(s, " ")
|
||||||
|
if ind == -1 {
|
||||||
|
return false, false, ""
|
||||||
|
}
|
||||||
|
addr = s[ind+1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLoadingError(err error) bool {
|
||||||
|
return strings.HasPrefix(err.Error(), "LOADING ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isReadOnlyError(err error) bool {
|
||||||
|
return strings.HasPrefix(err.Error(), "READONLY ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMovedSameConnAddr(err error, addr string) bool {
|
||||||
|
redisError := err.Error()
|
||||||
|
if !strings.HasPrefix(redisError, "MOVED ") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasSuffix(redisError, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type timeoutError interface {
|
||||||
|
Timeout() bool
|
||||||
|
}
|
||||||
11
vendor/github.com/go-redis/redis/v8/go.mod
generated
vendored
Normal file
11
vendor/github.com/go-redis/redis/v8/go.mod
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/go-redis/redis/v8
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
|
||||||
|
github.com/google/go-cmp v0.5.6 // indirect
|
||||||
|
github.com/onsi/ginkgo v1.16.4
|
||||||
|
github.com/onsi/gomega v1.16.0
|
||||||
|
)
|
||||||
100
vendor/github.com/go-redis/redis/v8/go.sum
generated
vendored
Normal file
100
vendor/github.com/go-redis/redis/v8/go.sum
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/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/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
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.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
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.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||||
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
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-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
|
||||||
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
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/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
56
vendor/github.com/go-redis/redis/v8/internal/arg.go
generated
vendored
Normal file
56
vendor/github.com/go-redis/redis/v8/internal/arg.go
generated
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
78
vendor/github.com/go-redis/redis/v8/internal/hashtag/hashtag.go
generated
vendored
Normal file
78
vendor/github.com/go-redis/redis/v8/internal/hashtag/hashtag.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package hashtag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
const slotNumber = 16384
|
||||||
|
|
||||||
|
// CRC16 implementation according to CCITT standards.
|
||||||
|
// Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c
|
||||||
|
var crc16tab = [256]uint16{
|
||||||
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||||
|
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
||||||
|
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
||||||
|
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
||||||
|
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
||||||
|
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
||||||
|
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
||||||
|
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
||||||
|
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||||
|
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
||||||
|
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
||||||
|
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
||||||
|
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
||||||
|
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
||||||
|
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
||||||
|
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
||||||
|
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
||||||
|
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||||
|
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
||||||
|
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||||
|
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
||||||
|
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||||
|
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
||||||
|
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||||
|
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
||||||
|
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
||||||
|
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
||||||
|
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
||||||
|
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
||||||
|
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||||
|
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
||||||
|
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key(key string) string {
|
||||||
|
if s := strings.IndexByte(key, '{'); s > -1 {
|
||||||
|
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
||||||
|
return key[s+1 : s+e+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomSlot() int {
|
||||||
|
return rand.Intn(slotNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns a consistent slot number between 0 and 16383
|
||||||
|
// for any given string key.
|
||||||
|
func Slot(key string) int {
|
||||||
|
if key == "" {
|
||||||
|
return RandomSlot()
|
||||||
|
}
|
||||||
|
key = Key(key)
|
||||||
|
return int(crc16sum(key)) % slotNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc16sum(key string) (crc uint16) {
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
201
vendor/github.com/go-redis/redis/v8/internal/hscan/hscan.go
generated
vendored
Normal file
201
vendor/github.com/go-redis/redis/v8/internal/hscan/hscan.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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: decodeInt8,
|
||||||
|
reflect.Int16: decodeInt16,
|
||||||
|
reflect.Int32: decodeInt32,
|
||||||
|
reflect.Int64: decodeInt64,
|
||||||
|
reflect.Uint: decodeUint,
|
||||||
|
reflect.Uint8: decodeUint8,
|
||||||
|
reflect.Uint16: decodeUint16,
|
||||||
|
reflect.Uint32: decodeUint32,
|
||||||
|
reflect.Uint64: decodeUint64,
|
||||||
|
reflect.Float32: decodeFloat32,
|
||||||
|
reflect.Float64: decodeFloat64,
|
||||||
|
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 destination 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 decodeInt8(f reflect.Value, s string) error {
|
||||||
|
return decodeNumber(f, s, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInt16(f reflect.Value, s string) error {
|
||||||
|
return decodeNumber(f, s, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInt32(f reflect.Value, s string) error {
|
||||||
|
return decodeNumber(f, s, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInt64(f reflect.Value, s string) error {
|
||||||
|
return decodeNumber(f, s, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInt(f reflect.Value, s string) error {
|
||||||
|
return decodeNumber(f, s, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNumber(f reflect.Value, s string, bitSize int) error {
|
||||||
|
v, err := strconv.ParseInt(s, 10, bitSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.SetInt(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint8(f reflect.Value, s string) error {
|
||||||
|
return decodeUnsignedNumber(f, s, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint16(f reflect.Value, s string) error {
|
||||||
|
return decodeUnsignedNumber(f, s, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint32(f reflect.Value, s string) error {
|
||||||
|
return decodeUnsignedNumber(f, s, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint64(f reflect.Value, s string) error {
|
||||||
|
return decodeUnsignedNumber(f, s, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUint(f reflect.Value, s string) error {
|
||||||
|
return decodeUnsignedNumber(f, s, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error {
|
||||||
|
v, err := strconv.ParseUint(s, 10, bitSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.SetUint(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeFloat32(f reflect.Value, s string) error {
|
||||||
|
v, err := strconv.ParseFloat(s, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.SetFloat(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// although the default is float64, but we better define it.
|
||||||
|
func decodeFloat64(f reflect.Value, s string) error {
|
||||||
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
|
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())
|
||||||
|
}
|
||||||
93
vendor/github.com/go-redis/redis/v8/internal/hscan/structmap.go
generated
vendored
Normal file
93
vendor/github.com/go-redis/redis/v8/internal/hscan/structmap.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package hscan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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 {
|
||||||
|
numField := t.NumField()
|
||||||
|
out := &structSpec{
|
||||||
|
m: make(map[string]*structField, numField),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numField; 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
|
||||||
|
}
|
||||||
|
if err := field.fn(s.value.Field(field.index), value); err != nil {
|
||||||
|
t := s.value.Type()
|
||||||
|
return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s",
|
||||||
|
value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
29
vendor/github.com/go-redis/redis/v8/internal/internal.go
generated
vendored
Normal file
29
vendor/github.com/go-redis/redis/v8/internal/internal.go
generated
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
26
vendor/github.com/go-redis/redis/v8/internal/log.go
generated
vendored
Normal file
26
vendor/github.com/go-redis/redis/v8/internal/log.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger calls Output to print to the stderr.
|
||||||
|
// Arguments are handled in the manner of fmt.Print.
|
||||||
|
var Logger Logging = &logger{
|
||||||
|
log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile),
|
||||||
|
}
|
||||||
60
vendor/github.com/go-redis/redis/v8/internal/once.go
generated
vendored
Normal file
60
vendor/github.com/go-redis/redis/v8/internal/once.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Camlistore 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 (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Once will perform a successful action exactly once.
|
||||||
|
//
|
||||||
|
// Unlike a sync.Once, this Once's func returns an error
|
||||||
|
// and is re-armed on failure.
|
||||||
|
type Once struct {
|
||||||
|
m sync.Mutex
|
||||||
|
done uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do calls the function f if and only if Do has not been invoked
|
||||||
|
// without error for this instance of Once. In other words, given
|
||||||
|
// var once Once
|
||||||
|
// if once.Do(f) is called multiple times, only the first call will
|
||||||
|
// invoke f, even if f has a different value in each invocation unless
|
||||||
|
// f returns an error. A new instance of Once is required for each
|
||||||
|
// function to execute.
|
||||||
|
//
|
||||||
|
// Do is intended for initialization that must be run exactly once. Since f
|
||||||
|
// is niladic, it may be necessary to use a function literal to capture the
|
||||||
|
// arguments to a function to be invoked by Do:
|
||||||
|
// err := config.once.Do(func() error { return config.init(filename) })
|
||||||
|
func (o *Once) Do(f func() error) error {
|
||||||
|
if atomic.LoadUint32(&o.done) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Slow-path.
|
||||||
|
o.m.Lock()
|
||||||
|
defer o.m.Unlock()
|
||||||
|
var err error
|
||||||
|
if o.done == 0 {
|
||||||
|
err = f()
|
||||||
|
if err == nil {
|
||||||
|
atomic.StoreUint32(&o.done, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
121
vendor/github.com/go-redis/redis/v8/internal/pool/conn.go
generated
vendored
Normal file
121
vendor/github.com/go-redis/redis/v8/internal/pool/conn.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noDeadline = time.Time{}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
usedAt int64 // atomic
|
||||||
|
netConn net.Conn
|
||||||
|
|
||||||
|
rd *proto.Reader
|
||||||
|
bw *bufio.Writer
|
||||||
|
wr *proto.Writer
|
||||||
|
|
||||||
|
Inited bool
|
||||||
|
pooled bool
|
||||||
|
createdAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(netConn net.Conn) *Conn {
|
||||||
|
cn := &Conn{
|
||||||
|
netConn: netConn,
|
||||||
|
createdAt: time.Now(),
|
||||||
|
}
|
||||||
|
cn.rd = proto.NewReader(netConn)
|
||||||
|
cn.bw = bufio.NewWriter(netConn)
|
||||||
|
cn.wr = proto.NewWriter(cn.bw)
|
||||||
|
cn.SetUsedAt(time.Now())
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) UsedAt() time.Time {
|
||||||
|
unix := atomic.LoadInt64(&cn.usedAt)
|
||||||
|
return time.Unix(unix, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetUsedAt(tm time.Time) {
|
||||||
|
atomic.StoreInt64(&cn.usedAt, tm.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetNetConn(netConn net.Conn) {
|
||||||
|
cn.netConn = netConn
|
||||||
|
cn.rd.Reset(netConn)
|
||||||
|
cn.bw.Reset(netConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Write(b []byte) (int, error) {
|
||||||
|
return cn.netConn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) RemoteAddr() net.Addr {
|
||||||
|
if cn.netConn != nil {
|
||||||
|
return cn.netConn.RemoteAddr()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) WithReader(ctx context.Context, timeout time.Duration, fn func(rd *proto.Reader) error) error {
|
||||||
|
if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn(cn.rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) WithWriter(
|
||||||
|
ctx context.Context, timeout time.Duration, fn func(wr *proto.Writer) error,
|
||||||
|
) error {
|
||||||
|
if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cn.bw.Buffered() > 0 {
|
||||||
|
cn.bw.Reset(cn.netConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := fn(cn.wr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn.bw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Close() error {
|
||||||
|
return cn.netConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) deadline(ctx context.Context, timeout time.Duration) time.Time {
|
||||||
|
tm := time.Now()
|
||||||
|
cn.SetUsedAt(tm)
|
||||||
|
|
||||||
|
if timeout > 0 {
|
||||||
|
tm = tm.Add(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
deadline, ok := ctx.Deadline()
|
||||||
|
if ok {
|
||||||
|
if timeout == 0 {
|
||||||
|
return deadline
|
||||||
|
}
|
||||||
|
if deadline.Before(tm) {
|
||||||
|
return deadline
|
||||||
|
}
|
||||||
|
return tm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout > 0 {
|
||||||
|
return tm
|
||||||
|
}
|
||||||
|
|
||||||
|
return noDeadline
|
||||||
|
}
|
||||||
557
vendor/github.com/go-redis/redis/v8/internal/pool/pool.go
generated
vendored
Normal file
557
vendor/github.com/go-redis/redis/v8/internal/pool/pool.go
generated
vendored
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrClosed performs any operation on the closed client will return this error.
|
||||||
|
ErrClosed = errors.New("redis: client is closed")
|
||||||
|
|
||||||
|
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
|
||||||
|
ErrPoolTimeout = errors.New("redis: connection pool timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
var timers = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
t := time.NewTimer(time.Hour)
|
||||||
|
t.Stop()
|
||||||
|
return t
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats contains pool state information and accumulated stats.
|
||||||
|
type Stats struct {
|
||||||
|
Hits uint32 // number of times free connection was found in the pool
|
||||||
|
Misses uint32 // number of times free connection was NOT found in the pool
|
||||||
|
Timeouts uint32 // number of times a wait timeout occurred
|
||||||
|
|
||||||
|
TotalConns uint32 // number of total connections in the pool
|
||||||
|
IdleConns uint32 // number of idle connections in the pool
|
||||||
|
StaleConns uint32 // number of stale connections removed from the pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pooler interface {
|
||||||
|
NewConn(context.Context) (*Conn, error)
|
||||||
|
CloseConn(*Conn) error
|
||||||
|
|
||||||
|
Get(context.Context) (*Conn, error)
|
||||||
|
Put(context.Context, *Conn)
|
||||||
|
Remove(context.Context, *Conn, error)
|
||||||
|
|
||||||
|
Len() int
|
||||||
|
IdleLen() int
|
||||||
|
Stats() *Stats
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Dialer func(context.Context) (net.Conn, error)
|
||||||
|
OnClose func(*Conn) error
|
||||||
|
|
||||||
|
PoolFIFO bool
|
||||||
|
PoolSize int
|
||||||
|
MinIdleConns int
|
||||||
|
MaxConnAge time.Duration
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type lastDialErrorWrap struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnPool struct {
|
||||||
|
opt *Options
|
||||||
|
|
||||||
|
dialErrorsNum uint32 // atomic
|
||||||
|
|
||||||
|
lastDialError atomic.Value
|
||||||
|
|
||||||
|
queue chan struct{}
|
||||||
|
|
||||||
|
connsMu sync.Mutex
|
||||||
|
conns []*Conn
|
||||||
|
idleConns []*Conn
|
||||||
|
poolSize int
|
||||||
|
idleConnsLen int
|
||||||
|
|
||||||
|
stats Stats
|
||||||
|
|
||||||
|
_closed uint32 // atomic
|
||||||
|
closedCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pooler = (*ConnPool)(nil)
|
||||||
|
|
||||||
|
func NewConnPool(opt *Options) *ConnPool {
|
||||||
|
p := &ConnPool{
|
||||||
|
opt: opt,
|
||||||
|
|
||||||
|
queue: make(chan struct{}, opt.PoolSize),
|
||||||
|
conns: make([]*Conn, 0, opt.PoolSize),
|
||||||
|
idleConns: make([]*Conn, 0, opt.PoolSize),
|
||||||
|
closedCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.connsMu.Lock()
|
||||||
|
p.checkMinIdleConns()
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
|
||||||
|
go p.reaper(opt.IdleCheckFrequency)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) checkMinIdleConns() {
|
||||||
|
if p.opt.MinIdleConns == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns {
|
||||||
|
p.poolSize++
|
||||||
|
p.idleConnsLen++
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := p.addIdleConn()
|
||||||
|
if err != nil && err != ErrClosed {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
p.poolSize--
|
||||||
|
p.idleConnsLen--
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) addIdleConn() error {
|
||||||
|
cn, err := p.dialConn(context.TODO(), true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.connsMu.Lock()
|
||||||
|
defer p.connsMu.Unlock()
|
||||||
|
|
||||||
|
// It is not allowed to add new connections to the closed connection pool.
|
||||||
|
if p.closed() {
|
||||||
|
_ = cn.Close()
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
p.conns = append(p.conns, cn)
|
||||||
|
p.idleConns = append(p.idleConns, cn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
||||||
|
return p.newConn(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||||
|
cn, err := p.dialConn(ctx, pooled)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.connsMu.Lock()
|
||||||
|
defer p.connsMu.Unlock()
|
||||||
|
|
||||||
|
// It is not allowed to add new connections to the closed connection pool.
|
||||||
|
if p.closed() {
|
||||||
|
_ = cn.Close()
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
p.conns = append(p.conns, cn)
|
||||||
|
if pooled {
|
||||||
|
// If pool is full remove the cn on next Put.
|
||||||
|
if p.poolSize >= p.opt.PoolSize {
|
||||||
|
cn.pooled = false
|
||||||
|
} else {
|
||||||
|
p.poolSize++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) dialConn(ctx context.Context, pooled bool) (*Conn, error) {
|
||||||
|
if p.closed() {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) {
|
||||||
|
return nil, p.getLastDialError()
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn, err := p.opt.Dialer(ctx)
|
||||||
|
if err != nil {
|
||||||
|
p.setLastDialError(err)
|
||||||
|
if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) {
|
||||||
|
go p.tryDial()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := NewConn(netConn)
|
||||||
|
cn.pooled = pooled
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) tryDial() {
|
||||||
|
for {
|
||||||
|
if p.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := p.opt.Dialer(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
p.setLastDialError(err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
||||||
|
_ = conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) setLastDialError(err error) {
|
||||||
|
p.lastDialError.Store(&lastDialErrorWrap{err: err})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) getLastDialError() error {
|
||||||
|
err, _ := p.lastDialError.Load().(*lastDialErrorWrap)
|
||||||
|
if err != nil {
|
||||||
|
return err.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns existed connection from the pool or creates a new one.
|
||||||
|
func (p *ConnPool) Get(ctx context.Context) (*Conn, error) {
|
||||||
|
if p.closed() {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.waitTurn(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
cn, err := p.popIdle()
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cn == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.isStaleConn(cn) {
|
||||||
|
_ = p.CloseConn(cn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint32(&p.stats.Hits, 1)
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint32(&p.stats.Misses, 1)
|
||||||
|
|
||||||
|
newcn, err := p.newConn(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
p.freeTurn()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newcn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) getTurn() {
|
||||||
|
p.queue <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) waitTurn(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p.queue <- struct{}{}:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
timer := timers.Get().(*time.Timer)
|
||||||
|
timer.Reset(p.opt.PoolTimeout)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
timers.Put(timer)
|
||||||
|
return ctx.Err()
|
||||||
|
case p.queue <- struct{}{}:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
timers.Put(timer)
|
||||||
|
return nil
|
||||||
|
case <-timer.C:
|
||||||
|
timers.Put(timer)
|
||||||
|
atomic.AddUint32(&p.stats.Timeouts, 1)
|
||||||
|
return ErrPoolTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) freeTurn() {
|
||||||
|
<-p.queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) popIdle() (*Conn, error) {
|
||||||
|
if p.closed() {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
n := len(p.idleConns)
|
||||||
|
if n == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cn *Conn
|
||||||
|
if p.opt.PoolFIFO {
|
||||||
|
cn = p.idleConns[0]
|
||||||
|
copy(p.idleConns, p.idleConns[1:])
|
||||||
|
p.idleConns = p.idleConns[:n-1]
|
||||||
|
} else {
|
||||||
|
idx := n - 1
|
||||||
|
cn = p.idleConns[idx]
|
||||||
|
p.idleConns = p.idleConns[:idx]
|
||||||
|
}
|
||||||
|
p.idleConnsLen--
|
||||||
|
p.checkMinIdleConns()
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Put(ctx context.Context, cn *Conn) {
|
||||||
|
if cn.rd.Buffered() > 0 {
|
||||||
|
internal.Logger.Printf(ctx, "Conn has unread data")
|
||||||
|
p.Remove(ctx, cn, BadConnError{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cn.pooled {
|
||||||
|
p.Remove(ctx, cn, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.connsMu.Lock()
|
||||||
|
p.idleConns = append(p.idleConns, cn)
|
||||||
|
p.idleConnsLen++
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
p.freeTurn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Remove(ctx context.Context, cn *Conn, reason error) {
|
||||||
|
p.removeConnWithLock(cn)
|
||||||
|
p.freeTurn()
|
||||||
|
_ = p.closeConn(cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) CloseConn(cn *Conn) error {
|
||||||
|
p.removeConnWithLock(cn)
|
||||||
|
return p.closeConn(cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) removeConnWithLock(cn *Conn) {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
p.removeConn(cn)
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) removeConn(cn *Conn) {
|
||||||
|
for i, c := range p.conns {
|
||||||
|
if c == cn {
|
||||||
|
p.conns = append(p.conns[:i], p.conns[i+1:]...)
|
||||||
|
if cn.pooled {
|
||||||
|
p.poolSize--
|
||||||
|
p.checkMinIdleConns()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) closeConn(cn *Conn) error {
|
||||||
|
if p.opt.OnClose != nil {
|
||||||
|
_ = p.opt.OnClose(cn)
|
||||||
|
}
|
||||||
|
return cn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns total number of connections.
|
||||||
|
func (p *ConnPool) Len() int {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
n := len(p.conns)
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdleLen returns number of idle connections.
|
||||||
|
func (p *ConnPool) IdleLen() int {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
n := p.idleConnsLen
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Stats() *Stats {
|
||||||
|
idleLen := p.IdleLen()
|
||||||
|
return &Stats{
|
||||||
|
Hits: atomic.LoadUint32(&p.stats.Hits),
|
||||||
|
Misses: atomic.LoadUint32(&p.stats.Misses),
|
||||||
|
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
||||||
|
|
||||||
|
TotalConns: uint32(p.Len()),
|
||||||
|
IdleConns: uint32(idleLen),
|
||||||
|
StaleConns: atomic.LoadUint32(&p.stats.StaleConns),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) closed() bool {
|
||||||
|
return atomic.LoadUint32(&p._closed) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Filter(fn func(*Conn) bool) error {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
defer p.connsMu.Unlock()
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for _, cn := range p.conns {
|
||||||
|
if fn(cn) {
|
||||||
|
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Close() error {
|
||||||
|
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
close(p.closedCh)
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
p.connsMu.Lock()
|
||||||
|
for _, cn := range p.conns {
|
||||||
|
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.conns = nil
|
||||||
|
p.poolSize = 0
|
||||||
|
p.idleConns = nil
|
||||||
|
p.idleConnsLen = 0
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) reaper(frequency time.Duration) {
|
||||||
|
ticker := time.NewTicker(frequency)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
// It is possible that ticker and closedCh arrive together,
|
||||||
|
// and select pseudo-randomly pick ticker case, we double
|
||||||
|
// check here to prevent being executed after closed.
|
||||||
|
if p.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err := p.ReapStaleConns()
|
||||||
|
if err != nil {
|
||||||
|
internal.Logger.Printf(context.Background(), "ReapStaleConns failed: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case <-p.closedCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) ReapStaleConns() (int, error) {
|
||||||
|
var n int
|
||||||
|
for {
|
||||||
|
p.getTurn()
|
||||||
|
|
||||||
|
p.connsMu.Lock()
|
||||||
|
cn := p.reapStaleConn()
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
p.freeTurn()
|
||||||
|
|
||||||
|
if cn != nil {
|
||||||
|
_ = p.closeConn(cn)
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&p.stats.StaleConns, uint32(n))
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) reapStaleConn() *Conn {
|
||||||
|
if len(p.idleConns) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := p.idleConns[0]
|
||||||
|
if !p.isStaleConn(cn) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.idleConns = append(p.idleConns[:0], p.idleConns[1:]...)
|
||||||
|
p.idleConnsLen--
|
||||||
|
p.removeConn(cn)
|
||||||
|
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) isStaleConn(cn *Conn) bool {
|
||||||
|
if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if p.opt.IdleTimeout > 0 && now.Sub(cn.UsedAt()) >= p.opt.IdleTimeout {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.opt.MaxConnAge > 0 && now.Sub(cn.createdAt) >= p.opt.MaxConnAge {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
58
vendor/github.com/go-redis/redis/v8/internal/pool/pool_single.go
generated
vendored
Normal file
58
vendor/github.com/go-redis/redis/v8/internal/pool/pool_single.go
generated
vendored
Normal file
@@ -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{}
|
||||||
|
}
|
||||||
201
vendor/github.com/go-redis/redis/v8/internal/pool/pool_sticky.go
generated
vendored
Normal file
201
vendor/github.com/go-redis/redis/v8/internal/pool/pool_sticky.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stateDefault = 0
|
||||||
|
stateInited = 1
|
||||||
|
stateClosed = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type BadConnError struct {
|
||||||
|
wrapped error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*BadConnError)(nil)
|
||||||
|
|
||||||
|
func (e BadConnError) Error() string {
|
||||||
|
s := "redis: Conn is in a bad state"
|
||||||
|
if e.wrapped != nil {
|
||||||
|
s += ": " + e.wrapped.Error()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e BadConnError) Unwrap() error {
|
||||||
|
return e.wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type StickyConnPool struct {
|
||||||
|
pool Pooler
|
||||||
|
shared int32 // atomic
|
||||||
|
|
||||||
|
state uint32 // atomic
|
||||||
|
ch chan *Conn
|
||||||
|
|
||||||
|
_badConnError atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pooler = (*StickyConnPool)(nil)
|
||||||
|
|
||||||
|
func NewStickyConnPool(pool Pooler) *StickyConnPool {
|
||||||
|
p, ok := pool.(*StickyConnPool)
|
||||||
|
if !ok {
|
||||||
|
p = &StickyConnPool{
|
||||||
|
pool: pool,
|
||||||
|
ch: make(chan *Conn, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.AddInt32(&p.shared, 1)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) NewConn(ctx context.Context) (*Conn, error) {
|
||||||
|
return p.pool.NewConn(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) CloseConn(cn *Conn) error {
|
||||||
|
return p.pool.CloseConn(cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Get(ctx context.Context) (*Conn, error) {
|
||||||
|
// In worst case this races with Close which is not a very common operation.
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
switch atomic.LoadUint32(&p.state) {
|
||||||
|
case stateDefault:
|
||||||
|
cn, err := p.pool.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapUint32(&p.state, stateDefault, stateInited) {
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
p.pool.Remove(ctx, cn, ErrClosed)
|
||||||
|
case stateInited:
|
||||||
|
if err := p.badConnError(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cn, ok := <-p.ch
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
return cn, nil
|
||||||
|
case stateClosed:
|
||||||
|
return nil, ErrClosed
|
||||||
|
default:
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("redis: StickyConnPool.Get: infinite loop")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Put(ctx context.Context, cn *Conn) {
|
||||||
|
defer func() {
|
||||||
|
if recover() != nil {
|
||||||
|
p.freeConn(ctx, cn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
p.ch <- cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) freeConn(ctx context.Context, cn *Conn) {
|
||||||
|
if err := p.badConnError(); err != nil {
|
||||||
|
p.pool.Remove(ctx, cn, err)
|
||||||
|
} else {
|
||||||
|
p.pool.Put(ctx, cn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Remove(ctx context.Context, cn *Conn, reason error) {
|
||||||
|
defer func() {
|
||||||
|
if recover() != nil {
|
||||||
|
p.pool.Remove(ctx, cn, ErrClosed)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
p._badConnError.Store(BadConnError{wrapped: reason})
|
||||||
|
p.ch <- cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Close() error {
|
||||||
|
if shared := atomic.AddInt32(&p.shared, -1); shared > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
state := atomic.LoadUint32(&p.state)
|
||||||
|
if state == stateClosed {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapUint32(&p.state, state, stateClosed) {
|
||||||
|
close(p.ch)
|
||||||
|
cn, ok := <-p.ch
|
||||||
|
if ok {
|
||||||
|
p.freeConn(context.TODO(), cn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("redis: StickyConnPool.Close: infinite loop")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Reset(ctx context.Context) error {
|
||||||
|
if p.badConnError() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case cn, ok := <-p.ch:
|
||||||
|
if !ok {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
p.pool.Remove(ctx, cn, ErrClosed)
|
||||||
|
p._badConnError.Store(BadConnError{wrapped: nil})
|
||||||
|
default:
|
||||||
|
return errors.New("redis: StickyConnPool does not have a Conn")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !atomic.CompareAndSwapUint32(&p.state, stateInited, stateDefault) {
|
||||||
|
state := atomic.LoadUint32(&p.state)
|
||||||
|
return fmt.Errorf("redis: invalid StickyConnPool state: %d", state)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) badConnError() error {
|
||||||
|
if v := p._badConnError.Load(); v != nil {
|
||||||
|
if err := v.(BadConnError); err.wrapped != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Len() int {
|
||||||
|
switch atomic.LoadUint32(&p.state) {
|
||||||
|
case stateDefault:
|
||||||
|
return 0
|
||||||
|
case stateInited:
|
||||||
|
return 1
|
||||||
|
case stateClosed:
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) IdleLen() int {
|
||||||
|
return len(p.ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Stats() *Stats {
|
||||||
|
return &Stats{}
|
||||||
|
}
|
||||||
332
vendor/github.com/go-redis/redis/v8/internal/proto/reader.go
generated
vendored
Normal file
332
vendor/github.com/go-redis/redis/v8/internal/proto/reader.go
generated
vendored
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// redis resp protocol data type.
|
||||||
|
const (
|
||||||
|
ErrorReply = '-'
|
||||||
|
StatusReply = '+'
|
||||||
|
IntReply = ':'
|
||||||
|
StringReply = '$'
|
||||||
|
ArrayReply = '*'
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const Nil = RedisError("redis: nil") // nolint:errname
|
||||||
|
|
||||||
|
type RedisError string
|
||||||
|
|
||||||
|
func (e RedisError) Error() string { return string(e) }
|
||||||
|
|
||||||
|
func (RedisError) RedisError() {}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type MultiBulkParse func(*Reader, int64) (interface{}, error)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
rd *bufio.Reader
|
||||||
|
_buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(rd io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
rd: bufio.NewReader(rd),
|
||||||
|
_buf: make([]byte, 64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Buffered() int {
|
||||||
|
return r.rd.Buffered()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Peek(n int) ([]byte, error) {
|
||||||
|
return r.rd.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Reset(rd io.Reader) {
|
||||||
|
r.rd.Reset(rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadLine() ([]byte, error) {
|
||||||
|
line, err := r.readLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isNilReply(line) {
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLine that returns an error if:
|
||||||
|
// - there is a pending read error;
|
||||||
|
// - or line does not end with \r\n.
|
||||||
|
func (r *Reader) readLine() ([]byte, error) {
|
||||||
|
b, err := r.rd.ReadSlice('\n')
|
||||||
|
if err != nil {
|
||||||
|
if err != bufio.ErrBufferFull {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
full := make([]byte, len(b))
|
||||||
|
copy(full, b)
|
||||||
|
|
||||||
|
b, err = r.rd.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
full = append(full, b...) //nolint:makezero
|
||||||
|
b = full
|
||||||
|
}
|
||||||
|
if len(b) <= 2 || b[len(b)-1] != '\n' || b[len(b)-2] != '\r' {
|
||||||
|
return nil, fmt.Errorf("redis: invalid reply: %q", b)
|
||||||
|
}
|
||||||
|
return b[:len(b)-2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case StatusReply:
|
||||||
|
return string(line[1:]), nil
|
||||||
|
case IntReply:
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
case StringReply:
|
||||||
|
return r.readStringReply(line)
|
||||||
|
case ArrayReply:
|
||||||
|
n, err := parseArrayLen(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
err := fmt.Errorf("redis: got %.100q, but multi bulk parser is nil", line)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m(r, n)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("redis: can't parse %.100q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadIntReply() (int64, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return 0, ParseErrorReply(line)
|
||||||
|
case IntReply:
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadString() (string, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return "", ParseErrorReply(line)
|
||||||
|
case StringReply:
|
||||||
|
return r.readStringReply(line)
|
||||||
|
case StatusReply:
|
||||||
|
return string(line[1:]), nil
|
||||||
|
case IntReply:
|
||||||
|
return string(line[1:]), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readStringReply(line []byte) (string, error) {
|
||||||
|
if isNilReply(line) {
|
||||||
|
return "", Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replyLen, err := util.Atoi(line[1:])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, replyLen+2)
|
||||||
|
_, err = io.ReadFull(r.rd, b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.BytesToString(b[:replyLen]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case ArrayReply:
|
||||||
|
n, err := parseArrayLen(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m(r, n)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadArrayLen() (int, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return 0, ParseErrorReply(line)
|
||||||
|
case ArrayReply:
|
||||||
|
n, err := parseArrayLen(line)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(n), nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadScanReply() ([]string, uint64, error) {
|
||||||
|
n, err := r.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := r.ReadUint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = r.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
key, err := r.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, cursor, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadInt() (int64, error) {
|
||||||
|
b, err := r.readTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseInt(b, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint() (uint64, error) {
|
||||||
|
b, err := r.readTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseUint(b, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadFloatReply() (float64, error) {
|
||||||
|
b, err := r.readTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseFloat(b, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readTmpBytesReply() ([]byte, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case StringReply:
|
||||||
|
return r._readTmpBytesReply(line)
|
||||||
|
case StatusReply:
|
||||||
|
return line[1:], nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) _readTmpBytesReply(line []byte) ([]byte, error) {
|
||||||
|
if isNilReply(line) {
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replyLen, err := util.Atoi(line[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := r.buf(replyLen + 2)
|
||||||
|
_, err = io.ReadFull(r.rd, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf[:replyLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) buf(n int) []byte {
|
||||||
|
if n <= cap(r._buf) {
|
||||||
|
return r._buf[:n]
|
||||||
|
}
|
||||||
|
d := n - cap(r._buf)
|
||||||
|
r._buf = append(r._buf, make([]byte, d)...)
|
||||||
|
return r._buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNilReply(b []byte) bool {
|
||||||
|
return len(b) == 3 &&
|
||||||
|
(b[0] == StringReply || b[0] == ArrayReply) &&
|
||||||
|
b[1] == '-' && b[2] == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseErrorReply(line []byte) error {
|
||||||
|
return RedisError(string(line[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseArrayLen(line []byte) (int64, error) {
|
||||||
|
if isNilReply(line) {
|
||||||
|
return 0, Nil
|
||||||
|
}
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
}
|
||||||
172
vendor/github.com/go-redis/redis/v8/internal/proto/scan.go
generated
vendored
Normal file
172
vendor/github.com/go-redis/redis/v8/internal/proto/scan.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan parses bytes `b` to `v` with appropriate type.
|
||||||
|
func Scan(b []byte, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return fmt.Errorf("redis: Scan(nil)")
|
||||||
|
case *string:
|
||||||
|
*v = util.BytesToString(b)
|
||||||
|
return nil
|
||||||
|
case *[]byte:
|
||||||
|
*v = b
|
||||||
|
return nil
|
||||||
|
case *int:
|
||||||
|
var err error
|
||||||
|
*v, err = util.Atoi(b)
|
||||||
|
return err
|
||||||
|
case *int8:
|
||||||
|
n, err := util.ParseInt(b, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int8(n)
|
||||||
|
return nil
|
||||||
|
case *int16:
|
||||||
|
n, err := util.ParseInt(b, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int16(n)
|
||||||
|
return nil
|
||||||
|
case *int32:
|
||||||
|
n, err := util.ParseInt(b, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int32(n)
|
||||||
|
return nil
|
||||||
|
case *int64:
|
||||||
|
n, err := util.ParseInt(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = n
|
||||||
|
return nil
|
||||||
|
case *uint:
|
||||||
|
n, err := util.ParseUint(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint(n)
|
||||||
|
return nil
|
||||||
|
case *uint8:
|
||||||
|
n, err := util.ParseUint(b, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint8(n)
|
||||||
|
return nil
|
||||||
|
case *uint16:
|
||||||
|
n, err := util.ParseUint(b, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint16(n)
|
||||||
|
return nil
|
||||||
|
case *uint32:
|
||||||
|
n, err := util.ParseUint(b, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint32(n)
|
||||||
|
return nil
|
||||||
|
case *uint64:
|
||||||
|
n, err := util.ParseUint(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = n
|
||||||
|
return nil
|
||||||
|
case *float32:
|
||||||
|
n, err := util.ParseFloat(b, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = float32(n)
|
||||||
|
return err
|
||||||
|
case *float64:
|
||||||
|
var err error
|
||||||
|
*v, err = util.ParseFloat(b, 64)
|
||||||
|
return err
|
||||||
|
case *bool:
|
||||||
|
*v = len(b) == 1 && b[0] == '1'
|
||||||
|
return nil
|
||||||
|
case *time.Time:
|
||||||
|
var err error
|
||||||
|
*v, err = time.Parse(time.RFC3339Nano, util.BytesToString(b))
|
||||||
|
return err
|
||||||
|
case encoding.BinaryUnmarshaler:
|
||||||
|
return v.UnmarshalBinary(b)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScanSlice(data []string, slice interface{}) error {
|
||||||
|
v := reflect.ValueOf(slice)
|
||||||
|
if !v.IsValid() {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(nil)")
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice)
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
if v.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := makeSliceNextElemFunc(v)
|
||||||
|
for i, s := range data {
|
||||||
|
elem := next()
|
||||||
|
if err := Scan([]byte(s), elem.Addr().Interface()); err != nil {
|
||||||
|
err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value {
|
||||||
|
elemType := v.Type().Elem()
|
||||||
|
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
return func() reflect.Value {
|
||||||
|
if v.Len() < v.Cap() {
|
||||||
|
v.Set(v.Slice(0, v.Len()+1))
|
||||||
|
elem := v.Index(v.Len() - 1)
|
||||||
|
if elem.IsNil() {
|
||||||
|
elem.Set(reflect.New(elemType))
|
||||||
|
}
|
||||||
|
return elem.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := reflect.New(elemType)
|
||||||
|
v.Set(reflect.Append(v, elem))
|
||||||
|
return elem.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zero := reflect.Zero(elemType)
|
||||||
|
return func() reflect.Value {
|
||||||
|
if v.Len() < v.Cap() {
|
||||||
|
v.Set(v.Slice(0, v.Len()+1))
|
||||||
|
return v.Index(v.Len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(reflect.Append(v, zero))
|
||||||
|
return v.Index(v.Len() - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
vendor/github.com/go-redis/redis/v8/internal/proto/writer.go
generated
vendored
Normal file
153
vendor/github.com/go-redis/redis/v8/internal/proto/writer.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
// io.StringWriter
|
||||||
|
WriteString(s string) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Writer struct {
|
||||||
|
writer
|
||||||
|
|
||||||
|
lenBuf []byte
|
||||||
|
numBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriter(wr writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
writer: wr,
|
||||||
|
|
||||||
|
lenBuf: make([]byte, 64),
|
||||||
|
numBuf: make([]byte, 64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteArgs(args []interface{}) error {
|
||||||
|
if err := w.WriteByte(ArrayReply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.writeLen(len(args)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
if err := w.WriteArg(arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) writeLen(n int) error {
|
||||||
|
w.lenBuf = strconv.AppendUint(w.lenBuf[:0], uint64(n), 10)
|
||||||
|
w.lenBuf = append(w.lenBuf, '\r', '\n')
|
||||||
|
_, err := w.Write(w.lenBuf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) WriteArg(v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return w.string("")
|
||||||
|
case string:
|
||||||
|
return w.string(v)
|
||||||
|
case []byte:
|
||||||
|
return w.bytes(v)
|
||||||
|
case int:
|
||||||
|
return w.int(int64(v))
|
||||||
|
case int8:
|
||||||
|
return w.int(int64(v))
|
||||||
|
case int16:
|
||||||
|
return w.int(int64(v))
|
||||||
|
case int32:
|
||||||
|
return w.int(int64(v))
|
||||||
|
case int64:
|
||||||
|
return w.int(v)
|
||||||
|
case uint:
|
||||||
|
return w.uint(uint64(v))
|
||||||
|
case uint8:
|
||||||
|
return w.uint(uint64(v))
|
||||||
|
case uint16:
|
||||||
|
return w.uint(uint64(v))
|
||||||
|
case uint32:
|
||||||
|
return w.uint(uint64(v))
|
||||||
|
case uint64:
|
||||||
|
return w.uint(v)
|
||||||
|
case float32:
|
||||||
|
return w.float(float64(v))
|
||||||
|
case float64:
|
||||||
|
return w.float(v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
return w.int(1)
|
||||||
|
}
|
||||||
|
return w.int(0)
|
||||||
|
case time.Time:
|
||||||
|
w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano)
|
||||||
|
return w.bytes(w.numBuf)
|
||||||
|
case encoding.BinaryMarshaler:
|
||||||
|
b, err := v.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.bytes(b)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"redis: can't marshal %T (implement encoding.BinaryMarshaler)", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) bytes(b []byte) error {
|
||||||
|
if err := w.WriteByte(StringReply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.writeLen(len(b)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.crlf()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) string(s string) error {
|
||||||
|
return w.bytes(util.StringToBytes(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) uint(n uint64) error {
|
||||||
|
w.numBuf = strconv.AppendUint(w.numBuf[:0], n, 10)
|
||||||
|
return w.bytes(w.numBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) int(n int64) error {
|
||||||
|
w.numBuf = strconv.AppendInt(w.numBuf[:0], n, 10)
|
||||||
|
return w.bytes(w.numBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) float(f float64) error {
|
||||||
|
w.numBuf = strconv.AppendFloat(w.numBuf[:0], f, 'f', -1, 64)
|
||||||
|
return w.bytes(w.numBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Writer) crlf() error {
|
||||||
|
if err := w.WriteByte('\r'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.WriteByte('\n')
|
||||||
|
}
|
||||||
50
vendor/github.com/go-redis/redis/v8/internal/rand/rand.go
generated
vendored
Normal file
50
vendor/github.com/go-redis/redis/v8/internal/rand/rand.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle pseudo-randomizes the order of elements.
|
||||||
|
// n is the number of elements.
|
||||||
|
// swap swaps the elements with indexes i and j.
|
||||||
|
func Shuffle(n int, swap func(i, j int)) { pseudo.Shuffle(n, swap) }
|
||||||
12
vendor/github.com/go-redis/redis/v8/internal/safe.go
generated
vendored
Normal file
12
vendor/github.com/go-redis/redis/v8/internal/safe.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
func String(b []byte) string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bytes(s string) []byte {
|
||||||
|
return []byte(s)
|
||||||
|
}
|
||||||
21
vendor/github.com/go-redis/redis/v8/internal/unsafe.go
generated
vendored
Normal file
21
vendor/github.com/go-redis/redis/v8/internal/unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build !appengine
|
||||||
|
// +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)},
|
||||||
|
))
|
||||||
|
}
|
||||||
46
vendor/github.com/go-redis/redis/v8/internal/util.go
generated
vendored
Normal file
46
vendor/github.com/go-redis/redis/v8/internal/util.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/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
|
||||||
|
}
|
||||||
12
vendor/github.com/go-redis/redis/v8/internal/util/safe.go
generated
vendored
Normal file
12
vendor/github.com/go-redis/redis/v8/internal/util/safe.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToBytes(s string) []byte {
|
||||||
|
return []byte(s)
|
||||||
|
}
|
||||||
19
vendor/github.com/go-redis/redis/v8/internal/util/strconv.go
generated
vendored
Normal file
19
vendor/github.com/go-redis/redis/v8/internal/util/strconv.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func Atoi(b []byte) (int, error) {
|
||||||
|
return strconv.Atoi(BytesToString(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInt(b []byte, base int, bitSize int) (int64, error) {
|
||||||
|
return strconv.ParseInt(BytesToString(b), base, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUint(b []byte, base int, bitSize int) (uint64, error) {
|
||||||
|
return strconv.ParseUint(BytesToString(b), base, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFloat(b []byte, bitSize int) (float64, error) {
|
||||||
|
return strconv.ParseFloat(BytesToString(b), bitSize)
|
||||||
|
}
|
||||||
23
vendor/github.com/go-redis/redis/v8/internal/util/unsafe.go
generated
vendored
Normal file
23
vendor/github.com/go-redis/redis/v8/internal/util/unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//go:build !appengine
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BytesToString converts byte slice to string.
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToBytes converts string to byte slice.
|
||||||
|
func StringToBytes(s string) []byte {
|
||||||
|
return *(*[]byte)(unsafe.Pointer(
|
||||||
|
&struct {
|
||||||
|
string
|
||||||
|
Cap int
|
||||||
|
}{s, len(s)},
|
||||||
|
))
|
||||||
|
}
|
||||||
77
vendor/github.com/go-redis/redis/v8/iterator.go
generated
vendored
Normal file
77
vendor/github.com/go-redis/redis/v8/iterator.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScanIterator is used to incrementally iterate over a collection of elements.
|
||||||
|
// It's safe for concurrent use by multiple goroutines.
|
||||||
|
type ScanIterator struct {
|
||||||
|
mu sync.Mutex // protects Scanner and pos
|
||||||
|
cmd *ScanCmd
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the last iterator error, if any.
|
||||||
|
func (it *ScanIterator) Err() error {
|
||||||
|
it.mu.Lock()
|
||||||
|
err := it.cmd.Err()
|
||||||
|
it.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances the cursor and returns true if more values can be read.
|
||||||
|
func (it *ScanIterator) Next(ctx context.Context) bool {
|
||||||
|
it.mu.Lock()
|
||||||
|
defer it.mu.Unlock()
|
||||||
|
|
||||||
|
// Instantly return on errors.
|
||||||
|
if it.cmd.Err() != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance cursor, check if we are still within range.
|
||||||
|
if it.pos < len(it.cmd.page) {
|
||||||
|
it.pos++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Return if there is no more data to fetch.
|
||||||
|
if it.cmd.cursor == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch next page.
|
||||||
|
switch it.cmd.args[0] {
|
||||||
|
case "scan", "qscan":
|
||||||
|
it.cmd.args[1] = it.cmd.cursor
|
||||||
|
default:
|
||||||
|
it.cmd.args[2] = it.cmd.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
err := it.cmd.process(ctx, it.cmd)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
it.pos = 1
|
||||||
|
|
||||||
|
// Redis can occasionally return empty page.
|
||||||
|
if len(it.cmd.page) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val returns the key/field at the current cursor position.
|
||||||
|
func (it *ScanIterator) Val() string {
|
||||||
|
var v string
|
||||||
|
it.mu.Lock()
|
||||||
|
if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
|
||||||
|
v = it.cmd.page[it.pos-1]
|
||||||
|
}
|
||||||
|
it.mu.Unlock()
|
||||||
|
return v
|
||||||
|
}
|
||||||
429
vendor/github.com/go-redis/redis/v8/options.go
generated
vendored
Normal file
429
vendor/github.com/go-redis/redis/v8/options.go
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Limiter is the interface of a rate limiter or a circuit breaker.
|
||||||
|
type Limiter interface {
|
||||||
|
// Allow returns nil if operation is allowed or an error otherwise.
|
||||||
|
// If operation is allowed client must ReportResult of the operation
|
||||||
|
// whether it is a success or a failure.
|
||||||
|
Allow() error
|
||||||
|
// ReportResult reports the result of the previously allowed operation.
|
||||||
|
// nil indicates a success, non-nil error usually indicates a failure.
|
||||||
|
ReportResult(result error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options keeps the settings to setup redis connection.
|
||||||
|
type Options struct {
|
||||||
|
// The network type, either tcp or unix.
|
||||||
|
// Default is tcp.
|
||||||
|
Network string
|
||||||
|
// host:port address.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// Dialer creates new network connection and has priority over
|
||||||
|
// Network and Addr options.
|
||||||
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Hook that is called when new connection is established.
|
||||||
|
OnConnect func(ctx context.Context, cn *Conn) error
|
||||||
|
|
||||||
|
// Use the specified Username to authenticate the current connection
|
||||||
|
// with one of the connections defined in the ACL list when connecting
|
||||||
|
// to a Redis 6.0 instance, or greater, that is using the Redis ACL system.
|
||||||
|
Username string
|
||||||
|
// Optional password. Must match the password specified in the
|
||||||
|
// requirepass server configuration option (if connecting to a Redis 5.0 instance, or lower),
|
||||||
|
// or the User Password when connecting to a Redis 6.0 instance, or greater,
|
||||||
|
// that is using the Redis ACL system.
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// Database to be selected after connecting to the server.
|
||||||
|
DB int
|
||||||
|
|
||||||
|
// Maximum number of retries before giving up.
|
||||||
|
// Default is 3 retries; -1 (not 0) disables retries.
|
||||||
|
MaxRetries int
|
||||||
|
// Minimum backoff between each retry.
|
||||||
|
// Default is 8 milliseconds; -1 disables backoff.
|
||||||
|
MinRetryBackoff time.Duration
|
||||||
|
// Maximum backoff between each retry.
|
||||||
|
// Default is 512 milliseconds; -1 disables backoff.
|
||||||
|
MaxRetryBackoff time.Duration
|
||||||
|
|
||||||
|
// Dial timeout for establishing new connections.
|
||||||
|
// Default is 5 seconds.
|
||||||
|
DialTimeout time.Duration
|
||||||
|
// Timeout for socket reads. If reached, commands will fail
|
||||||
|
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
|
||||||
|
// Default is 3 seconds.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
// Timeout for socket writes. If reached, commands will fail
|
||||||
|
// with a timeout instead of blocking.
|
||||||
|
// Default is ReadTimeout.
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
// Type of connection pool.
|
||||||
|
// true for FIFO pool, false for LIFO pool.
|
||||||
|
// Note that fifo has higher overhead compared to lifo.
|
||||||
|
PoolFIFO bool
|
||||||
|
// Maximum number of socket connections.
|
||||||
|
// Default is 10 connections per every available CPU as reported by runtime.GOMAXPROCS.
|
||||||
|
PoolSize int
|
||||||
|
// Minimum number of idle connections which is useful when establishing
|
||||||
|
// new connection is slow.
|
||||||
|
MinIdleConns int
|
||||||
|
// Connection age at which client retires (closes) the connection.
|
||||||
|
// Default is to not close aged connections.
|
||||||
|
MaxConnAge time.Duration
|
||||||
|
// Amount of time client waits for connection if all connections
|
||||||
|
// are busy before returning an error.
|
||||||
|
// Default is ReadTimeout + 1 second.
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
// Amount of time after which client closes idle connections.
|
||||||
|
// Should be less than server's timeout.
|
||||||
|
// Default is 5 minutes. -1 disables idle timeout check.
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
// Frequency of idle checks made by idle connections reaper.
|
||||||
|
// Default is 1 minute. -1 disables idle connections reaper,
|
||||||
|
// but idle connections are still discarded by the client
|
||||||
|
// if IdleTimeout is set.
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
|
||||||
|
// Enables read only queries on slave nodes.
|
||||||
|
readOnly bool
|
||||||
|
|
||||||
|
// TLS Config to use. When set TLS will be negotiated.
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
|
// Limiter interface used to implemented circuit breaker or rate limiter.
|
||||||
|
Limiter Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *Options) init() {
|
||||||
|
if opt.Addr == "" {
|
||||||
|
opt.Addr = "localhost:6379"
|
||||||
|
}
|
||||||
|
if opt.Network == "" {
|
||||||
|
if strings.HasPrefix(opt.Addr, "/") {
|
||||||
|
opt.Network = "unix"
|
||||||
|
} else {
|
||||||
|
opt.Network = "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.DialTimeout == 0 {
|
||||||
|
opt.DialTimeout = 5 * time.Second
|
||||||
|
}
|
||||||
|
if opt.Dialer == nil {
|
||||||
|
opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
netDialer := &net.Dialer{
|
||||||
|
Timeout: opt.DialTimeout,
|
||||||
|
KeepAlive: 5 * time.Minute,
|
||||||
|
}
|
||||||
|
if opt.TLSConfig == nil {
|
||||||
|
return netDialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.PoolSize == 0 {
|
||||||
|
opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
|
||||||
|
}
|
||||||
|
switch opt.ReadTimeout {
|
||||||
|
case -1:
|
||||||
|
opt.ReadTimeout = 0
|
||||||
|
case 0:
|
||||||
|
opt.ReadTimeout = 3 * time.Second
|
||||||
|
}
|
||||||
|
switch opt.WriteTimeout {
|
||||||
|
case -1:
|
||||||
|
opt.WriteTimeout = 0
|
||||||
|
case 0:
|
||||||
|
opt.WriteTimeout = opt.ReadTimeout
|
||||||
|
}
|
||||||
|
if opt.PoolTimeout == 0 {
|
||||||
|
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
||||||
|
}
|
||||||
|
if opt.IdleTimeout == 0 {
|
||||||
|
opt.IdleTimeout = 5 * time.Minute
|
||||||
|
}
|
||||||
|
if opt.IdleCheckFrequency == 0 {
|
||||||
|
opt.IdleCheckFrequency = time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.MaxRetries == -1 {
|
||||||
|
opt.MaxRetries = 0
|
||||||
|
} else if opt.MaxRetries == 0 {
|
||||||
|
opt.MaxRetries = 3
|
||||||
|
}
|
||||||
|
switch opt.MinRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MinRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||||
|
}
|
||||||
|
switch opt.MaxRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MaxRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *Options) clone() *Options {
|
||||||
|
clone := *opt
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseURL parses an URL into Options that can be used to connect to Redis.
|
||||||
|
// Scheme is required.
|
||||||
|
// There are two connection types: by tcp socket and by unix socket.
|
||||||
|
// Tcp connection:
|
||||||
|
// redis://<user>:<password>@<host>:<port>/<db_number>
|
||||||
|
// Unix connection:
|
||||||
|
// unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>
|
||||||
|
// Most Option fields can be set using query parameters, with the following restrictions:
|
||||||
|
// - field names are mapped using snake-case conversion: to set MaxRetries, use max_retries
|
||||||
|
// - only scalar type fields are supported (bool, int, time.Duration)
|
||||||
|
// - for time.Duration fields, values must be a valid input for time.ParseDuration();
|
||||||
|
// additionally a plain integer as value (i.e. without unit) is intepreted as seconds
|
||||||
|
// - to disable a duration field, use value less than or equal to 0; to use the default
|
||||||
|
// value, leave the value blank or remove the parameter
|
||||||
|
// - only the last value is interpreted if a parameter is given multiple times
|
||||||
|
// - fields "network", "addr", "username" and "password" can only be set using other
|
||||||
|
// URL attributes (scheme, host, userinfo, resp.), query paremeters using these
|
||||||
|
// names will be treated as unknown parameters
|
||||||
|
// - unknown parameter names will result in an error
|
||||||
|
// Examples:
|
||||||
|
// redis://user:password@localhost:6789/3?dial_timeout=3&db=1&read_timeout=6s&max_retries=2
|
||||||
|
// is equivalent to:
|
||||||
|
// &Options{
|
||||||
|
// Network: "tcp",
|
||||||
|
// Addr: "localhost:6789",
|
||||||
|
// DB: 1, // path "/3" was overridden by "&db=1"
|
||||||
|
// DialTimeout: 3 * time.Second, // no time unit = seconds
|
||||||
|
// ReadTimeout: 6 * time.Second,
|
||||||
|
// MaxRetries: 2,
|
||||||
|
// }
|
||||||
|
func ParseURL(redisURL string) (*Options, error) {
|
||||||
|
u, err := url.Parse(redisURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "redis", "rediss":
|
||||||
|
return setupTCPConn(u)
|
||||||
|
case "unix":
|
||||||
|
return setupUnixConn(u)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTCPConn(u *url.URL) (*Options, error) {
|
||||||
|
o := &Options{Network: "tcp"}
|
||||||
|
|
||||||
|
o.Username, o.Password = getUserPassword(u)
|
||||||
|
|
||||||
|
h, p, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
h = u.Host
|
||||||
|
}
|
||||||
|
if h == "" {
|
||||||
|
h = "localhost"
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = "6379"
|
||||||
|
}
|
||||||
|
o.Addr = net.JoinHostPort(h, p)
|
||||||
|
|
||||||
|
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
||||||
|
return r == '/'
|
||||||
|
})
|
||||||
|
switch len(f) {
|
||||||
|
case 0:
|
||||||
|
o.DB = 0
|
||||||
|
case 1:
|
||||||
|
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
||||||
|
return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "rediss" {
|
||||||
|
o.TLSConfig = &tls.Config{ServerName: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setupConnParams(u, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupUnixConn(u *url.URL) (*Options, error) {
|
||||||
|
o := &Options{
|
||||||
|
Network: "unix",
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(u.Path) == "" { // path is required with unix connection
|
||||||
|
return nil, errors.New("redis: empty unix socket path")
|
||||||
|
}
|
||||||
|
o.Addr = u.Path
|
||||||
|
o.Username, o.Password = getUserPassword(u)
|
||||||
|
return setupConnParams(u, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryOptions struct {
|
||||||
|
q url.Values
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryOptions) string(name string) string {
|
||||||
|
vs := o.q[name]
|
||||||
|
if len(vs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
delete(o.q, name) // enable detection of unknown parameters
|
||||||
|
return vs[len(vs)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryOptions) int(name string) int {
|
||||||
|
s := o.string(name)
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(s)
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
if o.err == nil {
|
||||||
|
o.err = fmt.Errorf("redis: invalid %s number: %s", name, err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryOptions) duration(name string) time.Duration {
|
||||||
|
s := o.string(name)
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// try plain number first
|
||||||
|
if i, err := strconv.Atoi(s); err == nil {
|
||||||
|
if i <= 0 {
|
||||||
|
// disable timeouts
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return time.Duration(i) * time.Second
|
||||||
|
}
|
||||||
|
dur, err := time.ParseDuration(s)
|
||||||
|
if err == nil {
|
||||||
|
return dur
|
||||||
|
}
|
||||||
|
if o.err == nil {
|
||||||
|
o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryOptions) bool(name string) bool {
|
||||||
|
switch s := o.string(name); s {
|
||||||
|
case "true", "1":
|
||||||
|
return true
|
||||||
|
case "false", "0", "":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
if o.err == nil {
|
||||||
|
o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryOptions) remaining() []string {
|
||||||
|
if len(o.q) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(o.q))
|
||||||
|
for k := range o.q {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupConnParams converts query parameters in u to option value in o.
|
||||||
|
func setupConnParams(u *url.URL, o *Options) (*Options, error) {
|
||||||
|
q := queryOptions{q: u.Query()}
|
||||||
|
|
||||||
|
// compat: a future major release may use q.int("db")
|
||||||
|
if tmp := q.string("db"); tmp != "" {
|
||||||
|
db, err := strconv.Atoi(tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("redis: invalid database number: %w", err)
|
||||||
|
}
|
||||||
|
o.DB = db
|
||||||
|
}
|
||||||
|
|
||||||
|
o.MaxRetries = q.int("max_retries")
|
||||||
|
o.MinRetryBackoff = q.duration("min_retry_backoff")
|
||||||
|
o.MaxRetryBackoff = q.duration("max_retry_backoff")
|
||||||
|
o.DialTimeout = q.duration("dial_timeout")
|
||||||
|
o.ReadTimeout = q.duration("read_timeout")
|
||||||
|
o.WriteTimeout = q.duration("write_timeout")
|
||||||
|
o.PoolFIFO = q.bool("pool_fifo")
|
||||||
|
o.PoolSize = q.int("pool_size")
|
||||||
|
o.MinIdleConns = q.int("min_idle_conns")
|
||||||
|
o.MaxConnAge = q.duration("max_conn_age")
|
||||||
|
o.PoolTimeout = q.duration("pool_timeout")
|
||||||
|
o.IdleTimeout = q.duration("idle_timeout")
|
||||||
|
o.IdleCheckFrequency = q.duration("idle_check_frequency")
|
||||||
|
if q.err != nil {
|
||||||
|
return nil, q.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// any parameters left?
|
||||||
|
if r := q.remaining(); len(r) > 0 {
|
||||||
|
return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserPassword(u *url.URL) (string, string) {
|
||||||
|
var user, password string
|
||||||
|
if u.User != nil {
|
||||||
|
user = u.User.Username()
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user, password
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnPool(opt *Options) *pool.ConnPool {
|
||||||
|
return pool.NewConnPool(&pool.Options{
|
||||||
|
Dialer: func(ctx context.Context) (net.Conn, error) {
|
||||||
|
return opt.Dialer(ctx, opt.Network, opt.Addr)
|
||||||
|
},
|
||||||
|
PoolFIFO: opt.PoolFIFO,
|
||||||
|
PoolSize: opt.PoolSize,
|
||||||
|
MinIdleConns: opt.MinIdleConns,
|
||||||
|
MaxConnAge: opt.MaxConnAge,
|
||||||
|
PoolTimeout: opt.PoolTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
||||||
|
})
|
||||||
|
}
|
||||||
8
vendor/github.com/go-redis/redis/v8/package.json
generated
vendored
Normal file
8
vendor/github.com/go-redis/redis/v8/package.json
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "redis",
|
||||||
|
"version": "8.11.4",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": "git@github.com:go-redis/redis.git",
|
||||||
|
"author": "Vladimir Mihailenco <vladimir.webdev@gmail.com>",
|
||||||
|
"license": "BSD-2-clause"
|
||||||
|
}
|
||||||
137
vendor/github.com/go-redis/redis/v8/pipeline.go
generated
vendored
Normal file
137
vendor/github.com/go-redis/redis/v8/pipeline.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pipelineExecer func(context.Context, []Cmder) error
|
||||||
|
|
||||||
|
// Pipeliner is an mechanism to realise Redis Pipeline technique.
|
||||||
|
//
|
||||||
|
// Pipelining is a technique to extremely speed up processing by packing
|
||||||
|
// operations to batches, send them at once to Redis and read a replies in a
|
||||||
|
// singe step.
|
||||||
|
// See https://redis.io/topics/pipelining
|
||||||
|
//
|
||||||
|
// Pay attention, that Pipeline is not a transaction, so you can get unexpected
|
||||||
|
// results in case of big pipelines and small read/write timeouts.
|
||||||
|
// Redis client has retransmission logic in case of timeouts, pipeline
|
||||||
|
// can be retransmitted and commands can be executed more then once.
|
||||||
|
// To avoid this: it is good idea to use reasonable bigger read/write timeouts
|
||||||
|
// depends of your batch size and/or use TxPipeline.
|
||||||
|
type Pipeliner interface {
|
||||||
|
StatefulCmdable
|
||||||
|
Do(ctx context.Context, args ...interface{}) *Cmd
|
||||||
|
Process(ctx context.Context, cmd Cmder) error
|
||||||
|
Close() error
|
||||||
|
Discard() error
|
||||||
|
Exec(ctx context.Context) ([]Cmder, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pipeliner = (*Pipeline)(nil)
|
||||||
|
|
||||||
|
// Pipeline implements pipelining as described in
|
||||||
|
// http://redis.io/topics/pipelining. It's safe for concurrent use
|
||||||
|
// by multiple goroutines.
|
||||||
|
type Pipeline struct {
|
||||||
|
cmdable
|
||||||
|
statefulCmdable
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
exec pipelineExecer
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
cmds []Cmder
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) init() {
|
||||||
|
c.cmdable = c.Process
|
||||||
|
c.statefulCmdable = c.Process
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||||
|
cmd := NewCmd(ctx, args...)
|
||||||
|
_ = c.Process(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process queues the cmd for later execution.
|
||||||
|
func (c *Pipeline) Process(ctx context.Context, cmd Cmder) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.cmds = append(c.cmds, cmd)
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the pipeline, releasing any open resources.
|
||||||
|
func (c *Pipeline) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
_ = c.discard()
|
||||||
|
c.closed = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard resets the pipeline and discards queued commands.
|
||||||
|
func (c *Pipeline) Discard() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.discard()
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) discard() error {
|
||||||
|
if c.closed {
|
||||||
|
return pool.ErrClosed
|
||||||
|
}
|
||||||
|
c.cmds = c.cmds[:0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes all previously queued commands using one
|
||||||
|
// client-server roundtrip.
|
||||||
|
//
|
||||||
|
// Exec always returns list of commands and error of the first failed
|
||||||
|
// command if any.
|
||||||
|
func (c *Pipeline) Exec(ctx context.Context) ([]Cmder, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.cmds) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := c.cmds
|
||||||
|
c.cmds = nil
|
||||||
|
|
||||||
|
return cmds, c.exec(ctx, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
if err := fn(c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmds, err := c.Exec(ctx)
|
||||||
|
_ = c.Close()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Pipeline() Pipeliner {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) TxPipeline() Pipeliner {
|
||||||
|
return c
|
||||||
|
}
|
||||||
668
vendor/github.com/go-redis/redis/v8/pubsub.go
generated
vendored
Normal file
668
vendor/github.com/go-redis/redis/v8/pubsub.go
generated
vendored
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"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/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PubSub implements Pub/Sub commands as described in
|
||||||
|
// http://redis.io/topics/pubsub. Message receiving is NOT safe
|
||||||
|
// for concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// PubSub automatically reconnects to Redis Server and resubscribes
|
||||||
|
// to the channels in case of network errors.
|
||||||
|
type PubSub struct {
|
||||||
|
opt *Options
|
||||||
|
|
||||||
|
newConn func(ctx context.Context, channels []string) (*pool.Conn, error)
|
||||||
|
closeConn func(*pool.Conn) error
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
cn *pool.Conn
|
||||||
|
channels map[string]struct{}
|
||||||
|
patterns map[string]struct{}
|
||||||
|
|
||||||
|
closed bool
|
||||||
|
exit chan struct{}
|
||||||
|
|
||||||
|
cmd *Cmd
|
||||||
|
|
||||||
|
chOnce sync.Once
|
||||||
|
msgCh *channel
|
||||||
|
allCh *channel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) init() {
|
||||||
|
c.exit = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) String() string {
|
||||||
|
channels := mapKeys(c.channels)
|
||||||
|
channels = append(channels, mapKeys(c.patterns)...)
|
||||||
|
return fmt.Sprintf("PubSub(%s)", strings.Join(channels, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) connWithLock(ctx context.Context) (*pool.Conn, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
cn, err := c.conn(ctx, nil)
|
||||||
|
c.mu.Unlock()
|
||||||
|
return cn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) conn(ctx context.Context, newChannels []string) (*pool.Conn, error) {
|
||||||
|
if c.closed {
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
if c.cn != nil {
|
||||||
|
return c.cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
channels := mapKeys(c.channels)
|
||||||
|
channels = append(channels, newChannels...)
|
||||||
|
|
||||||
|
cn, err := c.newConn(ctx, channels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.resubscribe(ctx, cn); err != nil {
|
||||||
|
_ = c.closeConn(cn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cn = cn
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) writeCmd(ctx context.Context, cn *pool.Conn, cmd Cmder) error {
|
||||||
|
return cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return writeCmd(wr, cmd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) resubscribe(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
var firstErr error
|
||||||
|
|
||||||
|
if len(c.channels) > 0 {
|
||||||
|
firstErr = c._subscribe(ctx, cn, "subscribe", mapKeys(c.channels))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.patterns) > 0 {
|
||||||
|
err := c._subscribe(ctx, cn, "psubscribe", mapKeys(c.patterns))
|
||||||
|
if err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapKeys(m map[string]struct{}) []string {
|
||||||
|
s := make([]string, len(m))
|
||||||
|
i := 0
|
||||||
|
for k := range m {
|
||||||
|
s[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) _subscribe(
|
||||||
|
ctx context.Context, cn *pool.Conn, redisCmd string, channels []string,
|
||||||
|
) error {
|
||||||
|
args := make([]interface{}, 0, 1+len(channels))
|
||||||
|
args = append(args, redisCmd)
|
||||||
|
for _, channel := range channels {
|
||||||
|
args = append(args, channel)
|
||||||
|
}
|
||||||
|
cmd := NewSliceCmd(ctx, args...)
|
||||||
|
return c.writeCmd(ctx, cn, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) releaseConnWithLock(
|
||||||
|
ctx context.Context,
|
||||||
|
cn *pool.Conn,
|
||||||
|
err error,
|
||||||
|
allowTimeout bool,
|
||||||
|
) {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.releaseConn(ctx, cn, err, allowTimeout)
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) releaseConn(ctx context.Context, cn *pool.Conn, err error, allowTimeout bool) {
|
||||||
|
if c.cn != cn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if isBadConn(err, allowTimeout, c.opt.Addr) {
|
||||||
|
c.reconnect(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) reconnect(ctx context.Context, reason error) {
|
||||||
|
_ = c.closeTheCn(reason)
|
||||||
|
_, _ = c.conn(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) closeTheCn(reason error) error {
|
||||||
|
if c.cn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !c.closed {
|
||||||
|
internal.Logger.Printf(c.getContext(), "redis: discarding bad PubSub connection: %s", reason)
|
||||||
|
}
|
||||||
|
err := c.closeConn(c.cn)
|
||||||
|
c.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return pool.ErrClosed
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
close(c.exit)
|
||||||
|
|
||||||
|
return c.closeTheCn(pool.ErrClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe the client to the specified channels. It returns
|
||||||
|
// empty subscription if there are no channels.
|
||||||
|
func (c *PubSub) Subscribe(ctx context.Context, channels ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
err := c.subscribe(ctx, "subscribe", channels...)
|
||||||
|
if c.channels == nil {
|
||||||
|
c.channels = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for _, s := range channels {
|
||||||
|
c.channels[s] = struct{}{}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe the client to the given patterns. It returns
|
||||||
|
// empty subscription if there are no patterns.
|
||||||
|
func (c *PubSub) PSubscribe(ctx context.Context, patterns ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
err := c.subscribe(ctx, "psubscribe", patterns...)
|
||||||
|
if c.patterns == nil {
|
||||||
|
c.patterns = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for _, s := range patterns {
|
||||||
|
c.patterns[s] = struct{}{}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe the client from the given channels, or from all of
|
||||||
|
// them if none is given.
|
||||||
|
func (c *PubSub) Unsubscribe(ctx context.Context, channels ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
delete(c.channels, channel)
|
||||||
|
}
|
||||||
|
err := c.subscribe(ctx, "unsubscribe", channels...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUnsubscribe the client from the given patterns, or from all of
|
||||||
|
// them if none is given.
|
||||||
|
func (c *PubSub) PUnsubscribe(ctx context.Context, patterns ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
delete(c.patterns, pattern)
|
||||||
|
}
|
||||||
|
err := c.subscribe(ctx, "punsubscribe", patterns...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) subscribe(ctx context.Context, redisCmd string, channels ...string) error {
|
||||||
|
cn, err := c.conn(ctx, channels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c._subscribe(ctx, cn, redisCmd, channels)
|
||||||
|
c.releaseConn(ctx, cn, err, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) Ping(ctx context.Context, payload ...string) error {
|
||||||
|
args := []interface{}{"ping"}
|
||||||
|
if len(payload) == 1 {
|
||||||
|
args = append(args, payload[0])
|
||||||
|
}
|
||||||
|
cmd := NewCmd(ctx, args...)
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
cn, err := c.conn(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.writeCmd(ctx, cn, cmd)
|
||||||
|
c.releaseConn(ctx, cn, err, false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscription received after a successful subscription to channel.
|
||||||
|
type Subscription struct {
|
||||||
|
// Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe".
|
||||||
|
Kind string
|
||||||
|
// Channel name we have subscribed to.
|
||||||
|
Channel string
|
||||||
|
// Number of channels we are currently subscribed to.
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Subscription) String() string {
|
||||||
|
return fmt.Sprintf("%s: %s", m.Kind, m.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message received as result of a PUBLISH command issued by another client.
|
||||||
|
type Message struct {
|
||||||
|
Channel string
|
||||||
|
Pattern string
|
||||||
|
Payload string
|
||||||
|
PayloadSlice []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) String() string {
|
||||||
|
return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pong received as result of a PING command issued by another client.
|
||||||
|
type Pong struct {
|
||||||
|
Payload string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pong) String() string {
|
||||||
|
if p.Payload != "" {
|
||||||
|
return fmt.Sprintf("Pong<%s>", p.Payload)
|
||||||
|
}
|
||||||
|
return "Pong"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) newMessage(reply interface{}) (interface{}, error) {
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case string:
|
||||||
|
return &Pong{
|
||||||
|
Payload: reply,
|
||||||
|
}, nil
|
||||||
|
case []interface{}:
|
||||||
|
switch kind := reply[0].(string); kind {
|
||||||
|
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
|
||||||
|
// Can be nil in case of "unsubscribe".
|
||||||
|
channel, _ := reply[1].(string)
|
||||||
|
return &Subscription{
|
||||||
|
Kind: kind,
|
||||||
|
Channel: channel,
|
||||||
|
Count: int(reply[2].(int64)),
|
||||||
|
}, nil
|
||||||
|
case "message":
|
||||||
|
switch payload := reply[2].(type) {
|
||||||
|
case string:
|
||||||
|
return &Message{
|
||||||
|
Channel: reply[1].(string),
|
||||||
|
Payload: payload,
|
||||||
|
}, nil
|
||||||
|
case []interface{}:
|
||||||
|
ss := make([]string, len(payload))
|
||||||
|
for i, s := range payload {
|
||||||
|
ss[i] = s.(string)
|
||||||
|
}
|
||||||
|
return &Message{
|
||||||
|
Channel: reply[1].(string),
|
||||||
|
PayloadSlice: ss,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unsupported pubsub message payload: %T", payload)
|
||||||
|
}
|
||||||
|
case "pmessage":
|
||||||
|
return &Message{
|
||||||
|
Pattern: reply[1].(string),
|
||||||
|
Channel: reply[2].(string),
|
||||||
|
Payload: reply[3].(string),
|
||||||
|
}, nil
|
||||||
|
case "pong":
|
||||||
|
return &Pong{
|
||||||
|
Payload: reply[1].(string),
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveTimeout acts like Receive but returns an error if message
|
||||||
|
// is not received in time. This is low-level API and in most cases
|
||||||
|
// Channel should be used instead.
|
||||||
|
func (c *PubSub) ReceiveTimeout(ctx context.Context, timeout time.Duration) (interface{}, error) {
|
||||||
|
if c.cmd == nil {
|
||||||
|
c.cmd = NewCmd(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't hold the lock to allow subscriptions and pings.
|
||||||
|
|
||||||
|
cn, err := c.connWithLock(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cn.WithReader(ctx, timeout, func(rd *proto.Reader) error {
|
||||||
|
return c.cmd.readReply(rd)
|
||||||
|
})
|
||||||
|
|
||||||
|
c.releaseConnWithLock(ctx, cn, err, timeout > 0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.newMessage(c.cmd.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive returns a message as a Subscription, Message, Pong or error.
|
||||||
|
// See PubSub example for details. This is low-level API and in most cases
|
||||||
|
// Channel should be used instead.
|
||||||
|
func (c *PubSub) Receive(ctx context.Context) (interface{}, error) {
|
||||||
|
return c.ReceiveTimeout(ctx, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveMessage returns a Message or error ignoring Subscription and Pong
|
||||||
|
// messages. This is low-level API and in most cases Channel should be used
|
||||||
|
// instead.
|
||||||
|
func (c *PubSub) ReceiveMessage(ctx context.Context) (*Message, error) {
|
||||||
|
for {
|
||||||
|
msg, err := c.Receive(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *Subscription:
|
||||||
|
// Ignore.
|
||||||
|
case *Pong:
|
||||||
|
// Ignore.
|
||||||
|
case *Message:
|
||||||
|
return msg, nil
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("redis: unknown message: %T", msg)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) getContext() context.Context {
|
||||||
|
if c.cmd != nil {
|
||||||
|
return c.cmd.ctx
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Channel returns a Go channel for concurrently receiving messages.
|
||||||
|
// The channel is closed together with the PubSub. If the Go channel
|
||||||
|
// is blocked full for 30 seconds the message is dropped.
|
||||||
|
// Receive* APIs can not be used after channel is created.
|
||||||
|
//
|
||||||
|
// go-redis periodically sends ping messages to test connection health
|
||||||
|
// and re-subscribes if ping can not not received for 30 seconds.
|
||||||
|
func (c *PubSub) Channel(opts ...ChannelOption) <-chan *Message {
|
||||||
|
c.chOnce.Do(func() {
|
||||||
|
c.msgCh = newChannel(c, opts...)
|
||||||
|
c.msgCh.initMsgChan()
|
||||||
|
})
|
||||||
|
if c.msgCh == nil {
|
||||||
|
err := fmt.Errorf("redis: Channel can't be called after ChannelWithSubscriptions")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c.msgCh.msgCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelSize is like Channel, but creates a Go channel
|
||||||
|
// with specified buffer size.
|
||||||
|
//
|
||||||
|
// Deprecated: use Channel(WithChannelSize(size)), remove in v9.
|
||||||
|
func (c *PubSub) ChannelSize(size int) <-chan *Message {
|
||||||
|
return c.Channel(WithChannelSize(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelWithSubscriptions is like Channel, but message type can be either
|
||||||
|
// *Subscription or *Message. Subscription messages can be used to detect
|
||||||
|
// reconnections.
|
||||||
|
//
|
||||||
|
// ChannelWithSubscriptions can not be used together with Channel or ChannelSize.
|
||||||
|
func (c *PubSub) ChannelWithSubscriptions(_ context.Context, size int) <-chan interface{} {
|
||||||
|
c.chOnce.Do(func() {
|
||||||
|
c.allCh = newChannel(c, WithChannelSize(size))
|
||||||
|
c.allCh.initAllChan()
|
||||||
|
})
|
||||||
|
if c.allCh == nil {
|
||||||
|
err := fmt.Errorf("redis: ChannelWithSubscriptions can't be called after Channel")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c.allCh.allCh
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChannelOption func(c *channel)
|
||||||
|
|
||||||
|
// WithChannelSize specifies the Go chan size that is used to buffer incoming messages.
|
||||||
|
//
|
||||||
|
// The default is 100 messages.
|
||||||
|
func WithChannelSize(size int) ChannelOption {
|
||||||
|
return func(c *channel) {
|
||||||
|
c.chanSize = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChannelHealthCheckInterval specifies the health check interval.
|
||||||
|
// PubSub will ping Redis Server if it does not receive any messages within the interval.
|
||||||
|
// To disable health check, use zero interval.
|
||||||
|
//
|
||||||
|
// The default is 3 seconds.
|
||||||
|
func WithChannelHealthCheckInterval(d time.Duration) ChannelOption {
|
||||||
|
return func(c *channel) {
|
||||||
|
c.checkInterval = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChannelSendTimeout specifies the channel send timeout after which
|
||||||
|
// the message is dropped.
|
||||||
|
//
|
||||||
|
// The default is 60 seconds.
|
||||||
|
func WithChannelSendTimeout(d time.Duration) ChannelOption {
|
||||||
|
return func(c *channel) {
|
||||||
|
c.chanSendTimeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type channel struct {
|
||||||
|
pubSub *PubSub
|
||||||
|
|
||||||
|
msgCh chan *Message
|
||||||
|
allCh chan interface{}
|
||||||
|
ping chan struct{}
|
||||||
|
|
||||||
|
chanSize int
|
||||||
|
chanSendTimeout time.Duration
|
||||||
|
checkInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChannel(pubSub *PubSub, opts ...ChannelOption) *channel {
|
||||||
|
c := &channel{
|
||||||
|
pubSub: pubSub,
|
||||||
|
|
||||||
|
chanSize: 100,
|
||||||
|
chanSendTimeout: time.Minute,
|
||||||
|
checkInterval: 3 * time.Second,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
if c.checkInterval > 0 {
|
||||||
|
c.initHealthCheck()
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *channel) initHealthCheck() {
|
||||||
|
ctx := context.TODO()
|
||||||
|
c.ping = make(chan struct{}, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
timer := time.NewTimer(time.Minute)
|
||||||
|
timer.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
timer.Reset(c.checkInterval)
|
||||||
|
select {
|
||||||
|
case <-c.ping:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
if pingErr := c.pubSub.Ping(ctx); pingErr != nil {
|
||||||
|
c.pubSub.mu.Lock()
|
||||||
|
c.pubSub.reconnect(ctx, pingErr)
|
||||||
|
c.pubSub.mu.Unlock()
|
||||||
|
}
|
||||||
|
case <-c.pubSub.exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initMsgChan must be in sync with initAllChan.
|
||||||
|
func (c *channel) initMsgChan() {
|
||||||
|
ctx := context.TODO()
|
||||||
|
c.msgCh = make(chan *Message, c.chanSize)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
timer := time.NewTimer(time.Minute)
|
||||||
|
timer.Stop()
|
||||||
|
|
||||||
|
var errCount int
|
||||||
|
for {
|
||||||
|
msg, err := c.pubSub.Receive(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err == pool.ErrClosed {
|
||||||
|
close(c.msgCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errCount > 0 {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
errCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
errCount = 0
|
||||||
|
|
||||||
|
// Any message is as good as a ping.
|
||||||
|
select {
|
||||||
|
case c.ping <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *Subscription:
|
||||||
|
// Ignore.
|
||||||
|
case *Pong:
|
||||||
|
// Ignore.
|
||||||
|
case *Message:
|
||||||
|
timer.Reset(c.chanSendTimeout)
|
||||||
|
select {
|
||||||
|
case c.msgCh <- msg:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
internal.Logger.Printf(
|
||||||
|
ctx, "redis: %s channel is full for %s (message is dropped)",
|
||||||
|
c, c.chanSendTimeout)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
internal.Logger.Printf(ctx, "redis: unknown message type: %T", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initAllChan must be in sync with initMsgChan.
|
||||||
|
func (c *channel) initAllChan() {
|
||||||
|
ctx := context.TODO()
|
||||||
|
c.allCh = make(chan interface{}, c.chanSize)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
timer := time.NewTimer(time.Minute)
|
||||||
|
timer.Stop()
|
||||||
|
|
||||||
|
var errCount int
|
||||||
|
for {
|
||||||
|
msg, err := c.pubSub.Receive(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err == pool.ErrClosed {
|
||||||
|
close(c.allCh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errCount > 0 {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
errCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
errCount = 0
|
||||||
|
|
||||||
|
// Any message is as good as a ping.
|
||||||
|
select {
|
||||||
|
case c.ping <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *Pong:
|
||||||
|
// Ignore.
|
||||||
|
case *Subscription, *Message:
|
||||||
|
timer.Reset(c.chanSendTimeout)
|
||||||
|
select {
|
||||||
|
case c.allCh <- msg:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
case <-timer.C:
|
||||||
|
internal.Logger.Printf(
|
||||||
|
ctx, "redis: %s channel is full for %s (message is dropped)",
|
||||||
|
c, c.chanSendTimeout)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
internal.Logger.Printf(ctx, "redis: unknown message type: %T", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
773
vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
Normal file
773
vendor/github.com/go-redis/redis/v8/redis.go
generated
vendored
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal"
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
"github.com/go-redis/redis/v8/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nil reply returned by Redis when key does not exist.
|
||||||
|
const Nil = proto.Nil
|
||||||
|
|
||||||
|
func SetLogger(logger internal.Logging) {
|
||||||
|
internal.Logger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Hook interface {
|
||||||
|
BeforeProcess(ctx context.Context, cmd Cmder) (context.Context, error)
|
||||||
|
AfterProcess(ctx context.Context, cmd Cmder) error
|
||||||
|
|
||||||
|
BeforeProcessPipeline(ctx context.Context, cmds []Cmder) (context.Context, error)
|
||||||
|
AfterProcessPipeline(ctx context.Context, cmds []Cmder) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type hooks struct {
|
||||||
|
hooks []Hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs *hooks) lock() {
|
||||||
|
hs.hooks = hs.hooks[:len(hs.hooks):len(hs.hooks)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs hooks) clone() hooks {
|
||||||
|
clone := hs
|
||||||
|
clone.lock()
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs *hooks) AddHook(hook Hook) {
|
||||||
|
hs.hooks = append(hs.hooks, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs hooks) process(
|
||||||
|
ctx context.Context, cmd Cmder, fn func(context.Context, Cmder) error,
|
||||||
|
) error {
|
||||||
|
if len(hs.hooks) == 0 {
|
||||||
|
err := fn(ctx, cmd)
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hookIndex int
|
||||||
|
var retErr error
|
||||||
|
|
||||||
|
for ; hookIndex < len(hs.hooks) && retErr == nil; hookIndex++ {
|
||||||
|
ctx, retErr = hs.hooks[hookIndex].BeforeProcess(ctx, cmd)
|
||||||
|
if retErr != nil {
|
||||||
|
cmd.SetErr(retErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retErr == nil {
|
||||||
|
retErr = fn(ctx, cmd)
|
||||||
|
cmd.SetErr(retErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
for hookIndex--; hookIndex >= 0; hookIndex-- {
|
||||||
|
if err := hs.hooks[hookIndex].AfterProcess(ctx, cmd); err != nil {
|
||||||
|
retErr = err
|
||||||
|
cmd.SetErr(retErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs hooks) processPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, fn func(context.Context, []Cmder) error,
|
||||||
|
) error {
|
||||||
|
if len(hs.hooks) == 0 {
|
||||||
|
err := fn(ctx, cmds)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hookIndex int
|
||||||
|
var retErr error
|
||||||
|
|
||||||
|
for ; hookIndex < len(hs.hooks) && retErr == nil; hookIndex++ {
|
||||||
|
ctx, retErr = hs.hooks[hookIndex].BeforeProcessPipeline(ctx, cmds)
|
||||||
|
if retErr != nil {
|
||||||
|
setCmdsErr(cmds, retErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retErr == nil {
|
||||||
|
retErr = fn(ctx, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
for hookIndex--; hookIndex >= 0; hookIndex-- {
|
||||||
|
if err := hs.hooks[hookIndex].AfterProcessPipeline(ctx, cmds); err != nil {
|
||||||
|
retErr = err
|
||||||
|
setCmdsErr(cmds, retErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs hooks) processTxPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, fn func(context.Context, []Cmder) error,
|
||||||
|
) error {
|
||||||
|
cmds = wrapMultiExec(ctx, cmds)
|
||||||
|
return hs.processPipeline(ctx, cmds, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type baseClient struct {
|
||||||
|
opt *Options
|
||||||
|
connPool pool.Pooler
|
||||||
|
|
||||||
|
onClose func() error // hook called when client is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBaseClient(opt *Options, connPool pool.Pooler) *baseClient {
|
||||||
|
return &baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: connPool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) clone() *baseClient {
|
||||||
|
clone := *c
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) withTimeout(timeout time.Duration) *baseClient {
|
||||||
|
opt := c.opt.clone()
|
||||||
|
opt.ReadTimeout = timeout
|
||||||
|
opt.WriteTimeout = timeout
|
||||||
|
|
||||||
|
clone := c.clone()
|
||||||
|
clone.opt = opt
|
||||||
|
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) String() string {
|
||||||
|
return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) newConn(ctx context.Context) (*pool.Conn, error) {
|
||||||
|
cn, err := c.connPool.NewConn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.initConn(ctx, cn)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.connPool.CloseConn(cn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) getConn(ctx context.Context) (*pool.Conn, error) {
|
||||||
|
if c.opt.Limiter != nil {
|
||||||
|
err := c.opt.Limiter.Allow()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, err := c._getConn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if c.opt.Limiter != nil {
|
||||||
|
c.opt.Limiter.ReportResult(err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) _getConn(ctx context.Context) (*pool.Conn, error) {
|
||||||
|
cn, err := c.connPool.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cn.Inited {
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.initConn(ctx, cn); err != nil {
|
||||||
|
c.connPool.Remove(ctx, cn, err)
|
||||||
|
if err := errors.Unwrap(err); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
if cn.Inited {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cn.Inited = true
|
||||||
|
|
||||||
|
if c.opt.Password == "" &&
|
||||||
|
c.opt.DB == 0 &&
|
||||||
|
!c.opt.readOnly &&
|
||||||
|
c.opt.OnConnect == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
connPool := pool.NewSingleConnPool(c.connPool, cn)
|
||||||
|
conn := newConn(ctx, c.opt, connPool)
|
||||||
|
|
||||||
|
_, err := conn.Pipelined(ctx, func(pipe Pipeliner) error {
|
||||||
|
if c.opt.Password != "" {
|
||||||
|
if c.opt.Username != "" {
|
||||||
|
pipe.AuthACL(ctx, c.opt.Username, c.opt.Password)
|
||||||
|
} else {
|
||||||
|
pipe.Auth(ctx, c.opt.Password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.DB > 0 {
|
||||||
|
pipe.Select(ctx, c.opt.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.readOnly {
|
||||||
|
pipe.ReadOnly(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.OnConnect != nil {
|
||||||
|
return c.opt.OnConnect(ctx, conn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) releaseConn(ctx context.Context, cn *pool.Conn, err error) {
|
||||||
|
if c.opt.Limiter != nil {
|
||||||
|
c.opt.Limiter.ReportResult(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isBadConn(err, false, c.opt.Addr) {
|
||||||
|
c.connPool.Remove(ctx, cn, err)
|
||||||
|
} else {
|
||||||
|
c.connPool.Put(ctx, cn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) withConn(
|
||||||
|
ctx context.Context, fn func(context.Context, *pool.Conn) error,
|
||||||
|
) error {
|
||||||
|
cn, err := c.getConn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.releaseConn(ctx, cn, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
done := ctx.Done() //nolint:ifshort
|
||||||
|
|
||||||
|
if done == nil {
|
||||||
|
err = fn(ctx, cn)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
go func() { errc <- fn(ctx, cn) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
_ = cn.Close()
|
||||||
|
// Wait for the goroutine to finish and send something.
|
||||||
|
<-errc
|
||||||
|
|
||||||
|
err = ctx.Err()
|
||||||
|
return err
|
||||||
|
case err = <-errc:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
attempt := attempt
|
||||||
|
|
||||||
|
retry, err := c._process(ctx, cmd, attempt)
|
||||||
|
if err == nil || !retry {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) _process(ctx context.Context, cmd Cmder, attempt int) (bool, error) {
|
||||||
|
if attempt > 0 {
|
||||||
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retryTimeout := uint32(1)
|
||||||
|
err := c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return writeCmd(wr, cmd)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cn.WithReader(ctx, c.cmdTimeout(cmd), cmd.readReply)
|
||||||
|
if err != nil {
|
||||||
|
if cmd.readTimeout() == nil {
|
||||||
|
atomic.StoreUint32(&retryTimeout, 1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
retry := shouldRetry(err, atomic.LoadUint32(&retryTimeout) == 1)
|
||||||
|
return retry, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
||||||
|
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration {
|
||||||
|
if timeout := cmd.readTimeout(); timeout != nil {
|
||||||
|
t := *timeout
|
||||||
|
if t == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return t + 10*time.Second
|
||||||
|
}
|
||||||
|
return c.opt.ReadTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the client, releasing any open resources.
|
||||||
|
//
|
||||||
|
// It is rare to Close a Client, as the Client is meant to be
|
||||||
|
// long-lived and shared between many goroutines.
|
||||||
|
func (c *baseClient) Close() error {
|
||||||
|
var firstErr error
|
||||||
|
if c.onClose != nil {
|
||||||
|
if err := c.onClose(); err != nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.connPool.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) getAddr() string {
|
||||||
|
return c.opt.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) processPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(ctx, cmds, c.pipelineProcessCmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(ctx, cmds, c.txPipelineProcessCmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineProcessor func(context.Context, *pool.Conn, []Cmder) (bool, error)
|
||||||
|
|
||||||
|
func (c *baseClient) generalProcessPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
||||||
|
) error {
|
||||||
|
err := c._generalProcessPipeline(ctx, cmds, p)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cmdsFirstErr(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) _generalProcessPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
||||||
|
) error {
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var canRetry bool
|
||||||
|
lastErr = c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
var err error
|
||||||
|
canRetry, err = p(ctx, cn, cmds)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if lastErr == nil || !canRetry || !shouldRetry(lastErr, true) {
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) pipelineProcessCmds(
|
||||||
|
ctx context.Context, cn *pool.Conn, cmds []Cmder,
|
||||||
|
) (bool, error) {
|
||||||
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return writeCmds(wr, cmds)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||||
|
return pipelineReadCmds(rd, cmds)
|
||||||
|
})
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func pipelineReadCmds(rd *proto.Reader, cmds []Cmder) error {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
err := cmd.readReply(rd)
|
||||||
|
cmd.SetErr(err)
|
||||||
|
if err != nil && !isRedisError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) txPipelineProcessCmds(
|
||||||
|
ctx context.Context, cn *pool.Conn, cmds []Cmder,
|
||||||
|
) (bool, error) {
|
||||||
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return writeCmds(wr, cmds)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||||
|
statusCmd := cmds[0].(*StatusCmd)
|
||||||
|
// Trim multi and exec.
|
||||||
|
cmds = cmds[1 : len(cmds)-1]
|
||||||
|
|
||||||
|
err := txPipelineReadQueued(rd, statusCmd, cmds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineReadCmds(rd, cmds)
|
||||||
|
})
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapMultiExec(ctx context.Context, cmds []Cmder) []Cmder {
|
||||||
|
if len(cmds) == 0 {
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
cmdCopy := make([]Cmder, len(cmds)+2)
|
||||||
|
cmdCopy[0] = NewStatusCmd(ctx, "multi")
|
||||||
|
copy(cmdCopy[1:], cmds)
|
||||||
|
cmdCopy[len(cmdCopy)-1] = NewSliceCmd(ctx, "exec")
|
||||||
|
return cmdCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
func txPipelineReadQueued(rd *proto.Reader, statusCmd *StatusCmd, cmds []Cmder) error {
|
||||||
|
// Parse queued replies.
|
||||||
|
if err := statusCmd.readReply(rd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for range cmds {
|
||||||
|
if err := statusCmd.readReply(rd); err != nil && !isRedisError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse number of replies.
|
||||||
|
line, err := rd.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == Nil {
|
||||||
|
err = TxFailedErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch line[0] {
|
||||||
|
case proto.ErrorReply:
|
||||||
|
return proto.ParseErrorReply(line)
|
||||||
|
case proto.ArrayReply:
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("redis: expected '*', but got line %q", line)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Client is a Redis client representing a pool of zero or more
|
||||||
|
// underlying connections. It's safe for concurrent use by multiple
|
||||||
|
// goroutines.
|
||||||
|
type Client struct {
|
||||||
|
*baseClient
|
||||||
|
cmdable
|
||||||
|
hooks
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a client to the Redis Server specified by Options.
|
||||||
|
func NewClient(opt *Options) *Client {
|
||||||
|
opt.init()
|
||||||
|
|
||||||
|
c := Client{
|
||||||
|
baseClient: newBaseClient(opt, newConnPool(opt)),
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
c.cmdable = c.Process
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clone() *Client {
|
||||||
|
clone := *c
|
||||||
|
clone.cmdable = clone.Process
|
||||||
|
clone.hooks.lock()
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WithTimeout(timeout time.Duration) *Client {
|
||||||
|
clone := c.clone()
|
||||||
|
clone.baseClient = c.baseClient.withTimeout(timeout)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Context() context.Context {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WithContext(ctx context.Context) *Client {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
clone := c.clone()
|
||||||
|
clone.ctx = ctx
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Conn(ctx context.Context) *Conn {
|
||||||
|
return newConn(ctx, c.opt, pool.NewStickyConnPool(c.connPool))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do creates a Cmd from the args and processes the cmd.
|
||||||
|
func (c *Client) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||||
|
cmd := NewCmd(ctx, args...)
|
||||||
|
_ = c.Process(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Process(ctx context.Context, cmd Cmder) error {
|
||||||
|
return c.hooks.process(ctx, cmd, c.baseClient.process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) processPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processPipeline(ctx, cmds, c.baseClient.processPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processTxPipeline(ctx, cmds, c.baseClient.processTxPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options returns read-only Options that were used to create the client.
|
||||||
|
func (c *Client) Options() *Options {
|
||||||
|
return c.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoolStats pool.Stats
|
||||||
|
|
||||||
|
// PoolStats returns connection pool stats.
|
||||||
|
func (c *Client) PoolStats() *PoolStats {
|
||||||
|
stats := c.connPool.Stats()
|
||||||
|
return (*PoolStats)(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||||
|
func (c *Client) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the client to the specified channels.
|
||||||
|
// Channels can be omitted to create empty subscription.
|
||||||
|
// Note that this method does not wait on a response from Redis, so the
|
||||||
|
// subscription may not be active immediately. To force the connection to wait,
|
||||||
|
// you may call the Receive() method on the returned *PubSub like so:
|
||||||
|
//
|
||||||
|
// sub := client.Subscribe(queryResp)
|
||||||
|
// iface, err := sub.Receive()
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Should be *Subscription, but others are possible if other actions have been
|
||||||
|
// // taken on sub since it was created.
|
||||||
|
// switch iface.(type) {
|
||||||
|
// case *Subscription:
|
||||||
|
// // subscribe succeeded
|
||||||
|
// case *Message:
|
||||||
|
// // received first message
|
||||||
|
// case *Pong:
|
||||||
|
// // pong received
|
||||||
|
// default:
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ch := sub.Channel()
|
||||||
|
func (c *Client) 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 *Client) PSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||||
|
pubsub := c.pubSub()
|
||||||
|
if len(channels) > 0 {
|
||||||
|
_ = pubsub.PSubscribe(ctx, channels...)
|
||||||
|
}
|
||||||
|
return pubsub
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
baseClient
|
||||||
|
cmdable
|
||||||
|
statefulCmdable
|
||||||
|
hooks // TODO: inherit hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents a single Redis connection rather than a pool of connections.
|
||||||
|
// Prefer running commands from Client unless there is a specific need
|
||||||
|
// for a continuous single Redis connection.
|
||||||
|
type Conn struct {
|
||||||
|
*conn
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConn(ctx context.Context, opt *Options, connPool pool.Pooler) *Conn {
|
||||||
|
c := Conn{
|
||||||
|
conn: &conn{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: connPool,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
c.cmdable = c.Process
|
||||||
|
c.statefulCmdable = c.Process
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Process(ctx context.Context, cmd Cmder) error {
|
||||||
|
return c.hooks.process(ctx, cmd, c.baseClient.process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) processPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processPipeline(ctx, cmds, c.baseClient.processPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processTxPipeline(ctx, cmds, c.baseClient.processTxPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||||
|
func (c *Conn) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
180
vendor/github.com/go-redis/redis/v8/result.go
generated
vendored
Normal file
180
vendor/github.com/go-redis/redis/v8/result.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// NewCmdResult returns a Cmd initialised with val and err for testing.
|
||||||
|
func NewCmdResult(val interface{}, err error) *Cmd {
|
||||||
|
var cmd Cmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSliceResult returns a SliceCmd initialised with val and err for testing.
|
||||||
|
func NewSliceResult(val []interface{}, err error) *SliceCmd {
|
||||||
|
var cmd SliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusResult returns a StatusCmd initialised with val and err for testing.
|
||||||
|
func NewStatusResult(val string, err error) *StatusCmd {
|
||||||
|
var cmd StatusCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIntResult returns an IntCmd initialised with val and err for testing.
|
||||||
|
func NewIntResult(val int64, err error) *IntCmd {
|
||||||
|
var cmd IntCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDurationResult returns a DurationCmd initialised with val and err for testing.
|
||||||
|
func NewDurationResult(val time.Duration, err error) *DurationCmd {
|
||||||
|
var cmd DurationCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBoolResult returns a BoolCmd initialised with val and err for testing.
|
||||||
|
func NewBoolResult(val bool, err error) *BoolCmd {
|
||||||
|
var cmd BoolCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringResult returns a StringCmd initialised with val and err for testing.
|
||||||
|
func NewStringResult(val string, err error) *StringCmd {
|
||||||
|
var cmd StringCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloatResult returns a FloatCmd initialised with val and err for testing.
|
||||||
|
func NewFloatResult(val float64, err error) *FloatCmd {
|
||||||
|
var cmd FloatCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing.
|
||||||
|
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
|
||||||
|
var cmd StringSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing.
|
||||||
|
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
|
||||||
|
var cmd BoolSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing.
|
||||||
|
func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd {
|
||||||
|
var cmd StringStringMapCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing.
|
||||||
|
func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd {
|
||||||
|
var cmd StringIntMapCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTimeCmdResult returns a TimeCmd initialised with val and err for testing.
|
||||||
|
func NewTimeCmdResult(val time.Time, err error) *TimeCmd {
|
||||||
|
var cmd TimeCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing.
|
||||||
|
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
|
||||||
|
var cmd ZSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZWithKeyCmdResult returns a NewZWithKeyCmd initialised with val and err for testing.
|
||||||
|
func NewZWithKeyCmdResult(val *ZWithKey, err error) *ZWithKeyCmd {
|
||||||
|
var cmd ZWithKeyCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScanCmdResult returns a ScanCmd initialised with val and err for testing.
|
||||||
|
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
|
||||||
|
var cmd ScanCmd
|
||||||
|
cmd.page = keys
|
||||||
|
cmd.cursor = cursor
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing.
|
||||||
|
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
|
||||||
|
var cmd ClusterSlotsCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing.
|
||||||
|
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
|
||||||
|
var cmd GeoLocationCmd
|
||||||
|
cmd.locations = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeoPosCmdResult returns a GeoPosCmd initialised with val and err for testing.
|
||||||
|
func NewGeoPosCmdResult(val []*GeoPos, err error) *GeoPosCmd {
|
||||||
|
var cmd GeoPosCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing.
|
||||||
|
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
|
||||||
|
var cmd CommandsInfoCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXMessageSliceCmdResult returns a XMessageSliceCmd initialised with val and err for testing.
|
||||||
|
func NewXMessageSliceCmdResult(val []XMessage, err error) *XMessageSliceCmd {
|
||||||
|
var cmd XMessageSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXStreamSliceCmdResult returns a XStreamSliceCmd initialised with val and err for testing.
|
||||||
|
func NewXStreamSliceCmdResult(val []XStream, err error) *XStreamSliceCmd {
|
||||||
|
var cmd XStreamSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.SetErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
736
vendor/github.com/go-redis/redis/v8/ring.go
generated
vendored
Normal file
736
vendor/github.com/go-redis/redis/v8/ring.go
generated
vendored
Normal file
@@ -0,0 +1,736 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
|
rendezvous "github.com/dgryski/go-rendezvous" //nolint
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal"
|
||||||
|
"github.com/go-redis/redis/v8/internal/hashtag"
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
"github.com/go-redis/redis/v8/internal/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errRingShardsDown = errors.New("redis: all ring shards are down")
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ConsistentHash interface {
|
||||||
|
Get(string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type rendezvousWrapper struct {
|
||||||
|
*rendezvous.Rendezvous
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w rendezvousWrapper) Get(key string) string {
|
||||||
|
return w.Lookup(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRendezvous(shards []string) ConsistentHash {
|
||||||
|
return rendezvousWrapper{rendezvous.New(shards, xxhash.Sum64String)}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// RingOptions are used to configure a ring client and should be
|
||||||
|
// passed to NewRing.
|
||||||
|
type RingOptions struct {
|
||||||
|
// Map of name => host:port addresses of ring shards.
|
||||||
|
Addrs map[string]string
|
||||||
|
|
||||||
|
// NewClient creates a shard client with provided name and options.
|
||||||
|
NewClient func(name string, opt *Options) *Client
|
||||||
|
|
||||||
|
// Frequency of PING commands sent to check shards availability.
|
||||||
|
// Shard is considered down after 3 subsequent failed checks.
|
||||||
|
HeartbeatFrequency time.Duration
|
||||||
|
|
||||||
|
// NewConsistentHash returns a consistent hash that is used
|
||||||
|
// to distribute keys across the shards.
|
||||||
|
//
|
||||||
|
// See https://medium.com/@dgryski/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8
|
||||||
|
// for consistent hashing algorithmic tradeoffs.
|
||||||
|
NewConsistentHash func(shards []string) ConsistentHash
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||||
|
PoolFIFO bool
|
||||||
|
|
||||||
|
PoolSize int
|
||||||
|
MinIdleConns int
|
||||||
|
MaxConnAge time.Duration
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
Limiter Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *RingOptions) init() {
|
||||||
|
if opt.NewClient == nil {
|
||||||
|
opt.NewClient = func(name string, opt *Options) *Client {
|
||||||
|
return NewClient(opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.HeartbeatFrequency == 0 {
|
||||||
|
opt.HeartbeatFrequency = 500 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.NewConsistentHash == nil {
|
||||||
|
opt.NewConsistentHash = newRendezvous
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.MaxRetries == -1 {
|
||||||
|
opt.MaxRetries = 0
|
||||||
|
} else if opt.MaxRetries == 0 {
|
||||||
|
opt.MaxRetries = 3
|
||||||
|
}
|
||||||
|
switch opt.MinRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MinRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||||
|
}
|
||||||
|
switch opt.MaxRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MaxRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *RingOptions) clientOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
Dialer: opt.Dialer,
|
||||||
|
OnConnect: opt.OnConnect,
|
||||||
|
|
||||||
|
Username: opt.Username,
|
||||||
|
Password: opt.Password,
|
||||||
|
DB: opt.DB,
|
||||||
|
|
||||||
|
MaxRetries: -1,
|
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout,
|
||||||
|
ReadTimeout: opt.ReadTimeout,
|
||||||
|
WriteTimeout: opt.WriteTimeout,
|
||||||
|
|
||||||
|
PoolFIFO: opt.PoolFIFO,
|
||||||
|
PoolSize: opt.PoolSize,
|
||||||
|
MinIdleConns: opt.MinIdleConns,
|
||||||
|
MaxConnAge: opt.MaxConnAge,
|
||||||
|
PoolTimeout: opt.PoolTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
||||||
|
|
||||||
|
TLSConfig: opt.TLSConfig,
|
||||||
|
Limiter: opt.Limiter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ringShard struct {
|
||||||
|
Client *Client
|
||||||
|
down int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRingShard(opt *RingOptions, name, addr string) *ringShard {
|
||||||
|
clopt := opt.clientOptions()
|
||||||
|
clopt.Addr = addr
|
||||||
|
|
||||||
|
return &ringShard{
|
||||||
|
Client: opt.NewClient(name, clopt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) String() string {
|
||||||
|
var state string
|
||||||
|
if shard.IsUp() {
|
||||||
|
state = "up"
|
||||||
|
} else {
|
||||||
|
state = "down"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s is %s", shard.Client, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) IsDown() bool {
|
||||||
|
const threshold = 3
|
||||||
|
return atomic.LoadInt32(&shard.down) >= threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) IsUp() bool {
|
||||||
|
return !shard.IsDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote votes to set shard state and returns true if state was changed.
|
||||||
|
func (shard *ringShard) Vote(up bool) bool {
|
||||||
|
if up {
|
||||||
|
changed := shard.IsDown()
|
||||||
|
atomic.StoreInt32(&shard.down, 0)
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
if shard.IsDown() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt32(&shard.down, 1)
|
||||||
|
return shard.IsDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ringShards struct {
|
||||||
|
opt *RingOptions
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
hash ConsistentHash
|
||||||
|
shards map[string]*ringShard // read only
|
||||||
|
list []*ringShard // read only
|
||||||
|
numShard int
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRingShards(opt *RingOptions) *ringShards {
|
||||||
|
shards := make(map[string]*ringShard, len(opt.Addrs))
|
||||||
|
list := make([]*ringShard, 0, len(shards))
|
||||||
|
|
||||||
|
for name, addr := range opt.Addrs {
|
||||||
|
shard := newRingShard(opt, name, addr)
|
||||||
|
shards[name] = shard
|
||||||
|
|
||||||
|
list = append(list, shard)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &ringShards{
|
||||||
|
opt: opt,
|
||||||
|
|
||||||
|
shards: shards,
|
||||||
|
list: list,
|
||||||
|
}
|
||||||
|
c.rebalance()
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) List() []*ringShard {
|
||||||
|
var list []*ringShard
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
if !c.closed {
|
||||||
|
list = c.list
|
||||||
|
}
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Hash(key string) string {
|
||||||
|
key = hashtag.Key(key)
|
||||||
|
|
||||||
|
var hash string
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
if c.numShard > 0 {
|
||||||
|
hash = c.hash.Get(key)
|
||||||
|
}
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) GetByKey(key string) (*ringShard, error) {
|
||||||
|
key = hashtag.Key(key)
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.numShard == 0 {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil, errRingShardsDown
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := c.hash.Get(key)
|
||||||
|
if hash == "" {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil, errRingShardsDown
|
||||||
|
}
|
||||||
|
|
||||||
|
shard := c.shards[hash]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
return shard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) GetByName(shardName string) (*ringShard, error) {
|
||||||
|
if shardName == "" {
|
||||||
|
return c.Random()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
shard := c.shards[shardName]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return shard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Random() (*ringShard, error) {
|
||||||
|
return c.GetByKey(strconv.Itoa(rand.Int()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeat monitors state of each shard in the ring.
|
||||||
|
func (c *ringShards) Heartbeat(frequency time.Duration) {
|
||||||
|
ticker := time.NewTicker(frequency)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
for range ticker.C {
|
||||||
|
var rebalance bool
|
||||||
|
|
||||||
|
for _, shard := range c.List() {
|
||||||
|
err := shard.Client.Ping(ctx).Err()
|
||||||
|
isUp := err == nil || err == pool.ErrPoolTimeout
|
||||||
|
if shard.Vote(isUp) {
|
||||||
|
internal.Logger.Printf(context.Background(), "ring shard state changed: %s", shard)
|
||||||
|
rebalance = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rebalance {
|
||||||
|
c.rebalance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rebalance removes dead shards from the Ring.
|
||||||
|
func (c *ringShards) rebalance() {
|
||||||
|
c.mu.RLock()
|
||||||
|
shards := c.shards
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
liveShards := make([]string, 0, len(shards))
|
||||||
|
|
||||||
|
for name, shard := range shards {
|
||||||
|
if shard.IsUp() {
|
||||||
|
liveShards = append(liveShards, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := c.opt.NewConsistentHash(liveShards)
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
c.hash = hash
|
||||||
|
c.numShard = len(liveShards)
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Len() int {
|
||||||
|
c.mu.RLock()
|
||||||
|
l := c.numShard
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for _, shard := range c.shards {
|
||||||
|
if err := shard.Client.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.hash = nil
|
||||||
|
c.shards = nil
|
||||||
|
c.list = nil
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ring struct {
|
||||||
|
opt *RingOptions
|
||||||
|
shards *ringShards
|
||||||
|
cmdsInfoCache *cmdsInfoCache //nolint:structcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ring is a Redis client that uses consistent hashing to distribute
|
||||||
|
// keys across multiple Redis servers (shards). It's safe for
|
||||||
|
// concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// Ring monitors the state of each shard and removes dead shards from
|
||||||
|
// the ring. When a shard comes online it is added back to the ring. This
|
||||||
|
// gives you maximum availability and partition tolerance, but no
|
||||||
|
// consistency between different shards or even clients. Each client
|
||||||
|
// uses shards that are available to the client and does not do any
|
||||||
|
// coordination when shard state is changed.
|
||||||
|
//
|
||||||
|
// Ring should be used when you need multiple Redis servers for caching
|
||||||
|
// and can tolerate losing data when one of the servers dies.
|
||||||
|
// Otherwise you should use Redis Cluster.
|
||||||
|
type Ring struct {
|
||||||
|
*ring
|
||||||
|
cmdable
|
||||||
|
hooks
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRing(opt *RingOptions) *Ring {
|
||||||
|
opt.init()
|
||||||
|
|
||||||
|
ring := Ring{
|
||||||
|
ring: &ring{
|
||||||
|
opt: opt,
|
||||||
|
shards: newRingShards(opt),
|
||||||
|
},
|
||||||
|
ctx: context.Background(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
|
||||||
|
ring.cmdable = ring.Process
|
||||||
|
|
||||||
|
go ring.shards.Heartbeat(opt.HeartbeatFrequency)
|
||||||
|
|
||||||
|
return &ring
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Context() context.Context {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) WithContext(ctx context.Context) *Ring {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
clone := *c
|
||||||
|
clone.cmdable = clone.Process
|
||||||
|
clone.hooks.lock()
|
||||||
|
clone.ctx = ctx
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do creates a Cmd from the args and processes the cmd.
|
||||||
|
func (c *Ring) Do(ctx context.Context, args ...interface{}) *Cmd {
|
||||||
|
cmd := NewCmd(ctx, args...)
|
||||||
|
_ = c.Process(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Process(ctx context.Context, cmd Cmder) error {
|
||||||
|
return c.hooks.process(ctx, cmd, c.process)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options returns read-only Options that were used to create the client.
|
||||||
|
func (c *Ring) Options() *RingOptions {
|
||||||
|
return c.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) retryBackoff(attempt int) time.Duration {
|
||||||
|
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolStats returns accumulated connection pool stats.
|
||||||
|
func (c *Ring) PoolStats() *PoolStats {
|
||||||
|
shards := c.shards.List()
|
||||||
|
var acc PoolStats
|
||||||
|
for _, shard := range shards {
|
||||||
|
s := shard.Client.connPool.Stats()
|
||||||
|
acc.Hits += s.Hits
|
||||||
|
acc.Misses += s.Misses
|
||||||
|
acc.Timeouts += s.Timeouts
|
||||||
|
acc.TotalConns += s.TotalConns
|
||||||
|
acc.IdleConns += s.IdleConns
|
||||||
|
}
|
||||||
|
return &acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the current number of shards in the ring.
|
||||||
|
func (c *Ring) Len() int {
|
||||||
|
return c.shards.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the client to the specified channels.
|
||||||
|
func (c *Ring) Subscribe(ctx context.Context, channels ...string) *PubSub {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
panic("at least one channel is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
shard, err := c.shards.GetByKey(channels[0])
|
||||||
|
if err != nil {
|
||||||
|
// TODO: return PubSub with sticky error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return shard.Client.Subscribe(ctx, channels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe subscribes the client to the given patterns.
|
||||||
|
func (c *Ring) PSubscribe(ctx context.Context, channels ...string) *PubSub {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
panic("at least one channel is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
shard, err := c.shards.GetByKey(channels[0])
|
||||||
|
if err != nil {
|
||||||
|
// TODO: return PubSub with sticky error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return shard.Client.PSubscribe(ctx, channels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEachShard concurrently calls the fn on each live shard in the ring.
|
||||||
|
// It returns the first error if any.
|
||||||
|
func (c *Ring) ForEachShard(
|
||||||
|
ctx context.Context,
|
||||||
|
fn func(ctx context.Context, client *Client) error,
|
||||||
|
) error {
|
||||||
|
shards := c.shards.List()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
for _, shard := range shards {
|
||||||
|
if shard.IsDown() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(shard *ringShard) {
|
||||||
|
defer wg.Done()
|
||||||
|
err := fn(ctx, shard.Client)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case errCh <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(shard)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) cmdsInfo(ctx context.Context) (map[string]*CommandInfo, error) {
|
||||||
|
shards := c.shards.List()
|
||||||
|
var firstErr error
|
||||||
|
for _, shard := range shards {
|
||||||
|
cmdsInfo, err := shard.Client.Command(ctx).Result()
|
||||||
|
if err == nil {
|
||||||
|
return cmdsInfo, nil
|
||||||
|
}
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstErr == nil {
|
||||||
|
return nil, errRingShardsDown
|
||||||
|
}
|
||||||
|
return nil, firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) cmdInfo(ctx context.Context, name string) *CommandInfo {
|
||||||
|
cmdsInfo, err := c.cmdsInfoCache.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
info := cmdsInfo[name]
|
||||||
|
if info == nil {
|
||||||
|
internal.Logger.Printf(c.Context(), "info for cmd=%s not found", name)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) cmdShard(ctx context.Context, cmd Cmder) (*ringShard, error) {
|
||||||
|
cmdInfo := c.cmdInfo(ctx, cmd.Name())
|
||||||
|
pos := cmdFirstKeyPos(cmd, cmdInfo)
|
||||||
|
if pos == 0 {
|
||||||
|
return c.shards.Random()
|
||||||
|
}
|
||||||
|
firstKey := cmd.stringArg(pos)
|
||||||
|
return c.shards.GetByKey(firstKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shard, err := c.cmdShard(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = shard.Client.Process(ctx, cmd)
|
||||||
|
if lastErr == nil || !shouldRetry(lastErr, cmd.readTimeout() == nil) {
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) processPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(ctx, cmds, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(ctx, cmds, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) generalProcessPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, tx bool,
|
||||||
|
) error {
|
||||||
|
cmdsMap := make(map[string][]Cmder)
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
cmdInfo := c.cmdInfo(ctx, cmd.Name())
|
||||||
|
hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
|
||||||
|
if hash != "" {
|
||||||
|
hash = c.shards.Hash(hash)
|
||||||
|
}
|
||||||
|
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for hash, cmds := range cmdsMap {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(hash string, cmds []Cmder) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
_ = c.processShardPipeline(ctx, hash, cmds, tx)
|
||||||
|
}(hash, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return cmdsFirstErr(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) processShardPipeline(
|
||||||
|
ctx context.Context, hash string, cmds []Cmder, tx bool,
|
||||||
|
) error {
|
||||||
|
// TODO: retry?
|
||||||
|
shard, err := c.shards.GetByName(hash)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tx {
|
||||||
|
return shard.Client.processTxPipeline(ctx, cmds)
|
||||||
|
}
|
||||||
|
return shard.Client.processPipeline(ctx, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return fmt.Errorf("redis: Watch requires at least one key")
|
||||||
|
}
|
||||||
|
|
||||||
|
var shards []*ringShard
|
||||||
|
for _, key := range keys {
|
||||||
|
if key != "" {
|
||||||
|
shard, err := c.shards.GetByKey(hashtag.Key(key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shards = append(shards, shard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(shards) == 0 {
|
||||||
|
return fmt.Errorf("redis: Watch requires at least one shard")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(shards) > 1 {
|
||||||
|
for _, shard := range shards[1:] {
|
||||||
|
if shard.Client != shards[0].Client {
|
||||||
|
err := fmt.Errorf("redis: Watch requires all keys to be in the same shard")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shards[0].Client.Watch(ctx, fn, keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the ring client, releasing any open resources.
|
||||||
|
//
|
||||||
|
// It is rare to Close a Ring, as the Ring is meant to be long-lived
|
||||||
|
// and shared between many goroutines.
|
||||||
|
func (c *Ring) Close() error {
|
||||||
|
return c.shards.Close()
|
||||||
|
}
|
||||||
65
vendor/github.com/go-redis/redis/v8/script.go
generated
vendored
Normal file
65
vendor/github.com/go-redis/redis/v8/script.go
generated
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
796
vendor/github.com/go-redis/redis/v8/sentinel.go
generated
vendored
Normal file
796
vendor/github.com/go-redis/redis/v8/sentinel.go
generated
vendored
Normal file
@@ -0,0 +1,796 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
// If specified with SentinelPassword, enables ACL-based authentication (via
|
||||||
|
// AUTH <user> <pass>).
|
||||||
|
SentinelUsername string
|
||||||
|
// Sentinel password from "requirepass <password>" (if enabled) in Sentinel
|
||||||
|
// configuration, or, if SentinelUsername is also supplied, used for ACL-based
|
||||||
|
// authentication.
|
||||||
|
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
|
||||||
|
|
||||||
|
// Use slaves disconnected with master when cannot get connected slaves
|
||||||
|
// Now, this option only works in RandomSlaveAddr function.
|
||||||
|
UseDisconnectedSlaves 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
|
||||||
|
|
||||||
|
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||||
|
PoolFIFO bool
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
PoolFIFO: opt.PoolFIFO,
|
||||||
|
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,
|
||||||
|
Username: opt.SentinelUsername,
|
||||||
|
Password: opt.SentinelPassword,
|
||||||
|
|
||||||
|
MaxRetries: opt.MaxRetries,
|
||||||
|
MinRetryBackoff: opt.MinRetryBackoff,
|
||||||
|
MaxRetryBackoff: opt.MaxRetryBackoff,
|
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout,
|
||||||
|
ReadTimeout: opt.ReadTimeout,
|
||||||
|
WriteTimeout: opt.WriteTimeout,
|
||||||
|
|
||||||
|
PoolFIFO: opt.PoolFIFO,
|
||||||
|
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,
|
||||||
|
|
||||||
|
PoolFIFO: opt.PoolFIFO,
|
||||||
|
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)
|
||||||
|
|
||||||
|
rand.Shuffle(len(sentinelAddrs), func(i, j int) {
|
||||||
|
sentinelAddrs[i], sentinelAddrs[j] = sentinelAddrs[j], sentinelAddrs[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
netDialer := &net.Dialer{
|
||||||
|
Timeout: failover.opt.DialTimeout,
|
||||||
|
KeepAlive: 5 * time.Minute,
|
||||||
|
}
|
||||||
|
if failover.opt.TLSConfig == nil {
|
||||||
|
return netDialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
return tls.DialWithDialer(netDialer, network, addr, failover.opt.TLSConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if c.opt == nil {
|
||||||
|
return "", errors.New("opt is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
addresses, err := c.slaveAddrs(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addresses) == 0 && c.opt.UseDisconnectedSlaves {
|
||||||
|
addresses, err = c.slaveAddrs(ctx, true)
|
||||||
|
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, useDisconnected bool) ([]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()
|
||||||
|
}
|
||||||
|
|
||||||
|
var sentinelReachable bool
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
sentinelReachable = true
|
||||||
|
addrs := parseSlaveAddrs(slaves, useDisconnected)
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Push working sentinel to the top.
|
||||||
|
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
|
||||||
|
c.setSentinel(ctx, sentinel)
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sentinelReachable {
|
||||||
|
return []string{}, 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, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSlaveAddrs(addrs []interface{}, keepDisconnected bool) []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":
|
||||||
|
isDown = true
|
||||||
|
case "disconnected":
|
||||||
|
if !keepDisconnected {
|
||||||
|
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 //nolint:ifshort
|
||||||
|
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{})
|
||||||
|
var ip, port string
|
||||||
|
for i := 0; i < len(vals); i += 2 {
|
||||||
|
key := vals[i].(string)
|
||||||
|
switch key {
|
||||||
|
case "ip":
|
||||||
|
ip = vals[i+1].(string)
|
||||||
|
case "port":
|
||||||
|
port = vals[i+1].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip != "" && port != "" {
|
||||||
|
sentinelAddr := net.JoinHostPort(ip, port)
|
||||||
|
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, false)
|
||||||
|
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
|
||||||
|
}
|
||||||
149
vendor/github.com/go-redis/redis/v8/tx.go
generated
vendored
Normal file
149
vendor/github.com/go-redis/redis/v8/tx.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8/internal/pool"
|
||||||
|
"github.com/go-redis/redis/v8/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TxFailedErr transaction redis failed.
|
||||||
|
const TxFailedErr = proto.RedisError("redis: transaction failed")
|
||||||
|
|
||||||
|
// Tx implements Redis transactions as described in
|
||||||
|
// http://redis.io/topics/transactions. It's NOT safe for concurrent use
|
||||||
|
// by multiple goroutines, because Exec resets list of watched keys.
|
||||||
|
//
|
||||||
|
// If you don't need WATCH, use Pipeline instead.
|
||||||
|
type Tx struct {
|
||||||
|
baseClient
|
||||||
|
cmdable
|
||||||
|
statefulCmdable
|
||||||
|
hooks
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newTx(ctx context.Context) *Tx {
|
||||||
|
tx := Tx{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: c.opt,
|
||||||
|
connPool: pool.NewStickyConnPool(c.connPool),
|
||||||
|
},
|
||||||
|
hooks: c.hooks.clone(),
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
tx.init()
|
||||||
|
return &tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) init() {
|
||||||
|
c.cmdable = c.Process
|
||||||
|
c.statefulCmdable = c.Process
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) Context() context.Context {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) WithContext(ctx context.Context) *Tx {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
clone := *c
|
||||||
|
clone.init()
|
||||||
|
clone.hooks.lock()
|
||||||
|
clone.ctx = ctx
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) Process(ctx context.Context, cmd Cmder) error {
|
||||||
|
return c.hooks.process(ctx, cmd, c.baseClient.process)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch prepares a transaction and marks the keys to be watched
|
||||||
|
// for conditional execution if there are any keys.
|
||||||
|
//
|
||||||
|
// The transaction is automatically closed when fn exits.
|
||||||
|
func (c *Client) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error {
|
||||||
|
tx := c.newTx(ctx)
|
||||||
|
defer tx.Close(ctx)
|
||||||
|
if len(keys) > 0 {
|
||||||
|
if err := tx.Watch(ctx, keys...).Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fn(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the transaction, releasing any open resources.
|
||||||
|
func (c *Tx) Close(ctx context.Context) error {
|
||||||
|
_ = c.Unwatch(ctx).Err()
|
||||||
|
return c.baseClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch marks the keys to be watched for conditional execution
|
||||||
|
// of a transaction.
|
||||||
|
func (c *Tx) Watch(ctx context.Context, keys ...string) *StatusCmd {
|
||||||
|
args := make([]interface{}, 1+len(keys))
|
||||||
|
args[0] = "watch"
|
||||||
|
for i, key := range keys {
|
||||||
|
args[1+i] = key
|
||||||
|
}
|
||||||
|
cmd := NewStatusCmd(ctx, args...)
|
||||||
|
_ = c.Process(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwatch flushes all the previously watched keys for a transaction.
|
||||||
|
func (c *Tx) Unwatch(ctx context.Context, keys ...string) *StatusCmd {
|
||||||
|
args := make([]interface{}, 1+len(keys))
|
||||||
|
args[0] = "unwatch"
|
||||||
|
for i, key := range keys {
|
||||||
|
args[1+i] = key
|
||||||
|
}
|
||||||
|
cmd := NewStatusCmd(ctx, args...)
|
||||||
|
_ = c.Process(ctx, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline creates a pipeline. Usually it is more convenient to use Pipelined.
|
||||||
|
func (c *Tx) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processPipeline(ctx, cmds, c.baseClient.processPipeline)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipelined executes commands queued in the fn outside of the transaction.
|
||||||
|
// Use TxPipelined if you need transactional behavior.
|
||||||
|
func (c *Tx) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipelined executes commands queued in the fn in the transaction.
|
||||||
|
//
|
||||||
|
// When using WATCH, EXEC will execute commands only if the watched keys
|
||||||
|
// were not modified, allowing for a check-and-set mechanism.
|
||||||
|
//
|
||||||
|
// Exec always returns list of commands. If transaction fails
|
||||||
|
// TxFailedErr is returned. Otherwise Exec returns an error of the first
|
||||||
|
// failed command or nil.
|
||||||
|
func (c *Tx) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(ctx, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipeline creates a pipeline. Usually it is more convenient to use TxPipelined.
|
||||||
|
func (c *Tx) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
ctx: c.ctx,
|
||||||
|
exec: func(ctx context.Context, cmds []Cmder) error {
|
||||||
|
return c.hooks.processTxPipeline(ctx, cmds, c.baseClient.processTxPipeline)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pipe.init()
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
213
vendor/github.com/go-redis/redis/v8/universal.go
generated
vendored
Normal file
213
vendor/github.com/go-redis/redis/v8/universal.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UniversalOptions information is required by UniversalClient to establish
|
||||||
|
// connections.
|
||||||
|
type UniversalOptions struct {
|
||||||
|
// Either a single address or a seed list of host:port addresses
|
||||||
|
// of cluster/sentinel nodes.
|
||||||
|
Addrs []string
|
||||||
|
|
||||||
|
// Database to be selected after connecting to the server.
|
||||||
|
// Only single-node and failover clients.
|
||||||
|
DB int
|
||||||
|
|
||||||
|
// Common options.
|
||||||
|
|
||||||
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
OnConnect func(ctx context.Context, cn *Conn) error
|
||||||
|
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
SentinelPassword string
|
||||||
|
|
||||||
|
MaxRetries int
|
||||||
|
MinRetryBackoff time.Duration
|
||||||
|
MaxRetryBackoff time.Duration
|
||||||
|
|
||||||
|
DialTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
|
||||||
|
PoolFIFO bool
|
||||||
|
|
||||||
|
PoolSize int
|
||||||
|
MinIdleConns int
|
||||||
|
MaxConnAge time.Duration
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
|
||||||
|
// Only cluster clients.
|
||||||
|
|
||||||
|
MaxRedirects int
|
||||||
|
ReadOnly bool
|
||||||
|
RouteByLatency bool
|
||||||
|
RouteRandomly bool
|
||||||
|
|
||||||
|
// The sentinel master name.
|
||||||
|
// Only failover clients.
|
||||||
|
|
||||||
|
MasterName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cluster returns cluster options created from the universal options.
|
||||||
|
func (o *UniversalOptions) Cluster() *ClusterOptions {
|
||||||
|
if len(o.Addrs) == 0 {
|
||||||
|
o.Addrs = []string{"127.0.0.1:6379"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ClusterOptions{
|
||||||
|
Addrs: o.Addrs,
|
||||||
|
Dialer: o.Dialer,
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
|
||||||
|
Username: o.Username,
|
||||||
|
Password: o.Password,
|
||||||
|
|
||||||
|
MaxRedirects: o.MaxRedirects,
|
||||||
|
ReadOnly: o.ReadOnly,
|
||||||
|
RouteByLatency: o.RouteByLatency,
|
||||||
|
RouteRandomly: o.RouteRandomly,
|
||||||
|
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
MinRetryBackoff: o.MinRetryBackoff,
|
||||||
|
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||||
|
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
PoolFIFO: o.PoolFIFO,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
MinIdleConns: o.MinIdleConns,
|
||||||
|
MaxConnAge: o.MaxConnAge,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
|
||||||
|
TLSConfig: o.TLSConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failover returns failover options created from the universal options.
|
||||||
|
func (o *UniversalOptions) Failover() *FailoverOptions {
|
||||||
|
if len(o.Addrs) == 0 {
|
||||||
|
o.Addrs = []string{"127.0.0.1:26379"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FailoverOptions{
|
||||||
|
SentinelAddrs: o.Addrs,
|
||||||
|
MasterName: o.MasterName,
|
||||||
|
|
||||||
|
Dialer: o.Dialer,
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
|
||||||
|
DB: o.DB,
|
||||||
|
Username: o.Username,
|
||||||
|
Password: o.Password,
|
||||||
|
SentinelPassword: o.SentinelPassword,
|
||||||
|
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
MinRetryBackoff: o.MinRetryBackoff,
|
||||||
|
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||||
|
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
|
||||||
|
PoolFIFO: o.PoolFIFO,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
MinIdleConns: o.MinIdleConns,
|
||||||
|
MaxConnAge: o.MaxConnAge,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
|
||||||
|
TLSConfig: o.TLSConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple returns basic options created from the universal options.
|
||||||
|
func (o *UniversalOptions) Simple() *Options {
|
||||||
|
addr := "127.0.0.1:6379"
|
||||||
|
if len(o.Addrs) > 0 {
|
||||||
|
addr = o.Addrs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Options{
|
||||||
|
Addr: addr,
|
||||||
|
Dialer: o.Dialer,
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
|
||||||
|
DB: o.DB,
|
||||||
|
Username: o.Username,
|
||||||
|
Password: o.Password,
|
||||||
|
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
MinRetryBackoff: o.MinRetryBackoff,
|
||||||
|
MaxRetryBackoff: o.MaxRetryBackoff,
|
||||||
|
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
|
||||||
|
PoolFIFO: o.PoolFIFO,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
MinIdleConns: o.MinIdleConns,
|
||||||
|
MaxConnAge: o.MaxConnAge,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
|
||||||
|
TLSConfig: o.TLSConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// UniversalClient is an abstract client which - based on the provided options -
|
||||||
|
// represents either a ClusterClient, a FailoverClient, or a single-node Client.
|
||||||
|
// This can be useful for testing cluster-specific applications locally or having different
|
||||||
|
// clients in different environments.
|
||||||
|
type UniversalClient interface {
|
||||||
|
Cmdable
|
||||||
|
Context() context.Context
|
||||||
|
AddHook(Hook)
|
||||||
|
Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error
|
||||||
|
Do(ctx context.Context, args ...interface{}) *Cmd
|
||||||
|
Process(ctx context.Context, cmd Cmder) error
|
||||||
|
Subscribe(ctx context.Context, channels ...string) *PubSub
|
||||||
|
PSubscribe(ctx context.Context, channels ...string) *PubSub
|
||||||
|
Close() error
|
||||||
|
PoolStats() *PoolStats
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ UniversalClient = (*Client)(nil)
|
||||||
|
_ UniversalClient = (*ClusterClient)(nil)
|
||||||
|
_ UniversalClient = (*Ring)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUniversalClient returns a new multi client. The type of the returned client depends
|
||||||
|
// on the following conditions:
|
||||||
|
//
|
||||||
|
// 1. If the MasterName option is specified, a sentinel-backed FailoverClient is returned.
|
||||||
|
// 2. if the number of Addrs is two or more, a ClusterClient is returned.
|
||||||
|
// 3. Otherwise, a single-node Client is returned.
|
||||||
|
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
||||||
|
if opts.MasterName != "" {
|
||||||
|
return NewFailoverClient(opts.Failover())
|
||||||
|
} else if len(opts.Addrs) > 1 {
|
||||||
|
return NewClusterClient(opts.Cluster())
|
||||||
|
}
|
||||||
|
return NewClient(opts.Simple())
|
||||||
|
}
|
||||||
6
vendor/github.com/go-redis/redis/v8/version.go
generated
vendored
Normal file
6
vendor/github.com/go-redis/redis/v8/version.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
// Version is the current release version.
|
||||||
|
func Version() string {
|
||||||
|
return "8.11.4"
|
||||||
|
}
|
||||||
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
48
vendor/github.com/hjson/hjson-go/.travis.yml
generated
vendored
Normal file
48
vendor/github.com/hjson/hjson-go/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- tip
|
||||||
|
install: true
|
||||||
|
script:
|
||||||
|
- go install github.com/hjson/hjson-go
|
||||||
|
- go install github.com/hjson/hjson-go/hjson-cli
|
||||||
|
- go test -v github.com/hjson/hjson-go
|
||||||
|
before_deploy:
|
||||||
|
- ./build_release.sh
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
file:
|
||||||
|
- _dist/darwin_386.tar.gz
|
||||||
|
- _dist/darwin_amd64.tar.gz
|
||||||
|
- _dist/dragonfly_amd64.tar.gz
|
||||||
|
- _dist/freebsd_386.tar.gz
|
||||||
|
- _dist/freebsd_amd64.tar.gz
|
||||||
|
- _dist/freebsd_arm.tar.gz
|
||||||
|
- _dist/linux_386.tar.gz
|
||||||
|
- _dist/linux_amd64.tar.gz
|
||||||
|
- _dist/linux_arm.tar.gz
|
||||||
|
- _dist/linux_arm64.tar.gz
|
||||||
|
- _dist/linux_mips64.tar.gz
|
||||||
|
- _dist/linux_mips64le.tar.gz
|
||||||
|
- _dist/linux_ppc64.tar.gz
|
||||||
|
- _dist/linux_ppc64le.tar.gz
|
||||||
|
- _dist/netbsd_386.tar.gz
|
||||||
|
- _dist/netbsd_amd64.tar.gz
|
||||||
|
- _dist/netbsd_arm.tar.gz
|
||||||
|
- _dist/openbsd_386.tar.gz
|
||||||
|
- _dist/openbsd_amd64.tar.gz
|
||||||
|
- _dist/openbsd_arm.tar.gz
|
||||||
|
- _dist/plan9_386.tar.gz
|
||||||
|
- _dist/plan9_amd64.tar.gz
|
||||||
|
- _dist/solaris_amd64.tar.gz
|
||||||
|
- _dist/windows_386.zip
|
||||||
|
- _dist/windows_amd64.zip
|
||||||
|
overwrite: true
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
go: 1.7
|
||||||
|
api-key:
|
||||||
|
secure: RAnrBG7xantKdUBlIsv8Od9yZ0GTwCsJ7d1V+hSEC3JSz76+Ie4v1nJw+rD0Rp25zxDAM2qSkMfIaQJVSQ5tYg3N8C/d+/lfeRNBwLhHEsQztsaE5BU0I/qrkKq0pGMoG+cu6ZJ06xEgECPKOamwQ2wSY0IiIwVn0eNN/ufMsZUTmPgBE4+lx9eEWYDqMxy4tfgLMC/ry2fnA3zc4fQk0TboodLkI0SgwMq7cHcm95j9kHWpydbwr8IhQ6pucLM9YuKGv2fNHywlQ6Z9rhpYdiK/9KUofW20Rm4OB1MBHLLhLdQ0qCkVPswhGRdFPcBcN7lvv8Czcp3Hl4LQ4UzxOXf58mEWbh4r6VbUjZgRnpMy8Ms9CcyLjwaRbJPKA/kI53yjeEViFbDlOtBV8qdVqx5orCUrpU2ML/iEltVDL7pukhjuKd0K/2c3s8gJwH4LaGEfQjj2DvYkQgK0XsWSOXhYF4LaN0qhiQ3xiss1bhjJIIIhD3PHPy+dCsazKoJtNRHoUyP9RYz8EjcwI7BsaEEzskaXuUSvLz3+k6f6DZvPydUDXDGYqX0H2dXCT5H0IaCrSAkDVcGageXt4vkzKb45/el1W5VME/q/WpGAIPfsJ2beR7ALlm1RhXdWTJpwq6LM/YbYy/6U/HlqQCwB4x/nwKXy1ToDukCrHy57yGU=
|
||||||
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016, 2017 Christian Zangl
|
||||||
|
|
||||||
|
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.
|
||||||
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# hjson-go
|
||||||
|
|
||||||
|
[](https://travis-ci.org/hjson/hjson-go)
|
||||||
|
[](https://github.com/hjson/hjson-go/releases)
|
||||||
|
[](https://goreportcard.com/report/github.com/hjson/hjson-go)
|
||||||
|
[](http://gocover.io/github.com/hjson/hjson-go/)
|
||||||
|
[](https://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
# specify rate in requests/second (because comments are helpful!)
|
||||||
|
rate: 1000
|
||||||
|
|
||||||
|
// prefer c-style comments?
|
||||||
|
/* feeling old fashioned? */
|
||||||
|
|
||||||
|
# did you notice that rate doesn't need quotes?
|
||||||
|
hey: look ma, no quotes for strings either!
|
||||||
|
|
||||||
|
# best of all
|
||||||
|
notice: []
|
||||||
|
anything: ?
|
||||||
|
|
||||||
|
# yes, commas are optional!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.github.io](https://hjson.github.io).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Make sure you have a working Go environment. See the [install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
1. Get the sources
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/hjson/hjson-go
|
||||||
|
```
|
||||||
|
2. Build the **hjson-cli** commandline tool (optional)
|
||||||
|
```bash
|
||||||
|
$ cd $(go env GOPATH)/src/github.com/hjson/hjson-go/hjson-cli && go install
|
||||||
|
$ hjson-cli --version
|
||||||
|
```
|
||||||
|
# Usage as command line tool
|
||||||
|
```
|
||||||
|
usage: hjson-cli [OPTIONS] [INPUT]
|
||||||
|
hjson can be used to convert JSON from/to Hjson.
|
||||||
|
|
||||||
|
hjson will read the given JSON/Hjson input file or read from stdin.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-allowMinusZero
|
||||||
|
Allow -0.
|
||||||
|
-bracesSameLine
|
||||||
|
Print braces on the same line.
|
||||||
|
-c Output as JSON.
|
||||||
|
-h Show this screen.
|
||||||
|
-indentBy string
|
||||||
|
The indent string. (default " ")
|
||||||
|
-j Output as formatted JSON.
|
||||||
|
-omitRootBraces
|
||||||
|
Omit braces at the root.
|
||||||
|
-quoteAlways
|
||||||
|
Always quote string values.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample:
|
||||||
|
- run `hjson-cli test.json > test.hjson` to convert to Hjson
|
||||||
|
- run `hjson-cli -j test.hjson > test.json` to convert to JSON
|
||||||
|
|
||||||
|
# Usage as a GO library
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Now let's look at decoding Hjson data into Go
|
||||||
|
// values.
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// We need to provide a variable where Hjson
|
||||||
|
// can put the decoded data.
|
||||||
|
var dat map[string]interface{}
|
||||||
|
|
||||||
|
// Decode and a check for errors.
|
||||||
|
if err := hjson.Unmarshal(sampleText, &dat); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(dat)
|
||||||
|
|
||||||
|
// In order to use the values in the decoded map,
|
||||||
|
// we'll need to cast them to their appropriate type.
|
||||||
|
|
||||||
|
rate := dat["rate"].(float64)
|
||||||
|
fmt.Println(rate)
|
||||||
|
|
||||||
|
array := dat["array"].([]interface{})
|
||||||
|
str1 := array[0].(string)
|
||||||
|
fmt.Println(str1)
|
||||||
|
|
||||||
|
|
||||||
|
// To encode to Hjson with default options:
|
||||||
|
sampleMap := map[string]int{"apple": 5, "lettuce": 7}
|
||||||
|
hjson, _ := hjson.Marshal(sampleMap)
|
||||||
|
// this is short for:
|
||||||
|
// options := hjson.DefaultOptions()
|
||||||
|
// hjson, _ := hjson.MarshalWithOptions(sampleMap, options)
|
||||||
|
fmt.Println(string(hjson))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you prefer, you can also unmarshal to Go objects by converting to JSON:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sample struct {
|
||||||
|
Rate int
|
||||||
|
Array []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// read Hjson
|
||||||
|
var dat map[string]interface{}
|
||||||
|
hjson.Unmarshal(sampleText, &dat)
|
||||||
|
|
||||||
|
// convert to JSON
|
||||||
|
b, _ := json.Marshal(dat)
|
||||||
|
|
||||||
|
// unmarshal
|
||||||
|
var sample Sample
|
||||||
|
json.Unmarshal(b, &sample)
|
||||||
|
|
||||||
|
fmt.Println(sample.Rate)
|
||||||
|
fmt.Println(sample.Array)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
[](http://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|
# History
|
||||||
|
|
||||||
|
[see releases](https://github.com/hjson/hjson-go/releases)
|
||||||
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd `dirname $0`
|
||||||
|
ROOT=$PWD/_dist
|
||||||
|
|
||||||
|
if [ -d "$ROOT" ]; then rm -rf $ROOT; fi
|
||||||
|
|
||||||
|
mkdir $ROOT
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
export GOOS=$1
|
||||||
|
export GOARCH=$2
|
||||||
|
|
||||||
|
echo build $GOOS $GOARCH
|
||||||
|
OUT=$ROOT/${GOOS}_${GOARCH}
|
||||||
|
mkdir $OUT
|
||||||
|
cd $OUT
|
||||||
|
go build github.com/hjson/hjson-go/hjson-cli
|
||||||
|
if [[ $3 == "zip" ]]; then
|
||||||
|
mv $OUT/hjson-cli.exe $OUT/hjson.exe
|
||||||
|
zip -j ${OUT}.zip $OUT/*
|
||||||
|
else
|
||||||
|
mv $OUT/hjson-cli $OUT/hjson
|
||||||
|
tar -czf ${OUT}.tar.gz -C $OUT .
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# not all targets can be built on travis
|
||||||
|
|
||||||
|
# build android arm
|
||||||
|
build darwin 386
|
||||||
|
build darwin amd64
|
||||||
|
# build darwin arm
|
||||||
|
# build darwin arm64
|
||||||
|
build dragonfly amd64
|
||||||
|
build freebsd 386
|
||||||
|
build freebsd amd64
|
||||||
|
build freebsd arm
|
||||||
|
build linux 386
|
||||||
|
build linux amd64
|
||||||
|
build linux arm
|
||||||
|
build linux arm64
|
||||||
|
build linux mips64
|
||||||
|
build linux mips64le
|
||||||
|
build linux ppc64
|
||||||
|
build linux ppc64le
|
||||||
|
build netbsd 386
|
||||||
|
build netbsd amd64
|
||||||
|
build netbsd arm
|
||||||
|
build openbsd 386
|
||||||
|
build openbsd amd64
|
||||||
|
build openbsd arm
|
||||||
|
build plan9 386
|
||||||
|
build plan9 amd64
|
||||||
|
build solaris amd64
|
||||||
|
build windows 386 zip
|
||||||
|
build windows amd64 zip
|
||||||
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hjsonParser struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) resetAt() {
|
||||||
|
p.at = 0
|
||||||
|
p.ch = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPunctuatorChar(c byte) bool {
|
||||||
|
return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) errAt(message string) error {
|
||||||
|
var i int
|
||||||
|
col := 0
|
||||||
|
line := 1
|
||||||
|
for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- {
|
||||||
|
col++
|
||||||
|
}
|
||||||
|
for ; i > 0; i-- {
|
||||||
|
if p.data[i] == '\n' {
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
samEnd := p.at - col + 20
|
||||||
|
if samEnd > len(p.data) {
|
||||||
|
samEnd = len(p.data)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
if p.at < len(p.data) {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.ch = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[p.at+offs]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var escapee = map[byte]byte{
|
||||||
|
'"': '"',
|
||||||
|
'\'': '\'',
|
||||||
|
'\\': '\\',
|
||||||
|
'/': '/',
|
||||||
|
'b': '\b',
|
||||||
|
'f': '\f',
|
||||||
|
'n': '\n',
|
||||||
|
'r': '\r',
|
||||||
|
't': '\t',
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readString(allowML bool) (string, error) {
|
||||||
|
|
||||||
|
// Parse a string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// callers make sure that (ch === '"' || ch === "'")
|
||||||
|
// When parsing for string values, we must look for " and \ characters.
|
||||||
|
exitCh := p.ch
|
||||||
|
for p.next() {
|
||||||
|
if p.ch == exitCh {
|
||||||
|
p.next()
|
||||||
|
if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 {
|
||||||
|
// ''' indicates a multiline string
|
||||||
|
p.next()
|
||||||
|
return p.readMLString()
|
||||||
|
} else {
|
||||||
|
return res.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\\' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == 'u' {
|
||||||
|
uffff := 0
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p.next()
|
||||||
|
var hex int
|
||||||
|
if p.ch >= '0' && p.ch <= '9' {
|
||||||
|
hex = int(p.ch - '0')
|
||||||
|
} else if p.ch >= 'a' && p.ch <= 'f' {
|
||||||
|
hex = int(p.ch - 'a' + 0xa)
|
||||||
|
} else if p.ch >= 'A' && p.ch <= 'F' {
|
||||||
|
hex = int(p.ch - 'A' + 0xa)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad \\u char " + string(p.ch))
|
||||||
|
}
|
||||||
|
uffff = uffff*16 + hex
|
||||||
|
}
|
||||||
|
res.WriteRune(rune(uffff))
|
||||||
|
} else if ech, ok := escapee[p.ch]; ok {
|
||||||
|
res.WriteByte(ech)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad escape \\" + string(p.ch))
|
||||||
|
}
|
||||||
|
} else if p.ch == '\n' || p.ch == '\r' {
|
||||||
|
return "", p.errAt("Bad string containing newline")
|
||||||
|
} else {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", p.errAt("Bad string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readMLString() (value string, err error) {
|
||||||
|
|
||||||
|
// Parse a multiline string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
triple := 0
|
||||||
|
|
||||||
|
// we are at ''' +1 - get indent
|
||||||
|
indent := 0
|
||||||
|
for {
|
||||||
|
c := p.peek(-indent - 5)
|
||||||
|
if c == 0 || c == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indent++
|
||||||
|
}
|
||||||
|
|
||||||
|
skipIndent := func() {
|
||||||
|
skip := indent
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 {
|
||||||
|
skip--
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When parsing multiline string values, we must look for ' characters.
|
||||||
|
lastLf := false
|
||||||
|
for {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Bad multiline string")
|
||||||
|
} else if p.ch == '\'' {
|
||||||
|
triple++
|
||||||
|
p.next()
|
||||||
|
if triple == 3 {
|
||||||
|
sres := res.Bytes()
|
||||||
|
if lastLf {
|
||||||
|
return string(sres[0 : len(sres)-1]), nil // remove last EOL
|
||||||
|
}
|
||||||
|
return string(sres), nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
for triple > 0 {
|
||||||
|
res.WriteByte('\'')
|
||||||
|
triple--
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
res.WriteByte('\n')
|
||||||
|
lastLf = true
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
} else {
|
||||||
|
if p.ch != '\r' {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readKeyname() (string, error) {
|
||||||
|
|
||||||
|
// quotes for keys are optional in Hjson
|
||||||
|
// unless they include {}[],: or whitespace.
|
||||||
|
|
||||||
|
if p.ch == '"' || p.ch == '\'' {
|
||||||
|
return p.readString(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := new(bytes.Buffer)
|
||||||
|
start := p.at
|
||||||
|
space := -1
|
||||||
|
for {
|
||||||
|
if p.ch == ':' {
|
||||||
|
if name.Len() == 0 {
|
||||||
|
return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)")
|
||||||
|
} else if space >= 0 && space != name.Len() {
|
||||||
|
p.at = start + space
|
||||||
|
return "", p.errAt("Found whitespace in your key name (use quotes to include)")
|
||||||
|
}
|
||||||
|
return name.String(), nil
|
||||||
|
} else if p.ch <= ' ' {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Found EOF while looking for a key name (check your syntax)")
|
||||||
|
}
|
||||||
|
if space < 0 {
|
||||||
|
space = name.Len()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)")
|
||||||
|
}
|
||||||
|
name.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) white() {
|
||||||
|
for p.ch > 0 {
|
||||||
|
// Skip whitespace.
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
// Hjson allows comments
|
||||||
|
if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
|
||||||
|
for p.ch > 0 && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else if p.ch == '/' && p.peek(0) == '*' {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch > 0 {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readTfnns() (interface{}, error) {
|
||||||
|
|
||||||
|
// Hjson strings can be quoteless
|
||||||
|
// returns string, true, false, or null.
|
||||||
|
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)")
|
||||||
|
}
|
||||||
|
chf := p.ch
|
||||||
|
value := new(bytes.Buffer)
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.next()
|
||||||
|
isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0
|
||||||
|
if isEol ||
|
||||||
|
p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' ||
|
||||||
|
p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
switch chf {
|
||||||
|
case 'f':
|
||||||
|
if strings.TrimSpace(value.String()) == "false" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
if strings.TrimSpace(value.String()) == "null" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case 't':
|
||||||
|
if strings.TrimSpace(value.String()) == "true" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if chf == '-' || chf >= '0' && chf <= '9' {
|
||||||
|
if n, err := tryParseNumber(value.Bytes(), false); err == nil {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isEol {
|
||||||
|
// remove any whitespace at the end (ignored in quoteless strings)
|
||||||
|
return strings.TrimSpace(value.String()), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readArray() (value interface{}, err error) {
|
||||||
|
|
||||||
|
// Parse an array value.
|
||||||
|
// assuming ch == '['
|
||||||
|
|
||||||
|
array := make([]interface{}, 0, 1)
|
||||||
|
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil // empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
for p.ch > 0 {
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
array = append(array, val)
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readObject(withoutBraces bool) (value interface{}, err error) {
|
||||||
|
// Parse an object value.
|
||||||
|
|
||||||
|
object := make(map[string]interface{})
|
||||||
|
|
||||||
|
if !withoutBraces {
|
||||||
|
// assuming ch == '{'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil // empty object
|
||||||
|
}
|
||||||
|
for p.ch > 0 {
|
||||||
|
var key string
|
||||||
|
if key, err = p.readKeyname(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch != ':' {
|
||||||
|
return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
// duplicate keys overwrite the previous value
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
object[key] = val
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
if withoutBraces {
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readValue() (interface{}, error) {
|
||||||
|
|
||||||
|
// Parse a Hjson value. It could be an object, an array, a string, a number or a word.
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.readObject(false)
|
||||||
|
case '[':
|
||||||
|
return p.readArray()
|
||||||
|
case '"', '\'':
|
||||||
|
return p.readString(true)
|
||||||
|
default:
|
||||||
|
return p.readTfnns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) rootValue() (interface{}, error) {
|
||||||
|
// Braces for the root object are optional
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.checkTrailing(p.readObject(false))
|
||||||
|
case '[':
|
||||||
|
return p.checkTrailing(p.readArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume we have a root object without braces
|
||||||
|
res, err := p.checkTrailing(p.readObject(true))
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
|
||||||
|
p.resetAt()
|
||||||
|
if res2, err2 := p.checkTrailing(p.readValue()); err2 == nil {
|
||||||
|
return res2, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch > 0 {
|
||||||
|
return nil, p.errAt("Syntax error, found trailing characters")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the Hjson-encoded data and stores the result
|
||||||
|
// in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Unmarshal uses the inverse of the encodings that
|
||||||
|
// Marshal uses, allocating maps, slices, and pointers as necessary.
|
||||||
|
//
|
||||||
|
func Unmarshal(data []byte, v interface{}) (err error) {
|
||||||
|
var value interface{}
|
||||||
|
parser := &hjsonParser{data, 0, ' '}
|
||||||
|
parser.resetAt()
|
||||||
|
value, err = parser.rootValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
||||||
|
return fmt.Errorf("non-pointer %v", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
for rv.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
rv.Set(reflect.ValueOf(value))
|
||||||
|
return err
|
||||||
|
}
|
||||||
468
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
468
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncoderOptions defines options for encoding to Hjson.
|
||||||
|
type EncoderOptions struct {
|
||||||
|
// End of line, should be either \n or \r\n
|
||||||
|
Eol string
|
||||||
|
// Place braces on the same line
|
||||||
|
BracesSameLine bool
|
||||||
|
// Deprecated: Hjson always emits braces
|
||||||
|
EmitRootBraces bool
|
||||||
|
// Always place string in quotes
|
||||||
|
QuoteAlways bool
|
||||||
|
// Indent string
|
||||||
|
IndentBy string
|
||||||
|
// Allow the -0 value (unlike ES6)
|
||||||
|
AllowMinusZero bool
|
||||||
|
// Encode unknown values as 'null'
|
||||||
|
UnknownAsNull bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOptions returns the default encoding options.
|
||||||
|
func DefaultOptions() EncoderOptions {
|
||||||
|
opt := EncoderOptions{}
|
||||||
|
opt.Eol = "\n"
|
||||||
|
opt.BracesSameLine = false
|
||||||
|
opt.EmitRootBraces = true
|
||||||
|
opt.QuoteAlways = false
|
||||||
|
opt.IndentBy = " "
|
||||||
|
opt.AllowMinusZero = false
|
||||||
|
opt.UnknownAsNull = false
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
type hjsonEncoder struct {
|
||||||
|
bytes.Buffer // output
|
||||||
|
EncoderOptions
|
||||||
|
indent int
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsEscape, needsQuotes, needsEscapeML, startsWithKeyword, needsEscapeName *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var commonRange = `\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}`
|
||||||
|
// needsEscape tests if the string can be written without escapes
|
||||||
|
needsEscape = regexp.MustCompile(`[\\\"\x00-\x1f` + commonRange + `]`)
|
||||||
|
// needsQuotes tests if the string can be written as a quoteless string (includes needsEscape but without \\ and \")
|
||||||
|
needsQuotes = regexp.MustCompile(`^\s|^"|^'|^#|^/\*|^//|^\{|^\}|^\[|^\]|^:|^,|\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}]`)
|
||||||
|
// needsEscapeML tests if the string can be written as a multiline string (like needsEscape but without \n, \r, \\, \", \t)
|
||||||
|
var x08Or9 = `\x08` // `\x09` for the old behavior
|
||||||
|
needsEscapeML = regexp.MustCompile(`'''|^[\s]+$|[\x00-` + x08Or9 + `\x0b\x0c\x0e-\x1f` + commonRange + `]`)
|
||||||
|
// starts with a keyword and optionally is followed by a comment
|
||||||
|
startsWithKeyword = regexp.MustCompile(`^(true|false|null)\s*((,|\]|\}|#|//|/\*).*)?$`)
|
||||||
|
needsEscapeName = regexp.MustCompile(`[,\{\[\}\]\s:#"']|//|/\*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta = map[byte][]byte{
|
||||||
|
// table of character substitutions
|
||||||
|
'\b': []byte("\\b"),
|
||||||
|
'\t': []byte("\\t"),
|
||||||
|
'\n': []byte("\\n"),
|
||||||
|
'\f': []byte("\\f"),
|
||||||
|
'\r': []byte("\\r"),
|
||||||
|
'"': []byte("\\\""),
|
||||||
|
'\\': []byte("\\\\"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteReplace(text string) string {
|
||||||
|
return string(needsEscape.ReplaceAllFunc([]byte(text), func(a []byte) []byte {
|
||||||
|
c := meta[a[0]]
|
||||||
|
if c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
r, _ := utf8.DecodeRune(a)
|
||||||
|
return []byte(fmt.Sprintf("\\u%04x", r))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quote(value string, separator string, isRootObject bool) {
|
||||||
|
|
||||||
|
// Check if we can insert this string without quotes
|
||||||
|
// see hjson syntax (must not parse as true, false, null or number)
|
||||||
|
|
||||||
|
if len(value) == 0 {
|
||||||
|
e.WriteString(separator + `""`)
|
||||||
|
} else if e.QuoteAlways ||
|
||||||
|
needsQuotes.MatchString(value) ||
|
||||||
|
startsWithNumber([]byte(value)) ||
|
||||||
|
startsWithKeyword.MatchString(value) {
|
||||||
|
|
||||||
|
// If the string contains no control characters, no quote characters, and no
|
||||||
|
// backslash characters, then we can safely slap some quotes around it.
|
||||||
|
// Otherwise we first check if the string can be expressed in multiline
|
||||||
|
// format or we must replace the offending characters with safe escape
|
||||||
|
// sequences.
|
||||||
|
|
||||||
|
if !needsEscape.MatchString(value) {
|
||||||
|
e.WriteString(separator + `"` + value + `"`)
|
||||||
|
} else if !needsEscapeML.MatchString(value) && !isRootObject {
|
||||||
|
e.mlString(value, separator)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator + `"` + e.quoteReplace(value) + `"`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// return without quotes
|
||||||
|
e.WriteString(separator + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) mlString(value string, separator string) {
|
||||||
|
// wrap the string into the ''' (multiline) format
|
||||||
|
|
||||||
|
a := strings.Split(strings.Replace(value, "\r", "", -1), "\n")
|
||||||
|
|
||||||
|
if len(a) == 1 {
|
||||||
|
// The string contains only a single line. We still use the multiline
|
||||||
|
// format as it avoids escaping the \ character (e.g. when used in a
|
||||||
|
// regex).
|
||||||
|
e.WriteString(separator + "'''")
|
||||||
|
e.WriteString(a[0])
|
||||||
|
} else {
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
e.WriteString("'''")
|
||||||
|
for _, v := range a {
|
||||||
|
indent := e.indent + 1
|
||||||
|
if len(v) == 0 {
|
||||||
|
indent = 0
|
||||||
|
}
|
||||||
|
e.writeIndent(indent)
|
||||||
|
e.WriteString(v)
|
||||||
|
}
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
}
|
||||||
|
e.WriteString("'''")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteName(name string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return `""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can insert this name without quotes
|
||||||
|
|
||||||
|
if needsEscapeName.MatchString(name) {
|
||||||
|
if needsEscape.MatchString(name) {
|
||||||
|
name = e.quoteReplace(name)
|
||||||
|
}
|
||||||
|
return `"` + name + `"`
|
||||||
|
}
|
||||||
|
// without quotes
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortAlpha []reflect.Value
|
||||||
|
|
||||||
|
func (s sortAlpha) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Less(i, j int) bool {
|
||||||
|
return s[i].String() < s[j].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) writeIndent(indent int) {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
for i := 0; i < indent; i++ {
|
||||||
|
e.WriteString(e.IndentBy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) useMarshaler(value reflect.Value, separator string) error {
|
||||||
|
b, err := value.Interface().(json.Marshaler).MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshaler = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, isRootObject bool) error {
|
||||||
|
|
||||||
|
// Produce a string from value.
|
||||||
|
|
||||||
|
kind := value.Kind()
|
||||||
|
|
||||||
|
if kind == reflect.Interface || kind == reflect.Ptr {
|
||||||
|
if value.IsNil() {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
|
kind = value.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Type().Implements(marshaler) {
|
||||||
|
return e.useMarshaler(value, separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
e.quote(value.String(), separator, isRootObject)
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatInt(value.Int(), 10))
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Uintptr:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatUint(value.Uint(), 10))
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||||
|
e.WriteString(separator)
|
||||||
|
number := value.Float()
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
e.WriteString("null")
|
||||||
|
} else if !e.AllowMinusZero && number == -0 {
|
||||||
|
e.WriteString("0")
|
||||||
|
} else {
|
||||||
|
// find shortest representation ('G' does not work)
|
||||||
|
val := strconv.FormatFloat(number, 'f', -1, 64)
|
||||||
|
exp := strconv.FormatFloat(number, 'E', -1, 64)
|
||||||
|
if len(exp) < len(val) {
|
||||||
|
val = strings.ToLower(exp)
|
||||||
|
}
|
||||||
|
e.WriteString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
e.WriteString(separator)
|
||||||
|
if value.Bool() {
|
||||||
|
e.WriteString("true")
|
||||||
|
} else {
|
||||||
|
e.WriteString("false")
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("[]")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
e.indent++
|
||||||
|
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
e.WriteString("[")
|
||||||
|
|
||||||
|
// Join all of the element texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
if err := e.str(value.Index(i), true, "", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("]")
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
e.indent++
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
e.WriteString("{")
|
||||||
|
|
||||||
|
keys := value.MapKeys()
|
||||||
|
sort.Sort(sortAlpha(keys))
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
e.WriteString(e.quoteName(keys[i].String()))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(value.MapIndex(keys[i]), false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
|
||||||
|
l := value.NumField()
|
||||||
|
if l == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
e.indent++
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
e.WriteString("{")
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
curStructField := value.Type().Field(i)
|
||||||
|
curField := value.Field(i)
|
||||||
|
|
||||||
|
name := curStructField.Name
|
||||||
|
jsonTag := curStructField.Tag.Get("json")
|
||||||
|
jsonComment := curStructField.Tag.Get("comment")
|
||||||
|
omitEmpty := false
|
||||||
|
if jsonTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
splits := strings.Split(jsonTag, ",")
|
||||||
|
if splits[0] != "" {
|
||||||
|
name = splits[0]
|
||||||
|
}
|
||||||
|
if len(splits) > 1 {
|
||||||
|
for _, opt := range splits[1:] {
|
||||||
|
if opt == "omitempty" {
|
||||||
|
omitEmpty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if omitEmpty && isEmptyValue(curField) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 {
|
||||||
|
for _, line := range strings.Split(jsonComment, e.Eol) {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
e.WriteString(fmt.Sprintf("# %s", line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
e.WriteString(e.quoteName(name))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(curField, false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 && i < l-1 {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
default:
|
||||||
|
if e.UnknownAsNull {
|
||||||
|
// Use null as a placeholder for non-JSON values.
|
||||||
|
e.WriteString("null")
|
||||||
|
} else {
|
||||||
|
return errors.New("Unsupported type " + value.Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the Hjson encoding of v using
|
||||||
|
// default options.
|
||||||
|
//
|
||||||
|
// See MarshalWithOptions.
|
||||||
|
//
|
||||||
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return MarshalWithOptions(v, DefaultOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalWithOptions returns the Hjson encoding of v.
|
||||||
|
//
|
||||||
|
// Marshal traverses the value v recursively.
|
||||||
|
//
|
||||||
|
// Boolean values encode as JSON booleans.
|
||||||
|
//
|
||||||
|
// Floating point, integer, and Number values encode as JSON numbers.
|
||||||
|
//
|
||||||
|
// String values encode as Hjson strings (quoteless, multiline or
|
||||||
|
// JSON).
|
||||||
|
//
|
||||||
|
// Array and slice values encode as JSON arrays.
|
||||||
|
//
|
||||||
|
// Map values encode as JSON objects. The map's key type must be a
|
||||||
|
// string. The map keys are sorted and used as JSON object keys.
|
||||||
|
//
|
||||||
|
// Pointer values encode as the value pointed to.
|
||||||
|
// A nil pointer encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// Interface values encode as the value contained in the interface.
|
||||||
|
// A nil interface value encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// JSON cannot represent cyclic data structures and Marshal does not
|
||||||
|
// handle them. Passing cyclic structures to Marshal will result in
|
||||||
|
// an infinite recursion.
|
||||||
|
//
|
||||||
|
func MarshalWithOptions(v interface{}, options EncoderOptions) ([]byte, error) {
|
||||||
|
e := &hjsonEncoder{}
|
||||||
|
e.indent = 0
|
||||||
|
e.Eol = options.Eol
|
||||||
|
e.BracesSameLine = options.BracesSameLine
|
||||||
|
e.QuoteAlways = options.QuoteAlways
|
||||||
|
e.IndentBy = options.IndentBy
|
||||||
|
|
||||||
|
err := e.str(reflect.ValueOf(v), true, "", true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Bytes(), nil
|
||||||
|
}
|
||||||
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseNumber struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
len := len(p.data)
|
||||||
|
if p.at < len {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.at == len {
|
||||||
|
p.at++
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[pos]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithNumber(text []byte) bool {
|
||||||
|
if _, err := tryParseNumber(text, true); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryParseNumber(text []byte, stopAtNext bool) (float64, error) {
|
||||||
|
// Parse a number value.
|
||||||
|
|
||||||
|
p := parseNumber{text, 0, ' '}
|
||||||
|
leadingZeros := 0
|
||||||
|
testLeading := true
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
if testLeading {
|
||||||
|
if p.ch == '0' {
|
||||||
|
leadingZeros++
|
||||||
|
} else {
|
||||||
|
testLeading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if testLeading {
|
||||||
|
leadingZeros--
|
||||||
|
} // single 0 is allowed
|
||||||
|
if p.ch == '.' {
|
||||||
|
for p.next() && p.ch >= '0' && p.ch <= '9' {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == 'e' || p.ch == 'E' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' || p.ch == '+' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := p.at
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopAtNext {
|
||||||
|
// end scan if we find a punctuator character like ,}] or a comment
|
||||||
|
if p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' || p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ch > 0 || leadingZeros != 0 {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
number, err := strconv.ParseFloat(string(p.data[0:end-1]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
Normal file
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
logrus
|
||||||
|
vendor
|
||||||
|
|
||||||
|
.idea/
|
||||||
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
run:
|
||||||
|
# do not run on test files yet
|
||||||
|
tests: false
|
||||||
|
|
||||||
|
# all available settings of specific linters
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-type-assertions: false
|
||||||
|
|
||||||
|
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-blank: false
|
||||||
|
|
||||||
|
lll:
|
||||||
|
line-length: 100
|
||||||
|
tab-width: 4
|
||||||
|
|
||||||
|
prealloc:
|
||||||
|
simple: false
|
||||||
|
range-loops: false
|
||||||
|
for-loops: false
|
||||||
|
|
||||||
|
whitespace:
|
||||||
|
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||||
|
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- megacheck
|
||||||
|
- govet
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- prealloc
|
||||||
|
disable-all: false
|
||||||
|
presets:
|
||||||
|
- bugs
|
||||||
|
- unused
|
||||||
|
fast: false
|
||||||
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
language: go
|
||||||
|
go_import_path: github.com/sirupsen/logrus
|
||||||
|
git:
|
||||||
|
depth: 1
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
go: 1.15.x
|
||||||
|
os: linux
|
||||||
|
install:
|
||||||
|
- ./travis/install.sh
|
||||||
|
script:
|
||||||
|
- cd ci
|
||||||
|
- go run mage.go -v -w ../ crossBuild
|
||||||
|
- go run mage.go -v -w ../ lint
|
||||||
|
- go run mage.go -v -w ../ test
|
||||||
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
# 1.8.1
|
||||||
|
Code quality:
|
||||||
|
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
||||||
|
* improve timestamp format documentation
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* fix race condition on logger hooks
|
||||||
|
|
||||||
|
|
||||||
|
# 1.8.0
|
||||||
|
|
||||||
|
Correct versioning number replacing v1.7.1.
|
||||||
|
|
||||||
|
# 1.7.1
|
||||||
|
|
||||||
|
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
||||||
|
|
||||||
|
Code quality:
|
||||||
|
* use go 1.15 in travis
|
||||||
|
* use magefile as task runner
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* small fixes about new go 1.13 error formatting system
|
||||||
|
* Fix for long time race condiction with mutating data hooks
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* build support for zos
|
||||||
|
|
||||||
|
# 1.7.0
|
||||||
|
Fixes:
|
||||||
|
* the dependency toward a windows terminal library has been removed
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* a new buffer pool management API has been added
|
||||||
|
* a set of `<LogLevel>Fn()` functions have been added
|
||||||
|
|
||||||
|
# 1.6.0
|
||||||
|
Fixes:
|
||||||
|
* end of line cleanup
|
||||||
|
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
||||||
|
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||||
|
|
||||||
|
# 1.5.0
|
||||||
|
Code quality:
|
||||||
|
* add golangci linter run on travis
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* add mutex for hooks concurrent access on `Entry` data
|
||||||
|
* caller function field for go1.14
|
||||||
|
* fix build issue for gopherjs target
|
||||||
|
|
||||||
|
Feature:
|
||||||
|
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||||
|
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||||
|
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||||
|
|
||||||
|
# 1.4.2
|
||||||
|
* Fixes build break for plan9, nacl, solaris
|
||||||
|
# 1.4.1
|
||||||
|
This new release introduces:
|
||||||
|
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||||
|
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||||
|
|
||||||
|
# 1.4.0
|
||||||
|
This new release introduces:
|
||||||
|
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||||
|
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||||
|
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||||
|
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||||
|
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||||
|
* Fix race condition in `getCaller` (#916).
|
||||||
|
|
||||||
|
|
||||||
|
# 1.3.0
|
||||||
|
This new release introduces:
|
||||||
|
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Building prometheus node_exporter on AIX (#840)
|
||||||
|
* Race condition in TextFormatter (#468)
|
||||||
|
* Travis CI import path (#868)
|
||||||
|
* Remove coloured output on Windows (#862)
|
||||||
|
* Pointer to func as field in JSONFormatter (#870)
|
||||||
|
* Properly marshal Levels (#873)
|
||||||
|
|
||||||
|
# 1.2.0
|
||||||
|
This new release introduces:
|
||||||
|
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||||
|
* A new trace level named `Trace` whose level is below `Debug`
|
||||||
|
* A configurable exit function to be called upon a Fatal trace
|
||||||
|
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||||
|
|
||||||
|
# 1.1.1
|
||||||
|
This is a bug fix release.
|
||||||
|
* fix the build break on Solaris
|
||||||
|
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||||
|
|
||||||
|
# 1.1.0
|
||||||
|
This new release introduces:
|
||||||
|
* several fixes:
|
||||||
|
* a fix for a race condition on entry formatting
|
||||||
|
* proper cleanup of previously used entries before putting them back in the pool
|
||||||
|
* the extra new line at the end of message in text formatter has been removed
|
||||||
|
* a new global public API to check if a level is activated: IsLevelEnabled
|
||||||
|
* the following methods have been added to the Logger object
|
||||||
|
* IsLevelEnabled
|
||||||
|
* SetFormatter
|
||||||
|
* SetOutput
|
||||||
|
* ReplaceHooks
|
||||||
|
* introduction of go module
|
||||||
|
* an indent configuration for the json formatter
|
||||||
|
* output colour support for windows
|
||||||
|
* the field sort function is now configurable for text formatter
|
||||||
|
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||||
|
|
||||||
|
# 1.0.6
|
||||||
|
|
||||||
|
This new release introduces:
|
||||||
|
* a new api WithTime which allows to easily force the time of the log entry
|
||||||
|
which is mostly useful for logger wrapper
|
||||||
|
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||||
|
a new configuration field of the json formatter in order to put all the fields
|
||||||
|
in a nested dictionnary
|
||||||
|
* a new SetOutput method in the Logger
|
||||||
|
* a new configuration of the textformatter to configure the name of the default keys
|
||||||
|
* a new configuration of the text formatter to disable the level truncation
|
||||||
|
|
||||||
|
# 1.0.5
|
||||||
|
|
||||||
|
* Fix hooks race (#707)
|
||||||
|
* Fix panic deadlock (#695)
|
||||||
|
|
||||||
|
# 1.0.4
|
||||||
|
|
||||||
|
* Fix race when adding hooks (#612)
|
||||||
|
* Fix terminal check in AppEngine (#635)
|
||||||
|
|
||||||
|
# 1.0.3
|
||||||
|
|
||||||
|
* Replace example files with testable examples
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
|
||||||
|
* bug: quote non-string values in text formatter (#583)
|
||||||
|
* Make (*Logger) SetLevel a public method
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
|
||||||
|
* bug: fix escaping in text formatter (#575)
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
* Officially changed name to lower-case
|
||||||
|
* bug: colors on Windows 10 (#541)
|
||||||
|
* bug: fix race in accessing level (#512)
|
||||||
|
|
||||||
|
# 0.11.5
|
||||||
|
|
||||||
|
* feature: add writer and writerlevel to entry (#372)
|
||||||
|
|
||||||
|
# 0.11.4
|
||||||
|
|
||||||
|
* bug: fix undefined variable on solaris (#493)
|
||||||
|
|
||||||
|
# 0.11.3
|
||||||
|
|
||||||
|
* formatter: configure quoting of empty values (#484)
|
||||||
|
* formatter: configure quoting character (default is `"`) (#484)
|
||||||
|
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||||
|
|
||||||
|
# 0.11.2
|
||||||
|
|
||||||
|
* bug: fix windows terminal detection (#476)
|
||||||
|
|
||||||
|
# 0.11.1
|
||||||
|
|
||||||
|
* bug: fix tty detection with custom out (#471)
|
||||||
|
|
||||||
|
# 0.11.0
|
||||||
|
|
||||||
|
* performance: Use bufferpool to allocate (#370)
|
||||||
|
* terminal: terminal detection for app-engine (#343)
|
||||||
|
* feature: exit handler (#375)
|
||||||
|
|
||||||
|
# 0.10.0
|
||||||
|
|
||||||
|
* feature: Add a test hook (#180)
|
||||||
|
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||||
|
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||||
|
* performance: avoid re-allocations on `WithFields` (#335)
|
||||||
|
|
||||||
|
# 0.9.0
|
||||||
|
|
||||||
|
* logrus/text_formatter: don't emit empty msg
|
||||||
|
* logrus/hooks/airbrake: move out of main repository
|
||||||
|
* logrus/hooks/sentry: move out of main repository
|
||||||
|
* logrus/hooks/papertrail: move out of main repository
|
||||||
|
* logrus/hooks/bugsnag: move out of main repository
|
||||||
|
* logrus/core: run tests with `-race`
|
||||||
|
* logrus/core: detect TTY based on `stderr`
|
||||||
|
* logrus/core: support `WithError` on logger
|
||||||
|
* logrus/core: Solaris support
|
||||||
|
|
||||||
|
# 0.8.7
|
||||||
|
|
||||||
|
* logrus/core: fix possible race (#216)
|
||||||
|
* logrus/doc: small typo fixes and doc improvements
|
||||||
|
|
||||||
|
|
||||||
|
# 0.8.6
|
||||||
|
|
||||||
|
* hooks/raven: allow passing an initialized client
|
||||||
|
|
||||||
|
# 0.8.5
|
||||||
|
|
||||||
|
* logrus/core: revert #208
|
||||||
|
|
||||||
|
# 0.8.4
|
||||||
|
|
||||||
|
* formatter/text: fix data race (#218)
|
||||||
|
|
||||||
|
# 0.8.3
|
||||||
|
|
||||||
|
* logrus/core: fix entry log level (#208)
|
||||||
|
* logrus/core: improve performance of text formatter by 40%
|
||||||
|
* logrus/core: expose `LevelHooks` type
|
||||||
|
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||||
|
* formatter/text: print structs more verbosely
|
||||||
|
|
||||||
|
# 0.8.2
|
||||||
|
|
||||||
|
* logrus: fix more Fatal family functions
|
||||||
|
|
||||||
|
# 0.8.1
|
||||||
|
|
||||||
|
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||||
|
|
||||||
|
# 0.8.0
|
||||||
|
|
||||||
|
* logrus: defaults to stderr instead of stdout
|
||||||
|
* hooks/sentry: add special field for `*http.Request`
|
||||||
|
* formatter/text: ignore Windows for colors
|
||||||
|
|
||||||
|
# 0.7.3
|
||||||
|
|
||||||
|
* formatter/\*: allow configuration of timestamp layout
|
||||||
|
|
||||||
|
# 0.7.2
|
||||||
|
|
||||||
|
* formatter/text: Add configuration option for time format (#158)
|
||||||
21
vendor/github.com/sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
vendor/github.com/sirupsen/logrus/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Simon Eskildsen
|
||||||
|
|
||||||
|
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.
|
||||||
513
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
Normal file
513
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/sirupsen/logrus) [](https://godoc.org/github.com/sirupsen/logrus)
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger.
|
||||||
|
|
||||||
|
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||||
|
simply too hard to do in a way that won't break many people's projects, which is
|
||||||
|
the last thing you want from your Logging library (again...).
|
||||||
|
|
||||||
|
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||||
|
security, (backwards compatible) bug fixes, and performance (where we are
|
||||||
|
limited by the interface).
|
||||||
|
|
||||||
|
I believe Logrus' biggest contribution is to have played a part in today's
|
||||||
|
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||||
|
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
||||||
|
community has built those independently. Many fantastic alternatives have sprung
|
||||||
|
up. Logrus would look like those, had it been re-designed with what we know
|
||||||
|
about structured logging in Go today. Check out, for example,
|
||||||
|
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
||||||
|
|
||||||
|
[zerolog]: https://github.com/rs/zerolog
|
||||||
|
[zap]: https://github.com/uber-go/zap
|
||||||
|
[apex]: https://github.com/apex/log
|
||||||
|
|
||||||
|
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||||
|
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||||
|
this caused issues in the community and we needed a standard. Some environments
|
||||||
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
|
Everything using `logrus` will need to use the lower-case:
|
||||||
|
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
|
To fix Glide, see [these
|
||||||
|
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||||
|
For an in-depth explanation of the casing issue, see [this
|
||||||
|
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||||
|
or Splunk:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||||
|
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||||
|
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||||
|
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||||
|
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||||
|
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||||
|
attached, the output is compatible with the
|
||||||
|
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||||
|
```
|
||||||
|
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logging Method Name
|
||||||
|
|
||||||
|
If you wish to add the calling method as a field, instruct the logger via:
|
||||||
|
```go
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
```
|
||||||
|
This adds the caller as 'method' like so:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||||
|
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||||
|
```
|
||||||
|
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||||
|
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||||
|
environment via benchmarks:
|
||||||
|
```
|
||||||
|
go test -bench=.*CallerTracing
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Case-sensitivity
|
||||||
|
|
||||||
|
The organization's name was changed to lower-case--and this will not be changed
|
||||||
|
back. If you are getting import conflicts due to case sensitivity, please use
|
||||||
|
the lower-case import: `github.com/sirupsen/logrus`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||||
|
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||||
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
|
want:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
|
// Output to stdout instead of the default stderr
|
||||||
|
// Can be any io.Writer, see below for File example
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
|
// Only log the warning severity or above.
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
|
||||||
|
// A common pattern is to re-use fields between logging statements by re-using
|
||||||
|
// the logrus.Entry returned from WithFields()
|
||||||
|
contextLogger := log.WithFields(log.Fields{
|
||||||
|
"common": "this is a common field",
|
||||||
|
"other": "I also should be logged always",
|
||||||
|
})
|
||||||
|
|
||||||
|
contextLogger.Info("I'll be logged with common and other field")
|
||||||
|
contextLogger.Info("Me too")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced usage such as logging to multiple locations from the same
|
||||||
|
application, you can also create an instance of the `logrus` Logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The API for setting attributes is a little different than the package level
|
||||||
|
// exported logger. See Godoc.
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// You could set this to any `io.Writer` such as a file
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
Logrus encourages careful, structured logging through logging fields instead of
|
||||||
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
|
discoverable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event": event,
|
||||||
|
"topic": topic,
|
||||||
|
"key": key,
|
||||||
|
}).Fatal("Failed to send event")
|
||||||
|
```
|
||||||
|
|
||||||
|
We've found this API forces you to think about logging in a way that produces
|
||||||
|
much more useful logging messages. We've been in countless situations where just
|
||||||
|
a single added field to a log statement that was already there would've saved us
|
||||||
|
hours. The `WithFields` call is optional.
|
||||||
|
|
||||||
|
In general, with Logrus using any of the `printf`-family functions should be
|
||||||
|
seen as a hint you should add a field, however, you can still use the
|
||||||
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Default Fields
|
||||||
|
|
||||||
|
Often it's helpful to have fields _always_ attached to log statements in an
|
||||||
|
application or parts of one. For example, you may want to always log the
|
||||||
|
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||||
|
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||||
|
every line, you can create a `logrus.Entry` to pass around instead:
|
||||||
|
|
||||||
|
```go
|
||||||
|
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||||
|
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||||
|
requestLogger.Warn("something not great happened")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Hooks
|
||||||
|
|
||||||
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||||
|
multiple places simultaneously, e.g. syslog.
|
||||||
|
|
||||||
|
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||||
|
`init`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||||
|
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||||
|
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||||
|
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||||
|
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to connect to local syslog daemon")
|
||||||
|
} else {
|
||||||
|
log.AddHook(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||||
|
|
||||||
|
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||||
|
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Trace("Something very low level.")
|
||||||
|
log.Debug("Useful debugging information.")
|
||||||
|
log.Info("Something noteworthy happened!")
|
||||||
|
log.Warn("You should probably take a look at this.")
|
||||||
|
log.Error("Something failed but I'm not quitting.")
|
||||||
|
// Calls os.Exit(1) after logging
|
||||||
|
log.Fatal("Bye.")
|
||||||
|
// Calls panic() after logging
|
||||||
|
log.Panic("I'm bailing.")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the logging level on a `Logger`, then it will only log entries with
|
||||||
|
that severity or anything above it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
|
environment if your application has that.
|
||||||
|
|
||||||
|
#### Entries
|
||||||
|
|
||||||
|
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||||
|
automatically added to all logging events:
|
||||||
|
|
||||||
|
1. `time`. The timestamp when the entry was created.
|
||||||
|
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||||
|
the `AddFields` call. E.g. `Failed to send event.`
|
||||||
|
3. `level`. The logging level. E.g. `info`.
|
||||||
|
|
||||||
|
#### Environments
|
||||||
|
|
||||||
|
Logrus has no notion of environment.
|
||||||
|
|
||||||
|
If you wish for hooks and formatters to only be used in specific environments,
|
||||||
|
you should handle that yourself. For example, if your application has a global
|
||||||
|
variable `Environment`, which is a string representation of the environment you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// do something here to set environment depending on an environment variable
|
||||||
|
// or command-line flag
|
||||||
|
if Environment == "production" {
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
} else {
|
||||||
|
// The TextFormatter is default, you don't actually have to do this.
|
||||||
|
log.SetFormatter(&log.TextFormatter{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration is how `logrus` was intended to be used, but JSON in
|
||||||
|
production is mostly only useful if you do log aggregation with tools like
|
||||||
|
Splunk or Logstash.
|
||||||
|
|
||||||
|
#### Formatters
|
||||||
|
|
||||||
|
The built-in logging formatters are:
|
||||||
|
|
||||||
|
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||||
|
without colors.
|
||||||
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
|
`DisableColors` field to `true`. For Windows, see
|
||||||
|
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||||
|
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||||
|
truncation set the `DisableLevelTruncation` field to `true`.
|
||||||
|
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||||
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||||
|
|
||||||
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||||
|
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
||||||
|
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||||
|
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||||
|
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
||||||
|
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||||
|
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
||||||
|
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||||
|
|
||||||
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
|
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||||
|
default ones (see Entries section above):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyJSONFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(new(MyJSONFormatter))
|
||||||
|
|
||||||
|
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logger as an `io.Writer`
|
||||||
|
|
||||||
|
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||||
|
|
||||||
|
```go
|
||||||
|
w := logger.Writer()
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
// create a stdlib log.Logger that writes to
|
||||||
|
// logrus.Logger.
|
||||||
|
ErrorLog: log.New(w, "", 0),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line written to that writer will be printed the usual way, using formatters
|
||||||
|
and hooks. The level for those entries is `info`.
|
||||||
|
|
||||||
|
This means that we can override the standard library logger easily:
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Formatter = &logrus.JSONFormatter{}
|
||||||
|
|
||||||
|
// Use logrus for standard log output
|
||||||
|
// Note that `log` here references stdlib's log
|
||||||
|
// Not logrus imported under the name `log`.
|
||||||
|
log.SetOutput(logger.Writer())
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rotation
|
||||||
|
|
||||||
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
external program (like `logrotate(8)`) that can compress and delete old log
|
||||||
|
entries. It should not be a feature of the application-level logger.
|
||||||
|
|
||||||
|
#### Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
||||||
|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
|
||||||
|
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||||
|
|
||||||
|
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
||||||
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
|
```go
|
||||||
|
import(
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSomething(t*testing.T){
|
||||||
|
logger, hook := test.NewNullLogger()
|
||||||
|
logger.Error("Helloerror")
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(hook.Entries))
|
||||||
|
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(t, hook.LastEntry())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fatal handlers
|
||||||
|
|
||||||
|
Logrus can register one or more functions that will be called when any `fatal`
|
||||||
|
level message is logged. The registered handlers will be executed before
|
||||||
|
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
||||||
|
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||||
|
|
||||||
|
```
|
||||||
|
...
|
||||||
|
handler := func() {
|
||||||
|
// gracefully shutdown something...
|
||||||
|
}
|
||||||
|
logrus.RegisterExitHandler(handler)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Thread safety
|
||||||
|
|
||||||
|
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
||||||
|
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||||
|
|
||||||
|
Situation when locking is not needed includes:
|
||||||
|
|
||||||
|
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||||
|
|
||||||
|
* Writing to logger.Out is already thread-safe, for example:
|
||||||
|
|
||||||
|
1) logger.Out is protected by locks.
|
||||||
|
|
||||||
|
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||||
|
|
||||||
|
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
||||||
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
// The following code was sourced and modified from the
|
||||||
|
// https://github.com/tebeka/atexit package governed by the following license:
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.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.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var handlers = []func(){}
|
||||||
|
|
||||||
|
func runHandler(handler func()) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHandlers() {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
runHandler(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||||
|
func Exit(code int) {
|
||||||
|
runHandlers()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
||||||
|
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||||
|
// any Fatal log entry is made.
|
||||||
|
//
|
||||||
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
|
// closing database connections, or sending a alert that the application is
|
||||||
|
// closing.
|
||||||
|
func RegisterExitHandler(handler func()) {
|
||||||
|
handlers = append(handlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
||||||
|
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||||
|
// any Fatal log entry is made.
|
||||||
|
//
|
||||||
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
|
// closing database connections, or sending a alert that the application is
|
||||||
|
// closing.
|
||||||
|
func DeferExitHandler(handler func()) {
|
||||||
|
handlers = append([]func(){handler}, handlers...)
|
||||||
|
}
|
||||||
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: "{build}"
|
||||||
|
platform: x64
|
||||||
|
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
install:
|
||||||
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go version
|
||||||
|
build_script:
|
||||||
|
- go get -t
|
||||||
|
- go test
|
||||||
52
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
52
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bufferPool BufferPool
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufferPool interface {
|
||||||
|
Put(*bytes.Buffer)
|
||||||
|
Get() *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultPool struct {
|
||||||
|
pool *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
||||||
|
p.pool.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *defaultPool) Get() *bytes.Buffer {
|
||||||
|
return p.pool.Get().(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuffer() *bytes.Buffer {
|
||||||
|
return bufferPool.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func putBuffer(buf *bytes.Buffer) {
|
||||||
|
buf.Reset()
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBufferPool allows to replace the default logrus buffer pool
|
||||||
|
// to better meets the specific needs of an application.
|
||||||
|
func SetBufferPool(bp BufferPool) {
|
||||||
|
bufferPool = bp
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetBufferPool(&defaultPool{
|
||||||
|
pool: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
Normal file
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||||
|
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 1,
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
|
For a full guide visit https://github.com/sirupsen/logrus
|
||||||
|
*/
|
||||||
|
package logrus
|
||||||
431
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
Normal file
431
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
|
||||||
|
// qualified package name, cached at first use
|
||||||
|
logrusPackage string
|
||||||
|
|
||||||
|
// Positions in the call stack when tracing to report the calling method
|
||||||
|
minimumCallerDepth int
|
||||||
|
|
||||||
|
// Used for caller information initialisation
|
||||||
|
callerInitOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maximumCallerDepth int = 25
|
||||||
|
knownLogrusFrames int = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// start at the bottom of the stack before the package-name cache is primed
|
||||||
|
minimumCallerDepth = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the key when adding errors using WithError.
|
||||||
|
var ErrorKey = "error"
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||||
|
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||||
|
// reused and passed around as much as you wish to avoid field duplication.
|
||||||
|
type Entry struct {
|
||||||
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Calling method, with package name
|
||||||
|
Caller *runtime.Frame
|
||||||
|
|
||||||
|
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
|
||||||
|
// Contains the context set by the user. Useful for hook processing etc.
|
||||||
|
Context context.Context
|
||||||
|
|
||||||
|
// err may contain a field formatting error
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, plus one optional. Give a little extra room.
|
||||||
|
Data: make(Fields, 6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Dup() *Entry {
|
||||||
|
data := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the bytes representation of this entry from the formatter.
|
||||||
|
func (entry *Entry) Bytes() ([]byte, error) {
|
||||||
|
return entry.Logger.Formatter.Format(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
serialized, err := entry.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
str := string(serialized)
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||||
|
func (entry *Entry) WithError(err error) *Entry {
|
||||||
|
return entry.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a context to the Entry.
|
||||||
|
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||||
|
dataCopy := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
dataCopy[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
|
return entry.WithFields(Fields{key: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
data := make(Fields, len(entry.Data)+len(fields))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
fieldErr := entry.err
|
||||||
|
for k, v := range fields {
|
||||||
|
isErrField := false
|
||||||
|
if t := reflect.TypeOf(v); t != nil {
|
||||||
|
switch {
|
||||||
|
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
||||||
|
isErrField = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isErrField {
|
||||||
|
tmp := fmt.Sprintf("can not add field %q", k)
|
||||||
|
if fieldErr != "" {
|
||||||
|
fieldErr = entry.err + ", " + tmp
|
||||||
|
} else {
|
||||||
|
fieldErr = tmp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the time of the Entry.
|
||||||
|
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||||
|
dataCopy := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
dataCopy[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPackageName reduces a fully qualified function name to the package name
|
||||||
|
// There really ought to be to be a better way...
|
||||||
|
func getPackageName(f string) string {
|
||||||
|
for {
|
||||||
|
lastPeriod := strings.LastIndex(f, ".")
|
||||||
|
lastSlash := strings.LastIndex(f, "/")
|
||||||
|
if lastPeriod > lastSlash {
|
||||||
|
f = f[:lastPeriod]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCaller retrieves the name of the first non-logrus calling function
|
||||||
|
func getCaller() *runtime.Frame {
|
||||||
|
// cache this package's fully-qualified name
|
||||||
|
callerInitOnce.Do(func() {
|
||||||
|
pcs := make([]uintptr, maximumCallerDepth)
|
||||||
|
_ = runtime.Callers(0, pcs)
|
||||||
|
|
||||||
|
// dynamic get the package name and the minimum caller depth
|
||||||
|
for i := 0; i < maximumCallerDepth; i++ {
|
||||||
|
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||||
|
if strings.Contains(funcName, "getCaller") {
|
||||||
|
logrusPackage = getPackageName(funcName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minimumCallerDepth = knownLogrusFrames
|
||||||
|
})
|
||||||
|
|
||||||
|
// Restrict the lookback frames to avoid runaway lookups
|
||||||
|
pcs := make([]uintptr, maximumCallerDepth)
|
||||||
|
depth := runtime.Callers(minimumCallerDepth, pcs)
|
||||||
|
frames := runtime.CallersFrames(pcs[:depth])
|
||||||
|
|
||||||
|
for f, again := frames.Next(); again; f, again = frames.Next() {
|
||||||
|
pkg := getPackageName(f.Function)
|
||||||
|
|
||||||
|
// If the caller isn't part of this package, we're done
|
||||||
|
if pkg != logrusPackage {
|
||||||
|
return &f //nolint:scopelint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, we failed to find the caller's context
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry Entry) HasCaller() (has bool) {
|
||||||
|
return entry.Logger != nil &&
|
||||||
|
entry.Logger.ReportCaller &&
|
||||||
|
entry.Caller != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) log(level Level, msg string) {
|
||||||
|
var buffer *bytes.Buffer
|
||||||
|
|
||||||
|
newEntry := entry.Dup()
|
||||||
|
|
||||||
|
if newEntry.Time.IsZero() {
|
||||||
|
newEntry.Time = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry.Level = level
|
||||||
|
newEntry.Message = msg
|
||||||
|
|
||||||
|
newEntry.Logger.mu.Lock()
|
||||||
|
reportCaller := newEntry.Logger.ReportCaller
|
||||||
|
newEntry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
if reportCaller {
|
||||||
|
newEntry.Caller = getCaller()
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry.fireHooks()
|
||||||
|
|
||||||
|
buffer = getBuffer()
|
||||||
|
defer func() {
|
||||||
|
newEntry.Buffer = nil
|
||||||
|
putBuffer(buffer)
|
||||||
|
}()
|
||||||
|
buffer.Reset()
|
||||||
|
newEntry.Buffer = buffer
|
||||||
|
|
||||||
|
newEntry.write()
|
||||||
|
|
||||||
|
newEntry.Buffer = nil
|
||||||
|
|
||||||
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
|
// directly here.
|
||||||
|
if level <= PanicLevel {
|
||||||
|
panic(newEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) fireHooks() {
|
||||||
|
var tmpHooks LevelHooks
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
||||||
|
for k, v := range entry.Logger.Hooks {
|
||||||
|
tmpHooks[k] = v
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
err := tmpHooks.Fire(entry.Level, entry)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) write() {
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
defer entry.Logger.mu.Unlock()
|
||||||
|
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.log(level, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Trace(args ...interface{}) {
|
||||||
|
entry.Log(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
|
entry.Log(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
entry.Log(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
entry.Log(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warning(args ...interface{}) {
|
||||||
|
entry.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
entry.Log(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
entry.Log(FatalLevel, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
entry.Log(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.Log(level, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||||
|
entry.Logf(TraceLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(DebugLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
entry.Logf(InfoLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(WarnLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(ErrorLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(FatalLevel, format, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(PanicLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.Log(level, entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Traceln(args ...interface{}) {
|
||||||
|
entry.Logln(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
entry.Logln(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
entry.Logln(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
entry.Logln(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
entry.Logln(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
entry.Logln(FatalLevel, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
entry.Logln(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||||
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||||
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||||
|
// string allocation, we do the simplest thing.
|
||||||
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
return msg[:len(msg)-1]
|
||||||
|
}
|
||||||
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
Normal file
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// std is the name of the standard logger in stdlib `log`
|
||||||
|
std = New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func StandardLogger() *Logger {
|
||||||
|
return std
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the standard logger output.
|
||||||
|
func SetOutput(out io.Writer) {
|
||||||
|
std.SetOutput(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the standard logger formatter.
|
||||||
|
func SetFormatter(formatter Formatter) {
|
||||||
|
std.SetFormatter(formatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReportCaller sets whether the standard logger will include the calling
|
||||||
|
// method as a field.
|
||||||
|
func SetReportCaller(include bool) {
|
||||||
|
std.SetReportCaller(include)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the standard logger level.
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
std.SetLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the standard logger level.
|
||||||
|
func GetLevel() Level {
|
||||||
|
return std.GetLevel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
||||||
|
func IsLevelEnabled(level Level) bool {
|
||||||
|
return std.IsLevelEnabled(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
func AddHook(hook Hook) {
|
||||||
|
std.AddHook(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||||
|
func WithError(err error) *Entry {
|
||||||
|
return std.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||||
|
func WithContext(ctx context.Context) *Entry {
|
||||||
|
return std.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField creates an entry from the standard logger and adds a field to
|
||||||
|
// it. If you want multiple fields, use `WithFields`.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithField(key string, value interface{}) *Entry {
|
||||||
|
return std.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields creates an entry from the standard logger and adds multiple
|
||||||
|
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||||
|
// once for each field.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithFields(fields Fields) *Entry {
|
||||||
|
return std.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTime creates an entry from the standard logger and overrides the time of
|
||||||
|
// logs generated with it.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithTime(t time.Time) *Entry {
|
||||||
|
return std.WithTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at level Trace on the standard logger.
|
||||||
|
func Trace(args ...interface{}) {
|
||||||
|
std.Trace(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
std.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print logs a message at level Info on the standard logger.
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
std.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at level Info on the standard logger.
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
std.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at level Warn on the standard logger.
|
||||||
|
func Warn(args ...interface{}) {
|
||||||
|
std.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at level Warn on the standard logger.
|
||||||
|
func Warning(args ...interface{}) {
|
||||||
|
std.Warning(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at level Error on the standard logger.
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
std.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic logs a message at level Panic on the standard logger.
|
||||||
|
func Panic(args ...interface{}) {
|
||||||
|
std.Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
std.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceFn logs a message from a func at level Trace on the standard logger.
|
||||||
|
func TraceFn(fn LogFunction) {
|
||||||
|
std.TraceFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugFn logs a message from a func at level Debug on the standard logger.
|
||||||
|
func DebugFn(fn LogFunction) {
|
||||||
|
std.DebugFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintFn logs a message from a func at level Info on the standard logger.
|
||||||
|
func PrintFn(fn LogFunction) {
|
||||||
|
std.PrintFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoFn logs a message from a func at level Info on the standard logger.
|
||||||
|
func InfoFn(fn LogFunction) {
|
||||||
|
std.InfoFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarnFn logs a message from a func at level Warn on the standard logger.
|
||||||
|
func WarnFn(fn LogFunction) {
|
||||||
|
std.WarnFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarningFn logs a message from a func at level Warn on the standard logger.
|
||||||
|
func WarningFn(fn LogFunction) {
|
||||||
|
std.WarningFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorFn logs a message from a func at level Error on the standard logger.
|
||||||
|
func ErrorFn(fn LogFunction) {
|
||||||
|
std.ErrorFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicFn logs a message from a func at level Panic on the standard logger.
|
||||||
|
func PanicFn(fn LogFunction) {
|
||||||
|
std.PanicFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func FatalFn(fn LogFunction) {
|
||||||
|
std.FatalFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracef logs a message at level Trace on the standard logger.
|
||||||
|
func Tracef(format string, args ...interface{}) {
|
||||||
|
std.Tracef(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a message at level Debug on the standard logger.
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
std.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf logs a message at level Info on the standard logger.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
std.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a message at level Info on the standard logger.
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
std.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a message at level Warn on the standard logger.
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
std.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs a message at level Warn on the standard logger.
|
||||||
|
func Warningf(format string, args ...interface{}) {
|
||||||
|
std.Warningf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a message at level Error on the standard logger.
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
std.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicf logs a message at level Panic on the standard logger.
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
std.Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
std.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traceln logs a message at level Trace on the standard logger.
|
||||||
|
func Traceln(args ...interface{}) {
|
||||||
|
std.Traceln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln logs a message at level Debug on the standard logger.
|
||||||
|
func Debugln(args ...interface{}) {
|
||||||
|
std.Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println logs a message at level Info on the standard logger.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
std.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a message at level Info on the standard logger.
|
||||||
|
func Infoln(args ...interface{}) {
|
||||||
|
std.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnln logs a message at level Warn on the standard logger.
|
||||||
|
func Warnln(args ...interface{}) {
|
||||||
|
std.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs a message at level Warn on the standard logger.
|
||||||
|
func Warningln(args ...interface{}) {
|
||||||
|
std.Warningln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs a message at level Error on the standard logger.
|
||||||
|
func Errorln(args ...interface{}) {
|
||||||
|
std.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicln logs a message at level Panic on the standard logger.
|
||||||
|
func Panicln(args ...interface{}) {
|
||||||
|
std.Panicln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatalln(args ...interface{}) {
|
||||||
|
std.Fatalln(args...)
|
||||||
|
}
|
||||||
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
Normal file
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Default key names for the default fields
|
||||||
|
const (
|
||||||
|
defaultTimestampFormat = time.RFC3339
|
||||||
|
FieldKeyMsg = "msg"
|
||||||
|
FieldKeyLevel = "level"
|
||||||
|
FieldKeyTime = "time"
|
||||||
|
FieldKeyLogrusError = "logrus_error"
|
||||||
|
FieldKeyFunc = "func"
|
||||||
|
FieldKeyFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
//
|
||||||
|
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||||
|
// * `entry.Data["time"]`. The timestamp.
|
||||||
|
// * `entry.Data["level"]. The level the entry was logged at.
|
||||||
|
//
|
||||||
|
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||||
|
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||||
|
// logged to `logger.Out`.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(*Entry) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
||||||
|
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||||
|
if t, ok := data[timeKey]; ok {
|
||||||
|
data["fields."+timeKey] = t
|
||||||
|
delete(data, timeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgKey := fieldMap.resolve(FieldKeyMsg)
|
||||||
|
if m, ok := data[msgKey]; ok {
|
||||||
|
data["fields."+msgKey] = m
|
||||||
|
delete(data, msgKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
levelKey := fieldMap.resolve(FieldKeyLevel)
|
||||||
|
if l, ok := data[levelKey]; ok {
|
||||||
|
data["fields."+levelKey] = l
|
||||||
|
delete(data, levelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
||||||
|
if l, ok := data[logrusErrKey]; ok {
|
||||||
|
data["fields."+logrusErrKey] = l
|
||||||
|
delete(data, logrusErrKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If reportCaller is not set, 'func' will not conflict.
|
||||||
|
if reportCaller {
|
||||||
|
funcKey := fieldMap.resolve(FieldKeyFunc)
|
||||||
|
if l, ok := data[funcKey]; ok {
|
||||||
|
data["fields."+funcKey] = l
|
||||||
|
}
|
||||||
|
fileKey := fieldMap.resolve(FieldKeyFile)
|
||||||
|
if l, ok := data[fileKey]; ok {
|
||||||
|
data["fields."+fileKey] = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
vendor/github.com/sirupsen/logrus/go.mod
generated
vendored
Normal file
10
vendor/github.com/sirupsen/logrus/go.mod
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
module github.com/sirupsen/logrus
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.2.2
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.13
|
||||||
8
vendor/github.com/sirupsen/logrus/go.sum
generated
vendored
Normal file
8
vendor/github.com/sirupsen/logrus/go.sum
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
// A hook to be fired when logging on the logging levels returned from
|
||||||
|
// `Levels()` on your implementation of the interface. Note that this is not
|
||||||
|
// fired in a goroutine or a channel with workers, you should handle such
|
||||||
|
// functionality yourself if your call is non-blocking and you don't wish for
|
||||||
|
// the logging calls for levels returned from `Levels()` to block.
|
||||||
|
type Hook interface {
|
||||||
|
Levels() []Level
|
||||||
|
Fire(*Entry) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal type for storing the hooks on a logger instance.
|
||||||
|
type LevelHooks map[Level][]Hook
|
||||||
|
|
||||||
|
// Add a hook to an instance of logger. This is called with
|
||||||
|
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||||
|
func (hooks LevelHooks) Add(hook Hook) {
|
||||||
|
for _, level := range hook.Levels() {
|
||||||
|
hooks[level] = append(hooks[level], hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||||
|
// appropriate hooks for a log entry.
|
||||||
|
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||||
|
for _, hook := range hooks[level] {
|
||||||
|
if err := hook.Fire(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldKey string
|
||||||
|
|
||||||
|
// FieldMap allows customization of the key names for default fields.
|
||||||
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
func (f FieldMap) resolve(key fieldKey) string {
|
||||||
|
if k, ok := f[key]; ok {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONFormatter formats logs into parsable json
|
||||||
|
type JSONFormatter struct {
|
||||||
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
|
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||||
|
// library.
|
||||||
|
// The standard Library already provides a set of predefined format.
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
|
DisableTimestamp bool
|
||||||
|
|
||||||
|
// DisableHTMLEscape allows disabling html escaping in output
|
||||||
|
DisableHTMLEscape bool
|
||||||
|
|
||||||
|
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||||
|
DataKey string
|
||||||
|
|
||||||
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
|
// As an example:
|
||||||
|
// formatter := &JSONFormatter{
|
||||||
|
// FieldMap: FieldMap{
|
||||||
|
// FieldKeyTime: "@timestamp",
|
||||||
|
// FieldKeyLevel: "@level",
|
||||||
|
// FieldKeyMsg: "@message",
|
||||||
|
// FieldKeyFunc: "@caller",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
FieldMap FieldMap
|
||||||
|
|
||||||
|
// CallerPrettyfier can be set by the user to modify the content
|
||||||
|
// of the function and file keys in the json data when ReportCaller is
|
||||||
|
// activated. If any of the returned value is the empty string the
|
||||||
|
// corresponding key will be removed from json fields.
|
||||||
|
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||||
|
|
||||||
|
// PrettyPrint will indent all json logs
|
||||||
|
PrettyPrint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
data := make(Fields, len(entry.Data)+4)
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case error:
|
||||||
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
|
// https://github.com/sirupsen/logrus/issues/137
|
||||||
|
data[k] = v.Error()
|
||||||
|
default:
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.DataKey != "" {
|
||||||
|
newData := make(Fields, 4)
|
||||||
|
newData[f.DataKey] = data
|
||||||
|
data = newData
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = defaultTimestampFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.err != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
||||||
|
}
|
||||||
|
if !f.DisableTimestamp {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||||
|
}
|
||||||
|
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||||
|
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||||
|
if entry.HasCaller() {
|
||||||
|
funcVal := entry.Caller.Function
|
||||||
|
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||||
|
if f.CallerPrettyfier != nil {
|
||||||
|
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||||
|
}
|
||||||
|
if funcVal != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||||
|
}
|
||||||
|
if fileVal != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *bytes.Buffer
|
||||||
|
if entry.Buffer != nil {
|
||||||
|
b = entry.Buffer
|
||||||
|
} else {
|
||||||
|
b = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(b)
|
||||||
|
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
||||||
|
if f.PrettyPrint {
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
}
|
||||||
|
if err := encoder.Encode(data); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
404
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
Normal file
404
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogFunction For big messages, it can be more efficient to pass a function
|
||||||
|
// and only call it if the log level is actually enables rather than
|
||||||
|
// generating the log message and then checking if the level is enabled
|
||||||
|
type LogFunction func() []interface{}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
|
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||||
|
// something more adventurous, such as logging to Kafka.
|
||||||
|
Out io.Writer
|
||||||
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
|
Hooks LevelHooks
|
||||||
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
|
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||||
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
|
// formatters for examples.
|
||||||
|
Formatter Formatter
|
||||||
|
|
||||||
|
// Flag for whether to log caller info (off by default)
|
||||||
|
ReportCaller bool
|
||||||
|
|
||||||
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
|
// logged.
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
|
mu MutexWrap
|
||||||
|
// Reusable empty entry
|
||||||
|
entryPool sync.Pool
|
||||||
|
// Function to exit the application, defaults to `os.Exit()`
|
||||||
|
ExitFunc exitFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type exitFunc func(int)
|
||||||
|
|
||||||
|
type MutexWrap struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
disabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Lock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Unlock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Disable() {
|
||||||
|
mw.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||||
|
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||||
|
// instantiate your own:
|
||||||
|
//
|
||||||
|
// var log = &logrus.Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(logrus.TextFormatter),
|
||||||
|
// Hooks: make(logrus.LevelHooks),
|
||||||
|
// Level: logrus.DebugLevel,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It's recommended to make this a global instance called `log`.
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
Out: os.Stderr,
|
||||||
|
Formatter: new(TextFormatter),
|
||||||
|
Hooks: make(LevelHooks),
|
||||||
|
Level: InfoLevel,
|
||||||
|
ExitFunc: os.Exit,
|
||||||
|
ReportCaller: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) newEntry() *Entry {
|
||||||
|
entry, ok := logger.entryPool.Get().(*Entry)
|
||||||
|
if ok {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return NewEntry(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
|
entry.Data = map[string]interface{}{}
|
||||||
|
logger.entryPool.Put(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField allocates a new entry and adds a field to it.
|
||||||
|
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||||
|
// this new returned entry.
|
||||||
|
// If you want multiple fields, use `WithFields`.
|
||||||
|
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||||
|
// each `Field`.
|
||||||
|
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field to the log entry. All it does is call
|
||||||
|
// `WithError` for the given `error`.
|
||||||
|
func (logger *Logger) WithError(err error) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a context to the log entry.
|
||||||
|
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the time of the log entry.
|
||||||
|
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Logf(level, format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||||
|
logger.Logf(TraceLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(DebugLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
logger.Logf(InfoLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Printf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(WarnLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
logger.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(ErrorLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(FatalLevel, format, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(PanicLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Log(level, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Log(level, fn()...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Trace(args ...interface{}) {
|
||||||
|
logger.Log(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
logger.Log(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
logger.Log(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Print(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
logger.Log(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
logger.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
logger.Log(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
logger.Log(FatalLevel, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
logger.Log(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||||
|
logger.LogFn(TraceLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) DebugFn(fn LogFunction) {
|
||||||
|
logger.LogFn(DebugLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) InfoFn(fn LogFunction) {
|
||||||
|
logger.LogFn(InfoLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) PrintFn(fn LogFunction) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Print(fn()...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) WarnFn(fn LogFunction) {
|
||||||
|
logger.LogFn(WarnLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) WarningFn(fn LogFunction) {
|
||||||
|
logger.WarnFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) ErrorFn(fn LogFunction) {
|
||||||
|
logger.LogFn(ErrorLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) FatalFn(fn LogFunction) {
|
||||||
|
logger.LogFn(FatalLevel, fn)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||||
|
logger.LogFn(PanicLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Logln(level, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Traceln(args ...interface{}) {
|
||||||
|
logger.Logln(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
logger.Logln(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
logger.Logln(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Println(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
logger.Logln(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
logger.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
logger.Logln(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
logger.Logln(FatalLevel, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
logger.Logln(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Exit(code int) {
|
||||||
|
runHandlers()
|
||||||
|
if logger.ExitFunc == nil {
|
||||||
|
logger.ExitFunc = os.Exit
|
||||||
|
}
|
||||||
|
logger.ExitFunc(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
//When file is opened with appending mode, it's safe to
|
||||||
|
//write concurrently to a file (within 4k message on Linux).
|
||||||
|
//In these cases user can choose to disable the lock.
|
||||||
|
func (logger *Logger) SetNoLock() {
|
||||||
|
logger.mu.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) level() Level {
|
||||||
|
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the logger level.
|
||||||
|
func (logger *Logger) SetLevel(level Level) {
|
||||||
|
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the logger level.
|
||||||
|
func (logger *Logger) GetLevel() Level {
|
||||||
|
return logger.level()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the logger hooks.
|
||||||
|
func (logger *Logger) AddHook(hook Hook) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
||||||
|
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||||
|
return logger.level() >= level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the logger formatter.
|
||||||
|
func (logger *Logger) SetFormatter(formatter Formatter) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Formatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the logger output.
|
||||||
|
func (logger *Logger) SetOutput(output io.Writer) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Out = output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.ReportCaller = reportCaller
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||||
|
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||||
|
logger.mu.Lock()
|
||||||
|
oldHooks := logger.Hooks
|
||||||
|
logger.Hooks = hooks
|
||||||
|
logger.mu.Unlock()
|
||||||
|
return oldHooks
|
||||||
|
}
|
||||||
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
Normal file
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint32
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
if b, err := level.MarshalText(); err == nil {
|
||||||
|
return string(b)
|
||||||
|
} else {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
|
switch strings.ToLower(lvl) {
|
||||||
|
case "panic":
|
||||||
|
return PanicLevel, nil
|
||||||
|
case "fatal":
|
||||||
|
return FatalLevel, nil
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel, nil
|
||||||
|
case "info":
|
||||||
|
return InfoLevel, nil
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel, nil
|
||||||
|
case "trace":
|
||||||
|
return TraceLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (level *Level) UnmarshalText(text []byte) error {
|
||||||
|
l, err := ParseLevel(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*level = l
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (level Level) MarshalText() ([]byte, error) {
|
||||||
|
switch level {
|
||||||
|
case TraceLevel:
|
||||||
|
return []byte("trace"), nil
|
||||||
|
case DebugLevel:
|
||||||
|
return []byte("debug"), nil
|
||||||
|
case InfoLevel:
|
||||||
|
return []byte("info"), nil
|
||||||
|
case WarnLevel:
|
||||||
|
return []byte("warning"), nil
|
||||||
|
case ErrorLevel:
|
||||||
|
return []byte("error"), nil
|
||||||
|
case FatalLevel:
|
||||||
|
return []byte("fatal"), nil
|
||||||
|
case PanicLevel:
|
||||||
|
return []byte("panic"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constant exposing all logging levels
|
||||||
|
var AllLevels = []Level{
|
||||||
|
PanicLevel,
|
||||||
|
FatalLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
WarnLevel,
|
||||||
|
InfoLevel,
|
||||||
|
DebugLevel,
|
||||||
|
TraceLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the different logging levels. You can set the logging level to log
|
||||||
|
// on your instance of logger, obtained with `logrus.New()`.
|
||||||
|
const (
|
||||||
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
|
// message passed to Debug, Info, ...
|
||||||
|
PanicLevel Level = iota
|
||||||
|
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||||
|
// logging level is set to Panic.
|
||||||
|
FatalLevel
|
||||||
|
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||||
|
// Commonly used for hooks to send errors to an error tracking service.
|
||||||
|
ErrorLevel
|
||||||
|
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||||
|
WarnLevel
|
||||||
|
// InfoLevel level. General operational entries about what's going on inside the
|
||||||
|
// application.
|
||||||
|
InfoLevel
|
||||||
|
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||||
|
DebugLevel
|
||||||
|
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||||
|
TraceLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||||
|
var (
|
||||||
|
_ StdLogger = &log.Logger{}
|
||||||
|
_ StdLogger = &Entry{}
|
||||||
|
_ StdLogger = &Logger{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
|
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||||
|
// interface, this is the closest we get, unfortunately.
|
||||||
|
type StdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FieldLogger interface generalizes the Entry and Logger types
|
||||||
|
type FieldLogger interface {
|
||||||
|
WithField(key string, value interface{}) *Entry
|
||||||
|
WithFields(fields Fields) *Entry
|
||||||
|
WithError(err error) *Entry
|
||||||
|
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Warningf(format string, args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Panicf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Debug(args ...interface{})
|
||||||
|
Info(args ...interface{})
|
||||||
|
Print(args ...interface{})
|
||||||
|
Warn(args ...interface{})
|
||||||
|
Warning(args ...interface{})
|
||||||
|
Error(args ...interface{})
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Panic(args ...interface{})
|
||||||
|
|
||||||
|
Debugln(args ...interface{})
|
||||||
|
Infoln(args ...interface{})
|
||||||
|
Println(args ...interface{})
|
||||||
|
Warnln(args ...interface{})
|
||||||
|
Warningln(args ...interface{})
|
||||||
|
Errorln(args ...interface{})
|
||||||
|
Fatalln(args ...interface{})
|
||||||
|
Panicln(args ...interface{})
|
||||||
|
|
||||||
|
// IsDebugEnabled() bool
|
||||||
|
// IsInfoEnabled() bool
|
||||||
|
// IsWarnEnabled() bool
|
||||||
|
// IsErrorEnabled() bool
|
||||||
|
// IsFatalEnabled() bool
|
||||||
|
// IsPanicEnabled() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
||||||
|
// here for consistancy. Do not use. Use Logger or Entry instead.
|
||||||
|
type Ext1FieldLogger interface {
|
||||||
|
FieldLogger
|
||||||
|
Tracef(format string, args ...interface{})
|
||||||
|
Trace(args ...interface{})
|
||||||
|
Traceln(args ...interface{})
|
||||||
|
}
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user