logstash-output-oss 0.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/CONTRIBUTORS +10 -0
  4. data/DEVELOPER.md +10 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +11 -0
  7. data/README.md +149 -0
  8. data/lib/com/aliyun/aliyun-java-sdk-core/3.4.0/aliyun-java-sdk-core-3.4.0.jar +0 -0
  9. data/lib/com/aliyun/aliyun-java-sdk-ecs/4.2.0/aliyun-java-sdk-ecs-4.2.0.jar +0 -0
  10. data/lib/com/aliyun/aliyun-java-sdk-ram/3.0.0/aliyun-java-sdk-ram-3.0.0.jar +0 -0
  11. data/lib/com/aliyun/aliyun-java-sdk-sts/3.0.0/aliyun-java-sdk-sts-3.0.0.jar +0 -0
  12. data/lib/com/aliyun/oss/aliyun-sdk-oss/3.4.0/aliyun-sdk-oss-3.4.0.jar +0 -0
  13. data/lib/com/sun/jersey/jersey-core/1.9/jersey-core-1.9.jar +0 -0
  14. data/lib/com/sun/jersey/jersey-json/1.9/jersey-json-1.9.jar +0 -0
  15. data/lib/com/sun/xml/bind/jaxb-impl/2.2.3-1/jaxb-impl-2.2.3-1.jar +0 -0
  16. data/lib/commons-codec/commons-codec/1.9/commons-codec-1.9.jar +0 -0
  17. data/lib/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
  18. data/lib/javax/activation/activation/1.1/activation-1.1.jar +0 -0
  19. data/lib/javax/xml/bind/jaxb-api/2.2.2/jaxb-api-2.2.2.jar +0 -0
  20. data/lib/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar +0 -0
  21. data/lib/logstash-output-oss_jars.rb +52 -0
  22. data/lib/logstash/outputs/oss.rb +288 -0
  23. data/lib/logstash/outputs/oss/file_generator.rb +69 -0
  24. data/lib/logstash/outputs/oss/file_manager.rb +87 -0
  25. data/lib/logstash/outputs/oss/file_uploader.rb +69 -0
  26. data/lib/logstash/outputs/oss/gzip_file.rb +36 -0
  27. data/lib/logstash/outputs/oss/rotations/hybrid_rotation.rb +24 -0
  28. data/lib/logstash/outputs/oss/rotations/size_based_rotation.rb +25 -0
  29. data/lib/logstash/outputs/oss/rotations/time_based_rotation.rb +25 -0
  30. data/lib/logstash/outputs/oss/temporary_file.rb +59 -0
  31. data/lib/logstash/outputs/oss/version.rb +14 -0
  32. data/lib/org/apache/httpcomponents/httpclient/4.4.1/httpclient-4.4.1.jar +0 -0
  33. data/lib/org/apache/httpcomponents/httpcore/4.4.1/httpcore-4.4.1.jar +0 -0
  34. data/lib/org/codehaus/jackson/jackson-core-asl/1.8.3/jackson-core-asl-1.8.3.jar +0 -0
  35. data/lib/org/codehaus/jackson/jackson-jaxrs/1.8.3/jackson-jaxrs-1.8.3.jar +0 -0
  36. data/lib/org/codehaus/jackson/jackson-mapper-asl/1.8.3/jackson-mapper-asl-1.8.3.jar +0 -0
  37. data/lib/org/codehaus/jackson/jackson-xc/1.8.3/jackson-xc-1.8.3.jar +0 -0
  38. data/lib/org/codehaus/jettison/jettison/1.1/jettison-1.1.jar +0 -0
  39. data/lib/org/jdom/jdom/1.1/jdom-1.1.jar +0 -0
  40. data/lib/stax/stax-api/1.0.1/stax-api-1.0.1.jar +0 -0
  41. data/logstash-output-oss.gemspec +30 -0
  42. data/spec/integration/common.rb +42 -0
  43. data/spec/integration/encoding_spec.rb +92 -0
  44. data/spec/integration/oss_spec.rb +47 -0
  45. data/spec/integration/recover_spec.rb +43 -0
  46. data/spec/outputs/oss/generator_spec.rb +67 -0
  47. data/spec/outputs/oss/rotation_spec.rb +69 -0
  48. data/spec/outputs/oss_spec.rb +58 -0
  49. 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
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ module LogStash
4
+ module Outputs
5
+ class OSS
6
+ class Version
7
+ VERSION = "0.1.1"
8
+ def self.version
9
+ VERSION
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
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