betterlog 0.20.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +72 -0
- data/.semaphore/semaphore.yml +0 -18
- data/.tool-versions +2 -3
- data/Rakefile +1 -3
- data/VERSION +1 -1
- data/betterlog.gemspec +10 -14
- data/bin/betterlog +6 -0
- data/lib/betterlog/version.rb +1 -1
- data/lib/betterlog.rb +1 -1
- data/spec/betterlog/global_metadata_spec.rb +9 -7
- data/spec/betterlog/version_spec.rb +7 -0
- data/spec/spec_helper.rb +0 -6
- metadata +8 -52
- data/Dockerfile +0 -44
- data/Makefile +0 -71
- data/TODO.md +0 -1
- data/betterlog/config.go +0 -11
- data/betterlog/healthz.go +0 -71
- data/betterlog/redis_cert_cache.go +0 -83
- data/bin/betterlog_pusher +0 -51
- data/bin/betterlog_sink +0 -47
- data/cloudbuild.yaml +0 -7
- data/cmd/betterlog-server/LICENSE +0 -13
- data/cmd/betterlog-server/main.go +0 -83
- data/go.mod +0 -16
- data/go.sum +0 -136
- data/lib/betterlog/logger.rb +0 -143
- data/spec/betterlog/logger_spec.rb +0 -96
@@ -1,83 +0,0 @@
|
|
1
|
-
package betterlog
|
2
|
-
|
3
|
-
import (
|
4
|
-
"context"
|
5
|
-
"strings"
|
6
|
-
|
7
|
-
"github.com/go-redis/redis"
|
8
|
-
"golang.org/x/crypto/acme/autocert"
|
9
|
-
)
|
10
|
-
|
11
|
-
type RedisCertCache struct {
|
12
|
-
Redis *redis.Client
|
13
|
-
PREFIX string
|
14
|
-
}
|
15
|
-
|
16
|
-
// Get reads certificate data from the specified key name.
|
17
|
-
func (cache RedisCertCache) Get(ctx context.Context, name string) ([]byte, error) {
|
18
|
-
name = strings.Join([]string{cache.PREFIX, name}, "/")
|
19
|
-
done := make(chan struct{})
|
20
|
-
var (
|
21
|
-
err error
|
22
|
-
data string
|
23
|
-
)
|
24
|
-
go func() {
|
25
|
-
defer close(done)
|
26
|
-
result := cache.Redis.Get(name)
|
27
|
-
err = result.Err()
|
28
|
-
if err == nil {
|
29
|
-
data, err = result.Result()
|
30
|
-
}
|
31
|
-
}()
|
32
|
-
select {
|
33
|
-
case <-ctx.Done():
|
34
|
-
return nil, ctx.Err()
|
35
|
-
case <-done:
|
36
|
-
}
|
37
|
-
if err == redis.Nil {
|
38
|
-
return nil, autocert.ErrCacheMiss
|
39
|
-
}
|
40
|
-
return []byte(data), err
|
41
|
-
}
|
42
|
-
|
43
|
-
// Put writes the certificate data to the specified redis key name.
|
44
|
-
func (cache RedisCertCache) Put(ctx context.Context, name string, data []byte) error {
|
45
|
-
name = strings.Join([]string{cache.PREFIX, name}, "/")
|
46
|
-
done := make(chan struct{})
|
47
|
-
var err error
|
48
|
-
go func() {
|
49
|
-
defer close(done)
|
50
|
-
select {
|
51
|
-
case <-ctx.Done():
|
52
|
-
// Don't overwrite the key if the context was canceled.
|
53
|
-
default:
|
54
|
-
result := cache.Redis.Set(name, string(data), 0)
|
55
|
-
err = result.Err()
|
56
|
-
}
|
57
|
-
}()
|
58
|
-
select {
|
59
|
-
case <-ctx.Done():
|
60
|
-
return ctx.Err()
|
61
|
-
case <-done:
|
62
|
-
}
|
63
|
-
return err
|
64
|
-
}
|
65
|
-
|
66
|
-
// Delete removes the specified key name.
|
67
|
-
func (cache RedisCertCache) Delete(ctx context.Context, name string) error {
|
68
|
-
name = strings.Join([]string{cache.PREFIX, name}, "/")
|
69
|
-
var (
|
70
|
-
err error
|
71
|
-
done = make(chan struct{})
|
72
|
-
)
|
73
|
-
go func() {
|
74
|
-
defer close(done)
|
75
|
-
err = cache.Redis.Del(name).Err()
|
76
|
-
}()
|
77
|
-
select {
|
78
|
-
case <-ctx.Done():
|
79
|
-
return ctx.Err()
|
80
|
-
case <-done:
|
81
|
-
}
|
82
|
-
return err
|
83
|
-
}
|
data/bin/betterlog_pusher
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# vim: set ft=ruby et sw=2 ts=2:
|
3
|
-
|
4
|
-
require 'betterlog'
|
5
|
-
require 'excon'
|
6
|
-
require 'redlock'
|
7
|
-
|
8
|
-
lines = Integer(ENV.fetch('BETTERLOG_LINES', 1_000))
|
9
|
-
lock_time = Integer(ENV.fetch('BETTERLOG_LOCK_TIME', 60_000))
|
10
|
-
url = ENV.fetch('BETTERLOG_SERVER_URL')
|
11
|
-
name = ENV['BETTERLOG_NAME']
|
12
|
-
redis_url = ENV.fetch('REDIS_URL')
|
13
|
-
redis =
|
14
|
-
if redis_sentinels = ENV['REDIS_SENTINELS']&.split(?,)
|
15
|
-
redis_sentinels.map! { |s|
|
16
|
-
h, p = s.split(?:, 2)
|
17
|
-
{ host: h, port: (p || 26379).to_i }
|
18
|
-
}
|
19
|
-
Redis.new(url: redis_url, sentinels: redis_sentinels, role: :master)
|
20
|
-
else
|
21
|
-
Redis.new(url: redis_url)
|
22
|
-
end
|
23
|
-
lm = Redlock::Client.new([ redis_url ])
|
24
|
-
logger = Betterlog::Logger.new(redis, name: name)
|
25
|
-
|
26
|
-
quit = false
|
27
|
-
[ :TERM, :INT, :QUIT ].each { |s| trap(s) { quit = true } }
|
28
|
-
|
29
|
-
STDOUT.sync = true
|
30
|
-
loop do
|
31
|
-
count = 0
|
32
|
-
lm.lock!(File.basename($0), lock_time) do
|
33
|
-
print ?…
|
34
|
-
logger.each_slice(lines).with_index do |batch, i|
|
35
|
-
count.zero? and print ?○
|
36
|
-
count += batch.sum(&:size)
|
37
|
-
attempt(attempts: 10, sleep: -60, reraise: true) do
|
38
|
-
print ?┄
|
39
|
-
Excon.post(url, body: batch.join)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
quit and exit
|
44
|
-
if count.zero?
|
45
|
-
sleep 1
|
46
|
-
else
|
47
|
-
print "→ %s sent.\n" % Tins::Unit.format(count, format: '%.2f %U', prefix: 1024, unit: ?b)
|
48
|
-
end
|
49
|
-
rescue Redlock::LockError => e
|
50
|
-
STDERR.puts "Caught #{e.class}: #{e} => Retrying!"
|
51
|
-
end
|
data/bin/betterlog_sink
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# vim: set ft=ruby et sw=2 ts=2:
|
3
|
-
|
4
|
-
require 'betterlog'
|
5
|
-
require 'complex_config/rude'
|
6
|
-
require 'json'
|
7
|
-
|
8
|
-
LOG_ENV = ENV.fetch('LOG_ENV', 'production')
|
9
|
-
|
10
|
-
@config = complex_config.log(LOG_ENV).sink
|
11
|
-
|
12
|
-
ENV['KUBECONFIG'] = File.join(
|
13
|
-
ENV.fetch('HOME'),
|
14
|
-
'.kube',
|
15
|
-
@config.cluster.name
|
16
|
-
)
|
17
|
-
|
18
|
-
def authenticate
|
19
|
-
return if File.exist?(ENV['KUBECONFIG'])
|
20
|
-
context = [
|
21
|
-
'gke',
|
22
|
-
@config.cluster.project,
|
23
|
-
@config.cluster.zone,
|
24
|
-
@config.cluster.name,
|
25
|
-
] * ?_
|
26
|
-
system(%{
|
27
|
-
gcloud --no-user-output-enabled container clusters get-credentials \
|
28
|
-
#{@config.cluster.name.inspect} \
|
29
|
-
--zone #{@config.cluster.zone.inspect} \
|
30
|
-
--project #{@config.cluster.project.inspect}
|
31
|
-
}) or fail "failed to authenticate for context #{context.inspect}"
|
32
|
-
end
|
33
|
-
|
34
|
-
authenticate
|
35
|
-
|
36
|
-
pods = JSON(
|
37
|
-
%x{ kubectl -n #{@config.namespace} -ojson get pods },
|
38
|
-
object_class: JSON::GenericObject
|
39
|
-
)
|
40
|
-
|
41
|
-
if pod = pods.items.find { |i| i.metadata.labels.app == @config.source }
|
42
|
-
system %{
|
43
|
-
kubectl logs -n #{@config.namespace} #{ARGV.join(' ')} #{pod.metadata.name}
|
44
|
-
}
|
45
|
-
else
|
46
|
-
exit 1
|
47
|
-
end
|
data/cloudbuild.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
steps:
|
2
|
-
- name: eu.gcr.io/betterplace-183212/better-builder
|
3
|
-
args: ['build', '-t', 'eu.gcr.io/$PROJECT_ID/${_IMAGE_NAME}:$SHORT_SHA', '-t', 'eu.gcr.io/$PROJECT_ID/${_IMAGE_NAME}', '.']
|
4
|
-
images:
|
5
|
-
- 'eu.gcr.io/$PROJECT_ID/${_IMAGE_NAME}:$SHORT_SHA'
|
6
|
-
- 'eu.gcr.io/$PROJECT_ID/${_IMAGE_NAME}'
|
7
|
-
timeout: 600s
|
@@ -1,13 +0,0 @@
|
|
1
|
-
Copyright 2018 Florian Frank
|
2
|
-
|
3
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
you may not use this file except in compliance with the License.
|
5
|
-
You may obtain a copy of the License at
|
6
|
-
|
7
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
|
9
|
-
Unless required by applicable law or agreed to in writing, software
|
10
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
See the License for the specific language governing permissions and
|
13
|
-
limitations under the License.
|
@@ -1,83 +0,0 @@
|
|
1
|
-
package main
|
2
|
-
|
3
|
-
import (
|
4
|
-
"fmt"
|
5
|
-
"io/ioutil"
|
6
|
-
"log"
|
7
|
-
"net/http"
|
8
|
-
"os"
|
9
|
-
"strings"
|
10
|
-
|
11
|
-
betterlog "github.com/betterplace/betterlog/betterlog"
|
12
|
-
"github.com/go-redis/redis"
|
13
|
-
"github.com/kelseyhightower/envconfig"
|
14
|
-
"github.com/labstack/echo/v4"
|
15
|
-
"github.com/labstack/echo/v4/middleware"
|
16
|
-
)
|
17
|
-
|
18
|
-
func postLogHandler(c echo.Context) error {
|
19
|
-
body := c.Request().Body
|
20
|
-
data, err := ioutil.ReadAll(body)
|
21
|
-
if err == nil {
|
22
|
-
defer body.Close()
|
23
|
-
os.Stdout.Write(data)
|
24
|
-
return c.NoContent(http.StatusOK)
|
25
|
-
} else {
|
26
|
-
return c.String(http.StatusInternalServerError, err.Error())
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
func basicAuthConfig(config betterlog.Config) middleware.BasicAuthConfig {
|
31
|
-
return middleware.BasicAuthConfig{
|
32
|
-
Realm: config.HTTP_REALM,
|
33
|
-
Validator: func(username, password string, c echo.Context) (bool, error) {
|
34
|
-
httpAuth := strings.Split(config.HTTP_AUTH, ":")
|
35
|
-
if username == httpAuth[0] && password == httpAuth[1] {
|
36
|
-
return true, nil
|
37
|
-
}
|
38
|
-
return false, nil
|
39
|
-
},
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
func initializeRedis(config betterlog.Config) *redis.Client {
|
44
|
-
options, err := redis.ParseURL(config.REDIS_URL)
|
45
|
-
if err != nil {
|
46
|
-
log.Panic(err)
|
47
|
-
}
|
48
|
-
options.MaxRetries = 3
|
49
|
-
return redis.NewClient(options)
|
50
|
-
}
|
51
|
-
|
52
|
-
func main() {
|
53
|
-
var config betterlog.Config
|
54
|
-
err := envconfig.Process("", &config)
|
55
|
-
if err != nil {
|
56
|
-
log.Fatal(err)
|
57
|
-
}
|
58
|
-
e := echo.New()
|
59
|
-
|
60
|
-
if !config.SSL {
|
61
|
-
e.Use(betterlog.Health{}.ZPage)
|
62
|
-
}
|
63
|
-
if config.HTTP_AUTH != "" {
|
64
|
-
fmt.Println("info: Configuring HTTP Auth access control")
|
65
|
-
e.Use(middleware.BasicAuthWithConfig(basicAuthConfig(config)))
|
66
|
-
}
|
67
|
-
e.POST("/log", postLogHandler)
|
68
|
-
if config.SSL {
|
69
|
-
log.Println("Starting SSL AutoTLS service.")
|
70
|
-
redis := initializeRedis(config)
|
71
|
-
e.AutoTLSManager.Cache = betterlog.RedisCertCache{
|
72
|
-
Redis: redis,
|
73
|
-
PREFIX: config.REDIS_PREFIX,
|
74
|
-
}
|
75
|
-
go betterlog.StartHealthzEcho(
|
76
|
-
betterlog.Health{
|
77
|
-
PORT: config.HEALTHZ_PORT,
|
78
|
-
})
|
79
|
-
e.Logger.Fatal(e.StartAutoTLS(fmt.Sprintf(":%d", config.PORT)))
|
80
|
-
} else {
|
81
|
-
e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", config.PORT)))
|
82
|
-
}
|
83
|
-
}
|
data/go.mod
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module github.com/betterplace/betterlog
|
2
|
-
|
3
|
-
go 1.16
|
4
|
-
|
5
|
-
require (
|
6
|
-
github.com/go-redis/redis v6.15.9+incompatible
|
7
|
-
github.com/kelseyhightower/envconfig v1.4.0
|
8
|
-
github.com/labstack/echo v3.3.10+incompatible
|
9
|
-
github.com/labstack/echo/v4 v4.5.0
|
10
|
-
github.com/mattn/go-isatty v0.0.13 // indirect
|
11
|
-
github.com/onsi/gomega v1.16.0 // indirect
|
12
|
-
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
13
|
-
golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect
|
14
|
-
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
15
|
-
golang.org/x/text v0.3.7 // indirect
|
16
|
-
)
|
data/go.sum
DELETED
@@ -1,136 +0,0 @@
|
|
1
|
-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
2
|
-
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
3
|
-
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
4
|
-
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
5
|
-
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
6
|
-
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
7
|
-
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
8
|
-
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
9
|
-
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
10
|
-
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
11
|
-
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
12
|
-
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
13
|
-
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
14
|
-
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
15
|
-
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
16
|
-
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
17
|
-
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
18
|
-
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
19
|
-
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
20
|
-
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
21
|
-
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
22
|
-
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
23
|
-
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
24
|
-
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
25
|
-
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
26
|
-
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
27
|
-
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
28
|
-
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
29
|
-
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
30
|
-
github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY=
|
31
|
-
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
32
|
-
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
33
|
-
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
34
|
-
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
35
|
-
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
36
|
-
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
37
|
-
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
38
|
-
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
39
|
-
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
40
|
-
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
41
|
-
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
42
|
-
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
43
|
-
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
44
|
-
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
45
|
-
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
46
|
-
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
47
|
-
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
48
|
-
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
49
|
-
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
50
|
-
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
51
|
-
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
52
|
-
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
53
|
-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
54
|
-
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
55
|
-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
56
|
-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
57
|
-
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
58
|
-
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
59
|
-
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
60
|
-
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
61
|
-
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
62
|
-
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
63
|
-
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
64
|
-
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
65
|
-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
66
|
-
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
67
|
-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
68
|
-
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
69
|
-
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
70
|
-
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
71
|
-
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
72
|
-
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
73
|
-
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
74
|
-
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
75
|
-
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
76
|
-
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
77
|
-
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
78
|
-
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
79
|
-
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
80
|
-
golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw=
|
81
|
-
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
82
|
-
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
83
|
-
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
84
|
-
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
85
|
-
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
86
|
-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
87
|
-
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
88
|
-
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
89
|
-
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
90
|
-
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
91
|
-
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
92
|
-
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
93
|
-
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
94
|
-
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
95
|
-
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
96
|
-
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
97
|
-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
98
|
-
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
99
|
-
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
100
|
-
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
101
|
-
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
102
|
-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
103
|
-
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k=
|
104
|
-
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
105
|
-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
106
|
-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
107
|
-
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
108
|
-
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
109
|
-
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
110
|
-
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
111
|
-
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
|
112
|
-
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
113
|
-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
114
|
-
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
115
|
-
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
116
|
-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
117
|
-
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
118
|
-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
119
|
-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
120
|
-
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
121
|
-
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
122
|
-
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
123
|
-
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
124
|
-
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
125
|
-
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
126
|
-
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
127
|
-
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
128
|
-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
129
|
-
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
130
|
-
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
131
|
-
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
132
|
-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
133
|
-
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
134
|
-
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
135
|
-
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
136
|
-
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
data/lib/betterlog/logger.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'redis' unless defined?(Redis)
|
3
|
-
rescue LoadError
|
4
|
-
end
|
5
|
-
require 'logger'
|
6
|
-
|
7
|
-
module Betterlog
|
8
|
-
class Logger < ::Logger
|
9
|
-
include ComplexConfig::Provider::Shortcuts
|
10
|
-
|
11
|
-
def initialize(redis, shift_age = 0, shift_size = 1048576, name: nil, buffer_size: nil, **opts)
|
12
|
-
@redis = redis
|
13
|
-
@fallback = ::Logger.new(STDERR)
|
14
|
-
if level = cc.log.level?
|
15
|
-
self.level = level
|
16
|
-
@fallback.level = level
|
17
|
-
end
|
18
|
-
@name = name || self.class.name
|
19
|
-
@buffer_size = determine_buffer_size(buffer_size)
|
20
|
-
super(nil, shift_age, shift_size, **opts)
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.for_redis_url(url, shift_age = 0, shift_size = 1048576, **opts)
|
24
|
-
redis = Redis.new(url: url)
|
25
|
-
redis.ping
|
26
|
-
new(redis, shift_age, shift_size, **opts)
|
27
|
-
rescue Redis::CannotConnectError
|
28
|
-
end
|
29
|
-
|
30
|
-
private def determine_buffer_size(buffer_size)
|
31
|
-
if buffer_size.nil? || buffer_size < 1 * 1024 ** 2
|
32
|
-
1 * 1024 ** 2 # Default to very small buffer
|
33
|
-
elsif buffer_size > 511 * 1024 ** 2
|
34
|
-
511 * 1024 ** 2 # Stay well below redis' 512Mb upper limit for strings
|
35
|
-
else
|
36
|
-
buffer_size
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
private def redis_write(msg)
|
41
|
-
# Stop before reaching configured buffer_size limit, after warning a lot.
|
42
|
-
if @redis.strlen(@name) > (@buffer_size * 96) / 100
|
43
|
-
@fallback.error("Redis memory limit will soon be reached =>"\
|
44
|
-
" Log output to redis stops now unless log data is pushed away!")
|
45
|
-
return nil
|
46
|
-
end
|
47
|
-
@redis.append @name, msg
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
def add(severity, message = nil, progname = nil)
|
53
|
-
severity ||= UNKNOWN
|
54
|
-
if severity < @level
|
55
|
-
return true
|
56
|
-
end
|
57
|
-
if progname.nil?
|
58
|
-
progname = @progname
|
59
|
-
end
|
60
|
-
if message.nil?
|
61
|
-
if block_given?
|
62
|
-
message = yield
|
63
|
-
else
|
64
|
-
message = progname
|
65
|
-
progname = @progname
|
66
|
-
end
|
67
|
-
end
|
68
|
-
redis_write(
|
69
|
-
format_message(format_severity(severity), Time.now, progname, message))
|
70
|
-
true
|
71
|
-
rescue Redis::BaseConnectionError
|
72
|
-
@fallback.add(severity, message, progname)
|
73
|
-
end
|
74
|
-
|
75
|
-
def <<(msg)
|
76
|
-
redis_write(msg)
|
77
|
-
rescue Redis::BaseConnectionError
|
78
|
-
@fallback << msg
|
79
|
-
end
|
80
|
-
|
81
|
-
def clear
|
82
|
-
@redis.del @name
|
83
|
-
self
|
84
|
-
end
|
85
|
-
|
86
|
-
def each_chunk(chunk_size: 100 * 1024, &block)
|
87
|
-
chunk_size > 0 or raise ArgumentError, 'chunk_size > 0 required'
|
88
|
-
|
89
|
-
# Delete any remaining temporary keys if we were interrtupted earlier
|
90
|
-
# (or in some other process.)
|
91
|
-
@redis.scan_each(match: "#{@name}_*") do |key|
|
92
|
-
@redis.del key
|
93
|
-
rescue Redis::BaseConnectionError
|
94
|
-
end
|
95
|
-
|
96
|
-
@redis.exists?(@name) or return Enumerator.new {}
|
97
|
-
|
98
|
-
Enumerator.new do |y|
|
99
|
-
name_tmp = "#{@name}_#{rand}"
|
100
|
-
@redis.rename @name, name_tmp
|
101
|
-
|
102
|
-
s = 0
|
103
|
-
e = @redis.strlen(name_tmp) - 1
|
104
|
-
until s > e
|
105
|
-
range = @redis.getrange(name_tmp, s, s + chunk_size - 1)
|
106
|
-
range.force_encoding 'ASCII-8BIT'
|
107
|
-
y.yield range
|
108
|
-
s += chunk_size
|
109
|
-
end
|
110
|
-
|
111
|
-
ensure
|
112
|
-
begin
|
113
|
-
@redis.del name_tmp
|
114
|
-
rescue Redis::BaseConnectionError
|
115
|
-
# We have to delete this later if del command failed here,
|
116
|
-
# see the beginning of this method.
|
117
|
-
end
|
118
|
-
end.each(&block)
|
119
|
-
|
120
|
-
rescue Redis::BaseConnectionError => e
|
121
|
-
# Maybe it works again later, just log the error…
|
122
|
-
@fallback.error(e)
|
123
|
-
Enumerator.new {}
|
124
|
-
end
|
125
|
-
|
126
|
-
def each(chunk_size: 100 * 1024, &block)
|
127
|
-
chunk_size > 0 or raise ArgumentError, 'chunk_size > 0 required'
|
128
|
-
Enumerator.new do |y|
|
129
|
-
buffer = ''
|
130
|
-
buffer.encode! 'ASCII-8BIT'
|
131
|
-
each_chunk(chunk_size: chunk_size) do |chunk|
|
132
|
-
buffer << chunk
|
133
|
-
buffer.gsub!(/\A(.*?#$/)/n) do |line|
|
134
|
-
y.yield(line)
|
135
|
-
''
|
136
|
-
end
|
137
|
-
end
|
138
|
-
buffer.length > 0 and y.yield(buffer)
|
139
|
-
end.each(&block)
|
140
|
-
end
|
141
|
-
include Enumerable
|
142
|
-
end
|
143
|
-
end
|