logstash-output-oss 0.1.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTORS +10 -0
- data/DEVELOPER.md +10 -0
- data/Gemfile +2 -0
- data/LICENSE +11 -0
- data/README.md +149 -0
- data/lib/com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar +0 -0
- data/lib/com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar +0 -0
- data/lib/com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar +0 -0
- data/lib/com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar +0 -0
- data/lib/com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar +0 -0
- data/lib/com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar +0 -0
- data/lib/commons-codec/commons-codec/1.9/commons-codec-1.9.jar +0 -0
- data/lib/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
- data/lib/javax/activation/activation/1.1/activation-1.1.jar +0 -0
- data/lib/javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar +0 -0
- data/lib/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar +0 -0
- data/lib/logstash-output-oss_jars.rb +52 -0
- data/lib/logstash/outputs/oss.rb +288 -0
- data/lib/logstash/outputs/oss/file_generator.rb +69 -0
- data/lib/logstash/outputs/oss/file_manager.rb +87 -0
- data/lib/logstash/outputs/oss/file_uploader.rb +69 -0
- data/lib/logstash/outputs/oss/gzip_file.rb +36 -0
- data/lib/logstash/outputs/oss/rotations/hybrid_rotation.rb +24 -0
- data/lib/logstash/outputs/oss/rotations/size_based_rotation.rb +25 -0
- data/lib/logstash/outputs/oss/rotations/time_based_rotation.rb +25 -0
- data/lib/logstash/outputs/oss/temporary_file.rb +59 -0
- data/lib/logstash/outputs/oss/version.rb +14 -0
- data/lib/org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar +0 -0
- data/lib/org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar +0 -0
- data/lib/org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar +0 -0
- data/lib/org/codehaus/jettison/jettison/1.1/jettison-1.1.jar +0 -0
- data/lib/org/jdom/jdom/1.1/jdom-1.1.jar +0 -0
- data/lib/stax/stax-api/1.0.1/stax-api-1.0.1.jar +0 -0
- data/logstash-output-oss.gemspec +30 -0
- data/spec/integration/common.rb +42 -0
- data/spec/integration/encoding_spec.rb +92 -0
- data/spec/integration/oss_spec.rb +47 -0
- data/spec/integration/recover_spec.rb +43 -0
- data/spec/outputs/oss/generator_spec.rb +67 -0
- data/spec/outputs/oss/rotation_spec.rb +69 -0
- data/spec/outputs/oss_spec.rb +58 -0
- metadata +206 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'uuid'
|
4
|
+
require 'logstash/outputs/oss/gzip_file'
|
5
|
+
require 'logstash/outputs/oss/temporary_file'
|
6
|
+
|
7
|
+
module LogStash
|
8
|
+
module Outputs
|
9
|
+
class OSS
|
10
|
+
class FileGenerator
|
11
|
+
FILE_MODE = "a"
|
12
|
+
STRFTIME = "%Y-%m-%dT%H.%M"
|
13
|
+
|
14
|
+
attr_accessor :index, :prefix, :encoding, :temporary_directory, :current_file
|
15
|
+
|
16
|
+
# `prefix`/logstash.oss.{random-uuid}.{%Y-%m-%dT%H.%M}.part-{index}.{extension}
|
17
|
+
def initialize(prefix, encoding, temporary_directory)
|
18
|
+
@index = 0
|
19
|
+
@prefix = prefix
|
20
|
+
# gzip or plain
|
21
|
+
@encoding = encoding
|
22
|
+
# temporary directory to save temporary file before upload to OSS
|
23
|
+
@temporary_directory = temporary_directory
|
24
|
+
|
25
|
+
@lock = Mutex.new
|
26
|
+
|
27
|
+
rotate
|
28
|
+
end
|
29
|
+
|
30
|
+
def rotate
|
31
|
+
@current_file = create_file
|
32
|
+
@index += 1
|
33
|
+
@current_file
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_lock
|
37
|
+
@lock.synchronize do
|
38
|
+
yield self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def extension
|
43
|
+
@encoding == "gzip" ? "gz" : "data"
|
44
|
+
end
|
45
|
+
|
46
|
+
def gzip?
|
47
|
+
@encoding == "gzip"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def create_file
|
52
|
+
uuid = UUID.new.generate
|
53
|
+
file_name = "ls.oss.#{uuid}.#{Time.now.strftime(STRFTIME)}.part-#{index}.#{extension}"
|
54
|
+
object_key = ::File.join(prefix, file_name)
|
55
|
+
local_path = ::File.join(temporary_directory, uuid)
|
56
|
+
|
57
|
+
FileUtils.mkdir_p(::File.join(local_path, prefix))
|
58
|
+
file = if gzip?
|
59
|
+
GzipFile.new(::File.open(::File.join(local_path, object_key), FILE_MODE))
|
60
|
+
else
|
61
|
+
::File.open(::File.join(local_path, object_key), FILE_MODE)
|
62
|
+
end
|
63
|
+
|
64
|
+
TemporaryFile.new(file, object_key, local_path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "java"
|
4
|
+
require "concurrent"
|
5
|
+
require "concurrent/timer_task"
|
6
|
+
require "logstash/util"
|
7
|
+
require "logstash/outputs/oss/file_generator"
|
8
|
+
|
9
|
+
ConcurrentHashMap = java.util.concurrent.ConcurrentHashMap
|
10
|
+
|
11
|
+
module LogStash
|
12
|
+
module Outputs
|
13
|
+
class OSS
|
14
|
+
class FileManager
|
15
|
+
|
16
|
+
STALE_FILES_CHECK_INTERVAL_IN_SECONDS = 15 * 60
|
17
|
+
|
18
|
+
def initialize(logger, encoding, temporary_directory)
|
19
|
+
@logger = logger
|
20
|
+
@encoding = encoding
|
21
|
+
|
22
|
+
@temporary_directory = temporary_directory
|
23
|
+
# map of generators
|
24
|
+
# since `prefix` support string interpolation, so we will write many files at the same time
|
25
|
+
@prefixed_generators = ConcurrentHashMap.new
|
26
|
+
|
27
|
+
@file_generator_initialize = FileGeneratorInitializer.new(encoding, temporary_directory)
|
28
|
+
|
29
|
+
start_stale_files_check
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_file_generator(prefix)
|
33
|
+
@prefixed_generators.computeIfAbsent(prefix, @file_generator_initialize).with_lock {|generator| yield generator}
|
34
|
+
end
|
35
|
+
|
36
|
+
def prefixes
|
37
|
+
@prefixed_generators.keySet
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def remove_stale_files
|
42
|
+
prefixes.each do |prefix|
|
43
|
+
get_file_generator(prefix) do |file_generator|
|
44
|
+
file = file_generator.current_file
|
45
|
+
if file.size == 0 && Time.now - file.ctime > STALE_FILES_CHECK_INTERVAL_IN_SECONDS
|
46
|
+
@logger.info("Logstash OSS Output Plugin starts to remove stale file",
|
47
|
+
:key => file.key,
|
48
|
+
:path => file.path,
|
49
|
+
:size => file.size,
|
50
|
+
:thread => Thread.current.to_s)
|
51
|
+
@prefixed_generators.remove(prefix)
|
52
|
+
file.delete!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def start_stale_files_check
|
60
|
+
@stale_check = Concurrent::TimerTask.new(:execution_interval => STALE_FILES_CHECK_INTERVAL_IN_SECONDS) do
|
61
|
+
@logger.info("Logstash OSS Output Plugin: start to check stale files")
|
62
|
+
remove_stale_files
|
63
|
+
end
|
64
|
+
|
65
|
+
@stale_check.execute
|
66
|
+
end
|
67
|
+
|
68
|
+
public
|
69
|
+
def close
|
70
|
+
@stale_check.shutdown
|
71
|
+
end
|
72
|
+
|
73
|
+
class FileGeneratorInitializer
|
74
|
+
include java.util.function.Function
|
75
|
+
def initialize(encoding, temporary_directory)
|
76
|
+
@encoding = encoding
|
77
|
+
@temporary_directory = temporary_directory
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply(prefix)
|
81
|
+
FileGenerator.new(prefix, @encoding, @temporary_directory)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'java'
|
3
|
+
java_import 'com.aliyun.oss.model.ObjectMetadata'
|
4
|
+
java_import 'java.io.FileInputStream'
|
5
|
+
|
6
|
+
module LogStash
|
7
|
+
module Outputs
|
8
|
+
class OSS
|
9
|
+
class FileUploader
|
10
|
+
TIME_BEFORE_RETRY_SECONDS = 3
|
11
|
+
|
12
|
+
attr_reader :oss, :bucket, :additional_oss_settings, :logger
|
13
|
+
|
14
|
+
def initialize(oss, bucket, additional_oss_settings, logger, thread_pool)
|
15
|
+
@oss = oss
|
16
|
+
@bucket = bucket
|
17
|
+
@additional_oss_settings = additional_oss_settings
|
18
|
+
@logger = logger
|
19
|
+
@thread_pool = thread_pool
|
20
|
+
end
|
21
|
+
|
22
|
+
def upload_async(file, options = {})
|
23
|
+
@thread_pool.post do
|
24
|
+
LogStash::Util.set_thread_name("Logstash OSS Output Plugin: output uploader, file: #{file.path}")
|
25
|
+
upload(file, options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def upload(file, options = {})
|
30
|
+
meta = ObjectMetadata.new
|
31
|
+
meta.setContentLength(file.size)
|
32
|
+
unless @additional_oss_settings.nil?
|
33
|
+
if @additional_oss_settings.include?(LogStash::Outputs::OSS::SERVER_SIDE_ENCRYPTION_ALGORITHM_KEY)
|
34
|
+
unless @additional_oss_settings[LogStash::Outputs::OSS::SERVER_SIDE_ENCRYPTION_ALGORITHM_KEY].empty?
|
35
|
+
meta.setServerSideEncryption(@additional_oss_settings[LogStash::Outputs::OSS::SERVER_SIDE_ENCRYPTION_ALGORITHM_KEY])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
stream = nil
|
41
|
+
begin
|
42
|
+
stream = FileInputStream.new(file.path)
|
43
|
+
oss.putObject(@bucket, file.key, stream, meta)
|
44
|
+
rescue Errno::ENOENT => e
|
45
|
+
logger.error("Logstash OSS Output Plugin: file to be uploaded doesn't exist!", :exception => e.class, :message => e.message, :path => file.path, :backtrace => e.backtrace)
|
46
|
+
rescue => e
|
47
|
+
@logger.error("Logstash OSS Output Plugin: uploading failed, retrying.", :exception => e.class, :message => e.message, :path => file.path, :backtrace => e.backtrace)
|
48
|
+
sleep TIME_BEFORE_RETRY_SECONDS
|
49
|
+
retry
|
50
|
+
ensure
|
51
|
+
unless stream.nil?
|
52
|
+
stream.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
options[:on_complete].call(file) unless options[:on_complete].nil?
|
57
|
+
rescue => e
|
58
|
+
logger.error("Logstash OSS Output Plugin: an error occurred in the `on_complete` uploader", :exception => e.class, :message => e.message, :path => file.path, :backtrace => e.backtrace)
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
|
62
|
+
def close
|
63
|
+
@thread_pool.shutdown
|
64
|
+
@thread_pool.wait_for_termination(nil)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module LogStash
|
4
|
+
module Outputs
|
5
|
+
class OSS
|
6
|
+
class GzipFile
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@gzip_writer, :write, :close
|
10
|
+
attr_reader :file, :gzip_writer
|
11
|
+
|
12
|
+
def initialize(file)
|
13
|
+
@file = file
|
14
|
+
@gzip_writer = Zlib::GzipWriter.new(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def path
|
18
|
+
@gzip_writer.to_io.path
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
if @gzip_writer.pos == 0
|
23
|
+
0
|
24
|
+
else
|
25
|
+
@gzip_writer.flush
|
26
|
+
@gzip_writer.to_io.size
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def fsync
|
31
|
+
@gzip_writer.to_io.fsync
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/outputs/oss/rotations/size_based_rotation"
|
3
|
+
require "logstash/outputs/oss/rotations/time_based_rotation"
|
4
|
+
|
5
|
+
module LogStash
|
6
|
+
module Outputs
|
7
|
+
class OSS
|
8
|
+
class HybridRotation
|
9
|
+
def initialize(size_rotate, time_rotate)
|
10
|
+
@size_strategy = SizeBasedRotation.new(size_rotate)
|
11
|
+
@time_strategy = TimeBasedRotation.new(time_rotate)
|
12
|
+
end
|
13
|
+
|
14
|
+
def rotate?(file)
|
15
|
+
@size_strategy.rotate?(file) || @time_strategy.rotate?(file)
|
16
|
+
end
|
17
|
+
|
18
|
+
def needs_periodic_check?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module LogStash
|
3
|
+
module Outputs
|
4
|
+
class OSS
|
5
|
+
class SizeBasedRotation
|
6
|
+
attr_reader :size_rotate
|
7
|
+
|
8
|
+
def initialize(size_rotate)
|
9
|
+
if size_rotate <= 0
|
10
|
+
raise LogStash::ConfigurationError, "Logstash OSS output has wrong configuration: size_rotate must be positive if strategy is `size`"
|
11
|
+
end
|
12
|
+
@size_rotate = size_rotate
|
13
|
+
end
|
14
|
+
|
15
|
+
def rotate?(file)
|
16
|
+
file.size >= @size_rotate
|
17
|
+
end
|
18
|
+
|
19
|
+
def needs_periodic_check?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module LogStash
|
3
|
+
module Outputs
|
4
|
+
class OSS
|
5
|
+
class TimeBasedRotation
|
6
|
+
attr_reader :time_rotate
|
7
|
+
|
8
|
+
def initialize(time_rotate)
|
9
|
+
if time_rotate <= 0
|
10
|
+
raise LogStash::ConfigurationError, "Logstash OSS output has wrong configuration: time_rotate must be positive if strategy is `time`"
|
11
|
+
end
|
12
|
+
@time_rotate = time_rotate * 60
|
13
|
+
end
|
14
|
+
|
15
|
+
def rotate?(file)
|
16
|
+
file.size > 0 && (Time.now - file.ctime) >= @time_rotate
|
17
|
+
end
|
18
|
+
|
19
|
+
def needs_periodic_check?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module LogStash
|
4
|
+
module Outputs
|
5
|
+
class OSS
|
6
|
+
class TemporaryFile
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@file, :path, :write, :close, :fsync
|
10
|
+
|
11
|
+
attr_reader :file
|
12
|
+
|
13
|
+
def initialize(file, object_key, temporary_path)
|
14
|
+
@file = file
|
15
|
+
@object_key = object_key
|
16
|
+
@temporary_path = temporary_path
|
17
|
+
@creation_time = Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
def ctime
|
21
|
+
@creation_time
|
22
|
+
end
|
23
|
+
|
24
|
+
def temporary_path
|
25
|
+
@temporary_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def size
|
29
|
+
begin
|
30
|
+
@file.size
|
31
|
+
rescue IOError
|
32
|
+
::File.size(path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def key
|
37
|
+
@object_key.gsub(/^\//, "")
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete!
|
41
|
+
@file.close rescue IOError
|
42
|
+
FileUtils.rm_r(@temporary_path, :secure => true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def empty?
|
46
|
+
size == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.create_existing_file(path, temporary_directory)
|
50
|
+
# path is #{temporary_directory}/${uuid}/${prefix}/${key}
|
51
|
+
elements = Pathname.new(path).relative_path_from(Pathname.new(temporary_directory)).to_s.split(::File::SEPARATOR)
|
52
|
+
uuid = elements[0]
|
53
|
+
object_key = ::File.join(elements.slice(1, elements.size - 1))
|
54
|
+
TemporaryFile.new(::File.open(path, "r"), object_key, ::File.join(temporary_directory, uuid))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'logstash-output-oss'
|
3
|
+
s.version = '0.1.1'
|
4
|
+
s.licenses = ['Apache-2.0']
|
5
|
+
s.summary = 'Sends Logstash events to the Aliyun Object Storage Service'
|
6
|
+
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gem-name. This gem is not a stand-alone program"
|
7
|
+
s.authors = ['jinhu.wu']
|
8
|
+
s.email = 'jinhu.wu.nju@gmail.com'
|
9
|
+
s.require_paths = ['lib']
|
10
|
+
s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
|
11
|
+
|
12
|
+
# Files
|
13
|
+
s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
|
14
|
+
# Tests
|
15
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
16
|
+
|
17
|
+
# Special flag to let us know this is actually a logstash plugin
|
18
|
+
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
|
19
|
+
|
20
|
+
# Gem dependencies
|
21
|
+
s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
|
22
|
+
s.add_runtime_dependency "logstash-codec-plain", "~> 3.0"
|
23
|
+
s.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
24
|
+
s.add_runtime_dependency "uuid", '~> 2.3', '>= 2.3.9'
|
25
|
+
s.add_development_dependency "logstash-devutils", "~> 1.3"
|
26
|
+
s.add_development_dependency "logstash-codec-line", "~> 3.0"
|
27
|
+
s.platform = 'java'
|
28
|
+
s.add_runtime_dependency 'jar-dependencies', '~> 0.3'
|
29
|
+
s.requirements << 'jar com.aliyun.oss:aliyun-sdk-oss, 3.4.0'
|
30
|
+
end
|