logstash-output-oss 0.1.1-java
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 +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
|