logstash-output-qingstor 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|