logstash-output-qingstor 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +1 -1
- data/lib/logstash/outputs/qingstor/file_repository.rb +64 -49
- data/lib/logstash/outputs/qingstor/qingstor_validator.rb +19 -18
- data/lib/logstash/outputs/qingstor/rotation_policy.rb +35 -28
- data/lib/logstash/outputs/qingstor/temporary_file.rb +38 -36
- data/lib/logstash/outputs/qingstor/temporary_file_factory.rb +61 -57
- data/lib/logstash/outputs/qingstor/uploader.rb +41 -36
- data/lib/logstash/outputs/qingstor.rb +144 -124
- data/logstash-output-qingstor.gemspec +12 -12
- data/spec/outputs/qingstor/file_repository_spec.rb +24 -22
- data/spec/outputs/qingstor/qingstor_validator_spec.rb +14 -12
- data/spec/outputs/qingstor/rotation_policy_spec.rb +58 -43
- data/spec/outputs/qingstor/temporary_file_factory_spec.rb +34 -32
- data/spec/outputs/qingstor/temporary_file_spec.rb +31 -30
- data/spec/outputs/qingstor/uploader_spec.rb +29 -30
- data/spec/outputs/qingstor_spec.rb +53 -44
- data/spec/outputs/qs_access_helper.rb +8 -8
- data/spec/outputs/spec_helper.rb +3 -3
- metadata +3 -5
- data/lib/logstash/outputs/qingstor/size_rotation_policy.rb +0 -26
- data/lib/logstash/outputs/qingstor/time_rotation_policy.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1fb6b25a4999d3628887cb326a791afddb002dd
|
4
|
+
data.tar.gz: d72aca418dc34fe47c11f303c7f48e459a35715c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 176d12530d09b496dd051ea70b8694ceda11307b301265114fb64aa46c76564b7a82304489508e37ea0adfedc95656badf0ba0d24aa74236fb06dee5a221835d
|
7
|
+
data.tar.gz: 4ba9d2490ce1564cf202803346fa8661b83e960743161113a9d3248c89e3d618792d57558c3397d2f224470b17ff5a39805344b80eb40ea306825f75a963ea98
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
|
3
|
+
require 'logstash/util'
|
4
|
+
require 'logstash/outputs/qingstor'
|
5
|
+
require 'java'
|
6
|
+
require 'concurrent'
|
7
|
+
require 'concurrent/timer_task'
|
6
8
|
|
7
9
|
ConcurrentHashMap = java.util.concurrent.ConcurrentHashMap
|
8
10
|
|
9
|
-
module LogStash
|
11
|
+
module LogStash
|
10
12
|
module Outputs
|
11
13
|
class Qingstor
|
12
14
|
class FileRepository
|
@@ -20,22 +22,25 @@ module LogStash
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def with_lock
|
23
|
-
@lock.synchronize
|
25
|
+
@lock.synchronize do
|
24
26
|
yield @file_factory
|
25
|
-
|
26
|
-
end
|
27
|
+
end
|
28
|
+
end
|
27
29
|
|
28
30
|
def stale?
|
29
|
-
with_lock
|
30
|
-
|
31
|
+
with_lock do |factory|
|
32
|
+
factory.current.empty? &&
|
33
|
+
(Time.now - factory.current.ctime > @stale_time)
|
34
|
+
end
|
35
|
+
end
|
31
36
|
|
32
|
-
def apply(
|
33
|
-
|
34
|
-
end
|
37
|
+
def apply(_prefix)
|
38
|
+
self
|
39
|
+
end
|
35
40
|
|
36
|
-
def delete!
|
41
|
+
def delete!
|
37
42
|
with_lock { |factory| factory.current.delete! }
|
38
|
-
end
|
43
|
+
end
|
39
44
|
end
|
40
45
|
|
41
46
|
class FactoryInitializer
|
@@ -44,69 +49,79 @@ module LogStash
|
|
44
49
|
@encoding = encoding
|
45
50
|
@temporary_directory = temporary_directory
|
46
51
|
@stale_time = stale_time
|
47
|
-
end
|
52
|
+
end
|
48
53
|
|
49
54
|
def apply(prefix_key)
|
50
|
-
PrefixedValue.new(
|
51
|
-
|
52
|
-
|
55
|
+
PrefixedValue.new(
|
56
|
+
TemporaryFileFactory.new(prefix_key, @tags, @encoding,
|
57
|
+
@temporary_directory),
|
58
|
+
@stale_time
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
53
62
|
|
54
63
|
def initialize(tags, encoding, temporary_directory,
|
55
64
|
stale_time = DEFAULT_STATE_TIME_SECS,
|
56
65
|
sweeper_interval = DEFAULT_STATE_SWEEPER_INTERVAL_SECS)
|
57
|
-
@prefixed_factories = ConcurrentHashMap.new
|
66
|
+
@prefixed_factories = ConcurrentHashMap.new
|
58
67
|
@sweeper_interval = sweeper_interval
|
59
|
-
@factoryinitializer = FactoryInitializer.new(tags,
|
68
|
+
@factoryinitializer = FactoryInitializer.new(tags,
|
69
|
+
encoding,
|
70
|
+
temporary_directory,
|
71
|
+
stale_time)
|
60
72
|
|
61
73
|
start_stale_sweeper
|
62
74
|
end
|
63
75
|
|
64
76
|
def keys
|
65
77
|
@prefixed_factories.keySet
|
66
|
-
end
|
78
|
+
end
|
67
79
|
|
68
|
-
def each_files
|
80
|
+
def each_files
|
69
81
|
@prefixed_factories.elements.each do |prefixed_file|
|
70
82
|
prefixed_file.with_lock { |factory| yield factory.current }
|
71
|
-
end
|
72
|
-
end
|
83
|
+
end
|
84
|
+
end
|
73
85
|
|
74
86
|
def get_factory(prefix_key)
|
75
|
-
@prefixed_factories.computeIfAbsent(prefix_key, @factoryinitializer)
|
76
|
-
|
87
|
+
@prefixed_factories.computeIfAbsent(prefix_key, @factoryinitializer)
|
88
|
+
.with_lock { |factory| yield factory }
|
89
|
+
end
|
77
90
|
|
78
91
|
def get_file(prefix_key)
|
79
92
|
get_factory(prefix_key) { |factory| yield factory.current }
|
80
|
-
end
|
93
|
+
end
|
81
94
|
|
82
|
-
def shutdown
|
83
|
-
stop_stale_sweeper
|
84
|
-
end
|
95
|
+
def shutdown
|
96
|
+
stop_stale_sweeper
|
97
|
+
end
|
98
|
+
|
99
|
+
def size
|
100
|
+
@prefixed_factories.size
|
101
|
+
end
|
85
102
|
|
86
|
-
def size
|
87
|
-
@prefixed_factories.size
|
88
|
-
end
|
89
|
-
|
90
103
|
def remove_stale(k, v)
|
91
|
-
if v.stale?
|
104
|
+
if v.stale?
|
92
105
|
@prefixed_factories.remove(k, v)
|
93
106
|
v.delete!
|
94
|
-
end
|
95
|
-
end
|
107
|
+
end
|
108
|
+
end
|
96
109
|
|
97
|
-
def start_stale_sweeper
|
98
|
-
@stale_sweeper = Concurrent::TimerTask.new(
|
99
|
-
|
110
|
+
def start_stale_sweeper
|
111
|
+
@stale_sweeper = Concurrent::TimerTask.new(
|
112
|
+
:execution_interval => @sweeper_interval
|
113
|
+
) do
|
114
|
+
LogStash::Util.set_thread_name('Qingstor, stale factory sweeper')
|
100
115
|
@prefixed_factories.forEach { |k, v| remove_stale(k, v) }
|
101
|
-
end
|
116
|
+
end
|
102
117
|
|
103
118
|
@stale_sweeper.execute
|
104
|
-
end
|
119
|
+
end
|
105
120
|
|
106
|
-
def stop_stale_sweeper
|
121
|
+
def stop_stale_sweeper
|
107
122
|
@stale_sweeper.shutdown
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -1,30 +1,31 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require 'logstash/outputs/qingstor'
|
4
|
+
require 'qingstor/sdk'
|
4
5
|
|
5
6
|
module LogStash
|
6
7
|
module Outputs
|
7
8
|
class Qingstor
|
8
|
-
|
9
|
-
|
9
|
+
module QingstorValidator
|
10
10
|
def self.bucket_valid?(bucket)
|
11
11
|
res = bucket.head
|
12
|
-
case res[:status_code]
|
12
|
+
case res[:status_code]
|
13
13
|
when 401
|
14
|
-
raise LogStash::ConfigurationError,
|
14
|
+
raise LogStash::ConfigurationError, 'Incorrect key id/access key.'
|
15
15
|
when 404
|
16
|
-
raise LogStash::ConfigurationError,
|
17
|
-
end
|
18
|
-
true
|
19
|
-
end
|
16
|
+
raise LogStash::ConfigurationError, 'Incorrect bucket/region name.'
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
20
|
|
21
21
|
def self.prefix_valid?(prefix)
|
22
|
-
if prefix.start_with?(
|
23
|
-
raise LogStash::ConfigurationError,
|
24
|
-
|
22
|
+
if prefix.start_with?('/') || prefix.length >= 1024
|
23
|
+
raise LogStash::ConfigurationError, 'Prefix must not start with'\
|
24
|
+
+ " '/' and with length less than 1024 "
|
25
|
+
end
|
25
26
|
true
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -1,71 +1,78 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
require 'logstash/outputs/qingstor'
|
4
|
+
|
5
|
+
module LogStash
|
6
|
+
module Outputs
|
4
7
|
class Qingstor
|
5
8
|
class RotationPolicy
|
6
|
-
|
7
9
|
def initialize(policy, file_size, file_time)
|
8
10
|
@policy = policy
|
9
11
|
case policy
|
10
|
-
when
|
12
|
+
when 'time'
|
11
13
|
init_time(file_time)
|
12
|
-
when
|
14
|
+
when 'size'
|
13
15
|
init_size(file_size)
|
14
|
-
when
|
16
|
+
when 'size_and_time'
|
15
17
|
init_size(file_size)
|
16
|
-
init_time(file_time)
|
17
|
-
end
|
18
|
-
end
|
18
|
+
init_time(file_time)
|
19
|
+
end
|
20
|
+
end
|
19
21
|
|
20
22
|
def init_size(file_size)
|
21
23
|
if file_size <= 0
|
22
|
-
raise LogStash::ConfigurationError, "'file_size' need to
|
24
|
+
raise LogStash::ConfigurationError, "'file_size' need to "\
|
25
|
+
+ 'be greater than 0'
|
23
26
|
end
|
24
27
|
@file_size = file_size
|
25
|
-
end
|
28
|
+
end
|
26
29
|
|
27
30
|
def init_time(file_time)
|
28
31
|
if file_time <= 0
|
29
|
-
raise LogStash::ConfigurationError, "'file_time' need to
|
30
|
-
|
32
|
+
raise LogStash::ConfigurationError, "'file_time' need to "\
|
33
|
+
+ 'be greater than 0'
|
34
|
+
end
|
31
35
|
@file_time = file_time
|
32
|
-
end
|
36
|
+
end
|
33
37
|
|
34
38
|
def rotate?(file)
|
35
39
|
case @policy
|
36
|
-
when
|
40
|
+
when 'time'
|
37
41
|
time_rotate?(file)
|
38
|
-
when
|
42
|
+
when 'size'
|
39
43
|
size_rotate?(file)
|
40
|
-
when
|
44
|
+
when 'size_and_time'
|
41
45
|
size_and_time_rotate?(file)
|
42
46
|
end
|
43
|
-
end
|
47
|
+
end
|
44
48
|
|
45
49
|
def size_and_time_rotate?(file)
|
46
50
|
size_rotate?(file) || time_rotate?(file)
|
47
|
-
end
|
51
|
+
end
|
48
52
|
|
49
53
|
def size_rotate?(file)
|
50
54
|
file.size >= @file_size
|
51
|
-
end
|
55
|
+
end
|
52
56
|
|
53
57
|
def time_rotate?(file)
|
54
|
-
file.
|
55
|
-
end
|
58
|
+
!file.empty? && (Time.now - file.ctime) >= @file_time
|
59
|
+
end
|
56
60
|
|
57
61
|
def needs_periodic?
|
58
62
|
case @policy
|
59
|
-
when
|
63
|
+
when 'time' then
|
60
64
|
true
|
61
|
-
when
|
65
|
+
when 'size_and_time' then
|
62
66
|
true
|
63
67
|
else
|
64
68
|
false
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
+
def to_s
|
73
|
+
"#{@policy} #{@file_time} #{@file_size}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
2
|
+
|
3
|
+
require 'logstash/outputs/qingstor'
|
4
|
+
require 'thread'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'pathname'
|
6
8
|
|
7
9
|
module LogStash
|
8
10
|
module Outputs
|
@@ -15,49 +17,49 @@ module LogStash
|
|
15
17
|
attr_reader :fd
|
16
18
|
|
17
19
|
def initialize(key, fd, tmp_path)
|
18
|
-
@key = key
|
20
|
+
@key = key
|
19
21
|
@fd = fd
|
20
22
|
@tmp_path = tmp_path
|
21
23
|
@created_at = Time.now
|
22
|
-
end
|
24
|
+
end
|
23
25
|
|
24
|
-
def ctime
|
25
|
-
@created_at
|
26
|
-
end
|
27
|
-
|
28
|
-
def tmp_path
|
29
|
-
@tmp_path
|
30
|
-
end
|
26
|
+
def ctime
|
27
|
+
@created_at
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
begin
|
34
|
-
@fd.size
|
35
|
-
rescue IOError
|
36
|
-
::File.size(path)
|
37
|
-
end
|
38
|
-
end
|
30
|
+
attr_reader :tmp_path
|
39
31
|
|
40
|
-
def
|
41
|
-
@
|
42
|
-
|
32
|
+
def size
|
33
|
+
@fd.size
|
34
|
+
rescue IOError
|
35
|
+
::File.size(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def key
|
39
|
+
@key.gsub(/^\//, '')
|
40
|
+
end
|
43
41
|
|
44
42
|
def delete!
|
45
|
-
|
43
|
+
begin
|
44
|
+
@fd.close
|
45
|
+
rescue
|
46
|
+
IOError
|
47
|
+
end
|
46
48
|
FileUtils.rm_r(@tmp_path, :secure => true)
|
47
|
-
end
|
49
|
+
end
|
48
50
|
|
49
51
|
def empty?
|
50
|
-
size
|
51
|
-
end
|
52
|
+
size.zero?
|
53
|
+
end
|
52
54
|
|
53
55
|
def self.create_from_existing_file(file_path, tmp_folder)
|
54
|
-
key_parts = Pathname.new(file_path).relative_path_from(tmp_folder)
|
55
|
-
|
56
|
-
|
56
|
+
key_parts = Pathname.new(file_path).relative_path_from(tmp_folder)
|
57
|
+
.to_s.split(::File::SEPARATOR)
|
58
|
+
TemporaryFile.new(key_parts.slice(1, key_parts.size).join('/'),
|
59
|
+
::File.open(file_path, 'r'),
|
57
60
|
::File.join(tmp_folder, key_parts.slice(0, 1)))
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,19 +1,21 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
2
|
+
|
3
|
+
require 'logstash/outputs/qingstor'
|
4
|
+
require 'socket'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'zlib'
|
8
|
+
require 'forwardable'
|
7
9
|
|
8
10
|
module LogStash
|
9
11
|
module Outputs
|
10
12
|
class Qingstor
|
11
13
|
class TemporaryFileFactory
|
12
|
-
FILE_MODE =
|
13
|
-
GZIP_ENCODING =
|
14
|
-
GZIP_EXTENSION =
|
15
|
-
TXT_EXTENSION =
|
16
|
-
STRFTIME =
|
14
|
+
FILE_MODE = 'a'.freeze
|
15
|
+
GZIP_ENCODING = 'gzip'.freeze
|
16
|
+
GZIP_EXTENSION = 'log.gz'.freeze
|
17
|
+
TXT_EXTENSION = 'log'.freeze
|
18
|
+
STRFTIME = '%Y-%m-%dT%H.%M'.freeze
|
17
19
|
|
18
20
|
attr_accessor :counter, :tags, :prefix, :encoding, :tmpdir, :current
|
19
21
|
|
@@ -23,88 +25,90 @@ module LogStash
|
|
23
25
|
@tags = tags
|
24
26
|
@encoding = encoding
|
25
27
|
@tmpdir = tmpdir
|
26
|
-
@lock = Mutex.new
|
28
|
+
@lock = Mutex.new
|
27
29
|
|
28
30
|
rotate!
|
29
|
-
end
|
31
|
+
end
|
30
32
|
|
31
33
|
def rotate!
|
32
|
-
@lock.synchronize
|
34
|
+
@lock.synchronize do
|
33
35
|
@current = new_file
|
34
36
|
increment_counter
|
35
|
-
@current
|
36
|
-
|
37
|
-
end
|
37
|
+
@current
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
38
42
|
|
39
|
-
private
|
40
43
|
def extension
|
41
44
|
gzip? ? GZIP_EXTENSION : TXT_EXTENSION
|
42
|
-
end
|
43
|
-
|
45
|
+
end
|
46
|
+
|
44
47
|
def gzip?
|
45
48
|
encoding == GZIP_ENCODING
|
46
|
-
end
|
49
|
+
end
|
47
50
|
|
48
51
|
def increment_counter
|
49
52
|
@counter += 1
|
50
|
-
end
|
53
|
+
end
|
51
54
|
|
52
|
-
def current_time
|
55
|
+
def current_time
|
53
56
|
Time.new.strftime(STRFTIME)
|
54
|
-
end
|
57
|
+
end
|
55
58
|
|
56
|
-
def generate_name
|
59
|
+
def generate_name
|
57
60
|
filename = "ls.qingstor.#{SecureRandom.uuid}.#{current_time}"
|
58
61
|
|
59
|
-
if tags.
|
62
|
+
if !tags.empty?
|
60
63
|
"#{filename}.tag_#{tags.join('.')}.part#{counter}.#{extension}"
|
61
|
-
else
|
64
|
+
else
|
62
65
|
"#{filename}.part#{counter}.#{extension}"
|
63
|
-
end
|
64
|
-
end
|
66
|
+
end
|
67
|
+
end
|
65
68
|
|
66
|
-
def new_file
|
67
|
-
uuid = SecureRandom.uuid
|
68
|
-
name = generate_name
|
69
|
+
def new_file
|
70
|
+
uuid = SecureRandom.uuid
|
71
|
+
name = generate_name
|
69
72
|
path = ::File.join(@tmpdir, uuid)
|
70
73
|
key = ::File.join(@prefix, name)
|
71
74
|
|
72
75
|
FileUtils.mkdir_p(::File.join(path, @prefix))
|
73
76
|
|
74
|
-
io = if gzip?
|
75
|
-
IOWrappedGzip.new(::File.open(::File.join(path, key),
|
76
|
-
|
77
|
+
io = if gzip?
|
78
|
+
IOWrappedGzip.new(::File.open(::File.join(path, key),
|
79
|
+
FILE_MODE))
|
80
|
+
else
|
77
81
|
::File.open(::File.join(path, key), FILE_MODE)
|
78
|
-
end
|
82
|
+
end
|
79
83
|
|
80
84
|
TemporaryFile.new(key, io, path)
|
81
85
|
end
|
82
86
|
|
83
|
-
class IOWrappedGzip
|
84
|
-
extend Forwardable
|
87
|
+
class IOWrappedGzip
|
88
|
+
extend Forwardable
|
85
89
|
|
86
90
|
def_delegators :@gzip_writer, :write, :close
|
87
91
|
attr_accessor :file_io, :gzip_writer
|
88
|
-
|
92
|
+
|
89
93
|
def initialize(file_io)
|
90
94
|
@file_io = file_io
|
91
95
|
@gzip_writer = Zlib::GzipWriter.open(file_io)
|
92
|
-
end
|
93
|
-
|
94
|
-
def path
|
95
|
-
@gzip_writer.to_io.path
|
96
|
-
end
|
97
|
-
|
98
|
-
def size
|
99
|
-
@gzip_writer.flush
|
100
|
-
@gzip_writer.to_io.size
|
101
|
-
end
|
102
|
-
|
103
|
-
def fsync
|
104
|
-
@gzip_writer.to_io.fsync
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def path
|
99
|
+
@gzip_writer.to_io.path
|
100
|
+
end
|
101
|
+
|
102
|
+
def size
|
103
|
+
@gzip_writer.flush
|
104
|
+
@gzip_writer.to_io.size
|
105
|
+
end
|
106
|
+
|
107
|
+
def fsync
|
108
|
+
@gzip_writer.to_io.fsync
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|