logstash-output-azure 0.3.0 → 1.0.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.
@@ -1,16 +1,16 @@
1
- # encoding: utf-8
2
1
  module LogStash
3
2
  module Outputs
4
3
  class LogstashAzureBlobOutput
5
4
  # a sub class of +LogstashAzureBlobOutput+
6
5
  # valdiates the path for the temporary directory
7
6
  class PathValidator
8
- INVALID_CHARACTERS = "\^`><"
9
-
7
+ INVALID_CHARACTERS = "\^`><".freeze
8
+ # boolean method to check if a name is valid
10
9
  def self.valid?(name)
11
10
  name.match(matches_re).nil?
12
11
  end
13
12
 
13
+ # define the invalid characters that shouldn't be in the path name
14
14
  def self.matches_re
15
15
  /[#{Regexp.escape(INVALID_CHARACTERS)}]/
16
16
  end
@@ -1,22 +1,24 @@
1
- # encoding: utf-8
2
- require "logstash/outputs/blob/size_rotation_policy"
3
- require "logstash/outputs/blob/time_rotation_policy"
1
+ require 'logstash/outputs/blob/size_rotation_policy'
2
+ require 'logstash/outputs/blob/time_rotation_policy'
4
3
 
5
4
  module LogStash
6
5
  module Outputs
7
6
  class LogstashAzureBlobOutput
8
7
  # a sub class of +LogstashAzureBlobOutput+
9
- # sets the rotation policy
8
+ # sets the rotation policy
10
9
  class SizeAndTimeRotationPolicy
10
+ # initialize the class
11
11
  def initialize(file_size, time_file)
12
12
  @size_strategy = SizeRotationPolicy.new(file_size)
13
13
  @time_strategy = TimeRotationPolicy.new(time_file)
14
14
  end
15
15
 
16
+ # check if it is time to rotate
16
17
  def rotate?(file)
17
18
  @size_strategy.rotate?(file) || @time_strategy.rotate?(file)
18
19
  end
19
20
 
21
+ # boolean method
20
22
  def needs_periodic?
21
23
  true
22
24
  end
@@ -1,24 +1,25 @@
1
- # encoding: utf-8
2
1
  module LogStash
3
2
  module Outputs
4
3
  class LogstashAzureBlobOutput
5
4
  # a sub class of +LogstashAzureBlobOutput+
6
- # sets the rotation policy by size
5
+ # sets the rotation policy by size
7
6
  class SizeRotationPolicy
8
7
  attr_reader :size_file
9
-
8
+ # initialize the class
10
9
  def initialize(size_file)
11
10
  if size_file <= 0
12
- raise LogStash::ConfigurationError, "`size_file` need to be greather than 0"
11
+ raise LogStash::ConfigurationError.new('`size_file` need to be greather than 0')
13
12
  end
14
13
 
15
14
  @size_file = size_file
16
15
  end
17
16
 
17
+ # boolean method to check if it is time to rotate
18
18
  def rotate?(file)
19
19
  file.size >= size_file
20
20
  end
21
21
 
22
+ # boolean method
22
23
  def needs_periodic?
23
24
  false
24
25
  end
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
- require "thread"
3
- require "forwardable"
4
- require "fileutils"
1
+ require 'thread'
2
+ require 'forwardable'
3
+ require 'fileutils'
5
4
 
6
5
  module LogStash
7
6
  module Outputs
@@ -16,6 +15,7 @@ module LogStash
16
15
 
17
16
  attr_reader :fd
18
17
 
18
+ # initialize the class
19
19
  def initialize(key, fd, temp_path)
20
20
  @fd = fd
21
21
  @key = key
@@ -23,27 +23,28 @@ module LogStash
23
23
  @created_at = Time.now
24
24
  end
25
25
 
26
+ # gets the created at time
26
27
  def ctime
27
28
  @created_at
28
29
  end
29
30
 
30
- def temp_path
31
- @temp_path
32
- end
31
+ # gets path to temporary directory
32
+ attr_reader :temp_path
33
33
 
34
+ # gets the size of file
34
35
  def size
35
36
  # Use the fd size to get the accurate result,
36
37
  # so we dont have to deal with fsync
37
38
  # if the file is close we will use the File::size
38
- begin
39
- @fd.size
40
- rescue IOError
41
- ::File.size(path)
42
- end
39
+
40
+ @fd.size
41
+ rescue IOError
42
+ ::File.size(path)
43
43
  end
44
44
 
45
+ # gets the key
45
46
  def key
46
- @key.gsub(/^\//, "")
47
+ @key.gsub(/^\//, '')
47
48
  end
48
49
 
49
50
  # Each temporary file is made inside a directory named with an UUID,
@@ -51,20 +52,28 @@ module LogStash
51
52
  # we delete the root of the UUID, using a UUID also remove the risk of deleting unwanted file, it acts as
52
53
  # a sandbox.
53
54
  def delete!
54
- @fd.close rescue IOError # force close anyway
55
- FileUtils.rm_r(@temp_path, :secure => true)
55
+ begin
56
+ @fd.close
57
+ rescue
58
+ IOError
59
+ end
60
+ FileUtils.rm_r(@temp_path, secure: true)
56
61
  end
57
62
 
63
+ # boolean method to determine if the file is empty
58
64
  def empty?
59
- size == 0
65
+ size.zero?
60
66
  end
61
67
 
68
+ # creates the temporary file in an existing temporary directory from existing file
69
+ # @param file_path [String] path to the file
70
+ # @param temporary_folder [String] path to the temporary folder
62
71
  def self.create_from_existing_file(file_path, temporary_folder)
63
72
  key_parts = Pathname.new(file_path).relative_path_from(temporary_folder).to_s.split(::File::SEPARATOR)
64
73
 
65
- TemporaryFile.new(key_parts.slice(1, key_parts.size).join("/"),
66
- ::File.open(file_path, "r"),
67
- ::File.join(temporary_folder, key_parts.slice(0, 1)))
74
+ TemporaryFile.new(key_parts.slice(1, key_parts.size).join('/'),
75
+ ::File.open(file_path, 'r'),
76
+ ::File.join(temporary_folder, key_parts.slice(0, 1)))
68
77
  end
69
78
  end
70
79
  end
@@ -1,9 +1,8 @@
1
- # encoding: utf-8
2
- require "socket"
3
- require "securerandom"
4
- require "fileutils"
5
- require "zlib"
6
- require "forwardable"
1
+ require 'socket'
2
+ require 'securerandom'
3
+ require 'fileutils'
4
+ require 'zlib'
5
+ require 'forwardable'
7
6
 
8
7
  module LogStash
9
8
  module Outputs
@@ -11,14 +10,15 @@ module LogStash
11
10
  # a sub class of +LogstashAzureBlobOutput+
12
11
  # creates the temporary files to write and later upload
13
12
  class TemporaryFileFactory
14
- FILE_MODE = "a"
15
- GZIP_ENCODING = "gzip"
16
- GZIP_EXTENSION = "txt.gz"
17
- TXT_EXTENSION = "txt"
18
- STRFTIME = "%Y-%m-%dT%H.%M"
13
+ FILE_MODE = 'a'.freeze
14
+ GZIP_ENCODING = 'gzip'.freeze
15
+ GZIP_EXTENSION = 'txt.gz'.freeze
16
+ TXT_EXTENSION = 'txt'.freeze
17
+ STRFTIME = '%Y-%m-%dT%H.%M'.freeze
19
18
 
20
19
  attr_accessor :counter, :tags, :prefix, :encoding, :temporary_directory, :current
21
20
 
21
+ # initialize the class
22
22
  def initialize(prefix, tags, encoding, temporary_directory)
23
23
  @counter = 0
24
24
  @prefix = prefix
@@ -31,41 +31,49 @@ module LogStash
31
31
  rotate!
32
32
  end
33
33
 
34
+ # do the rotation
34
35
  def rotate!
35
- @lock.synchronize {
36
+ @lock.synchronize do
36
37
  @current = new_file
37
38
  increment_counter
38
39
  @current
39
- }
40
+ end
40
41
  end
41
42
 
42
43
  private
44
+
45
+ # if it is not gzip ecoding, then it is txt extension
43
46
  def extension
44
47
  gzip? ? GZIP_EXTENSION : TXT_EXTENSION
45
48
  end
46
49
 
50
+ # boolean method to check if its gzip encoding
47
51
  def gzip?
48
52
  encoding == GZIP_ENCODING
49
53
  end
50
54
 
55
+ # increment the counter in 1 unit
51
56
  def increment_counter
52
57
  @counter += 1
53
58
  end
54
59
 
60
+ # gets the current time
55
61
  def current_time
56
62
  Time.now.strftime(STRFTIME)
57
63
  end
58
64
 
65
+ # method that generate the name of the file to be saved in blob storage
59
66
  def generate_name
60
67
  filename = "#{current_time}.#{SecureRandom.uuid}"
61
68
 
62
- if tags.size > 0
69
+ if !tags.empty?
63
70
  "#{filename}.tag_#{tags.join('.')}.part#{counter}.#{extension}"
64
71
  else
65
72
  "#{filename}.part#{counter}.#{extension}"
66
73
  end
67
74
  end
68
75
 
76
+ # create the file to be saved in blob storage
69
77
  def new_file
70
78
  uuid = SecureRandom.uuid
71
79
  name = generate_name
@@ -85,25 +93,28 @@ module LogStash
85
93
  TemporaryFile.new(key, io, path)
86
94
  end
87
95
 
88
- # clas for the necoding
96
+ # clas for the encoding
89
97
  class IOWrappedGzip
90
98
  extend Forwardable
91
99
 
92
100
  def_delegators :@gzip_writer, :write, :close
93
101
  attr_reader :file_io, :gzip_writer
94
102
 
103
+ # initialize the class for encoding
95
104
  def initialize(file_io)
96
105
  @file_io = file_io
97
106
  @gzip_writer = Zlib::GzipWriter.open(file_io)
98
107
  end
99
108
 
109
+ # gets the path
100
110
  def path
101
111
  @gzip_writer.to_io.path
102
112
  end
103
113
 
114
+ # gets the file size
104
115
  def size
105
116
  # to get the current file size
106
- if @gzip_writer.pos == 0
117
+ if @gzip_writer.pos.zero?
107
118
  # Ensure a zero file size is returned when nothing has
108
119
  # yet been written to the gzip file.
109
120
  0
@@ -113,6 +124,7 @@ module LogStash
113
124
  end
114
125
  end
115
126
 
127
+ # gets the fsync
116
128
  def fsync
117
129
  @gzip_writer.to_io.fsync
118
130
  end
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module LogStash
3
2
  module Outputs
4
3
  class LogstashAzureBlobOutput
@@ -6,19 +5,21 @@ module LogStash
6
5
  # sets the policy for time rotation
7
6
  class TimeRotationPolicy
8
7
  attr_reader :time_file
9
-
8
+ # initialize the class and validate the time file
10
9
  def initialize(time_file)
11
10
  if time_file <= 0
12
- raise LogStash::ConfigurationError, "`time_file` need to be greather than 0"
11
+ raise LogStash::ConfigurationError.new('`time_file` need to be greather than 0')
13
12
  end
14
13
 
15
14
  @time_file = time_file * 60
16
15
  end
17
16
 
17
+ # rotates based on time policy
18
18
  def rotate?(file)
19
- file.size > 0 && (Time.now - file.ctime) >= time_file
19
+ !file.empty? && (Time.now - file.ctime) >= time_file
20
20
  end
21
21
 
22
+ # boolean method
22
23
  def needs_periodic?
23
24
  true
24
25
  end
@@ -1,6 +1,5 @@
1
- # encoding: utf-8
2
- require "logstash/util"
3
- require "azure"
1
+ require 'logstash/util'
2
+ require 'azure'
4
3
 
5
4
  module LogStash
6
5
  module Outputs
@@ -9,22 +8,24 @@ module LogStash
9
8
  # this class uploads the files to Azure cloud
10
9
  class Uploader
11
10
  TIME_BEFORE_RETRYING_SECONDS = 1
12
- DEFAULT_THREADPOOL = Concurrent::ThreadPoolExecutor.new({
13
- :min_threads => 1,
14
- :max_threads => 8,
15
- :max_queue => 1,
16
- :fallback_policy => :caller_runs
17
- })
18
-
19
- attr_accessor :upload_options, :logger, :container_name, :blob_account
20
-
21
- def initialize(blob_account, container_name , logger, threadpool = DEFAULT_THREADPOOL)
11
+ DEFAULT_THREADPOOL = Concurrent::ThreadPoolExecutor.new(min_threads: 1,
12
+ max_threads: 8,
13
+ max_queue: 1,
14
+ fallback_policy: :caller_runs)
15
+
16
+ attr_accessor :upload_options, :logger, :container_name, :blob_account
17
+
18
+ # Initializes the class
19
+ # @param blob_account [Object] endpoint to azure gem
20
+ # @param container_name [String] name of the container in azure blob, at this point, if it doesn't exist, it was already created
21
+ def initialize(blob_account, container_name, logger, threadpool = DEFAULT_THREADPOOL)
22
22
  @blob_account = blob_account
23
23
  @workers_pool = threadpool
24
24
  @logger = logger
25
- @container_name = container_name
26
- end
25
+ @container_name = container_name
26
+ end
27
27
 
28
+ # Create threads to upload the file to the container
28
29
  def upload_async(file, options = {})
29
30
  @workers_pool.post do
30
31
  LogStash::Util.set_thread_name("LogstashAzureBlobOutput output uploader, file: #{file.path}")
@@ -32,32 +33,38 @@ module LogStash
32
33
  end
33
34
  end
34
35
 
36
+ # Uploads the file to the container
35
37
  def upload(file, options = {})
36
38
  upload_options = options.fetch(:upload_options, {})
37
39
 
38
40
  begin
39
- content = Object::File.open(file.path, "rb").read
40
- filename = Object::File.basename file.path
41
- puts filename
42
- blob = blob_account.create_block_blob(container_name, filename, content)
43
- puts blob.name
41
+ content = Object::File.open(file.path, 'rb').read
42
+ filename = Object::File.basename file.path
43
+ puts filename
44
+ blob = blob_account.create_block_blob(container_name, filename, content)
45
+ puts blob.name
44
46
  rescue => e
45
47
  # When we get here it usually mean that LogstashAzureBlobOutput tried to do some retry by himself (default is 3)
46
48
  # When the retry limit is reached or another error happen we will wait and retry.
47
49
  #
48
50
  # Thread might be stuck here, but I think its better than losing anything
49
51
  # its either a transient errors or something bad really happened.
50
- logger.error("Uploading failed, retrying", :exception => e.class, :message => e.message, :path => file.path, :backtrace => e.backtrace)
52
+ logger.error('Uploading failed, retrying', exception: e.class, message: e.message, path: file.path, backtrace: e.backtrace)
51
53
  retry
52
54
  end
53
55
 
54
56
  options[:on_complete].call(file) unless options[:on_complete].nil?
55
57
  blob
56
58
  rescue => e
57
- logger.error("An error occured in the `on_complete` uploader", :exception => e.class, :message => e.message, :path => file.path, :backtrace => e.backtrace)
59
+ logger.error('An error occured in the `on_complete` uploader',
60
+ exception: e.class,
61
+ message: e.message,
62
+ path: file.path,
63
+ backtrace: e.backtrace)
58
64
  raise e # reraise it since we don't deal with it now
59
65
  end
60
66
 
67
+ # stop threads
61
68
  def stop
62
69
  @workers_pool.shutdown
63
70
  @workers_pool.wait_for_termination(nil) # block until its done
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module LogStash
3
2
  module Outputs
4
3
  class LogstashAzureBlobOutput
@@ -6,13 +5,13 @@ module LogStash
6
5
  # validates that the specified tmeporary directory can be accesed with
7
6
  # write permission
8
7
  class WritableDirectoryValidator
8
+ # Checks if a path is valid
9
+ # @param path [String] String that represents the path
9
10
  def self.valid?(path)
10
- begin
11
- FileUtils.mkdir_p(path) unless Dir.exist?(path)
12
- ::File.writable?(path)
13
- rescue
14
- false
15
- end
11
+ FileUtils.mkdir_p(path) unless Dir.exist?(path)
12
+ ::File.writable?(path)
13
+ rescue
14
+ false
16
15
  end
17
16
  end
18
17
  end
@@ -1,25 +1,25 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-azure'
3
- s.version = '0.3.0'
3
+ s.version = '1.0.0'
4
4
  s.licenses = ['Apache-2.0']
5
5
  s.summary = 'Plugin for logstash to send output to Microsoft Azure Blob'
6
- #s.description = 'TODO: Write a longer description or delete this line.'
7
- #s.homepage = 'TODO: Put your plugin''s website or public repo URL here.'
6
+ # s.description = 'TODO: Write a longer description or delete this line.'
7
+ # s.homepage = 'TODO: Put your plugin''s website or public repo URL here.'
8
8
  s.authors = ['Tuffk']
9
9
  s.email = 'tuffkmulhall@gmail.com'
10
10
  s.require_paths = ['lib']
11
11
 
12
12
  # Files
13
- s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
14
- # Tests
13
+ s.files = Dir['lib/**/*', 'spec/**/*', 'vendor/**/*', '*.gemspec', '*.md', 'CONTRIBUTORS', 'Gemfile', 'LICENSE', 'NOTICE.TXT']
14
+ # Tests
15
15
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
16
 
17
17
  # Special flag to let us know this is actually a logstash plugin
18
- s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
18
+ s.metadata = { 'logstash_plugin' => 'true', 'logstash_group' => 'output' }
19
19
 
20
20
  # Gem dependencies
21
- s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
22
- s.add_runtime_dependency "logstash-codec-plain"
23
- s.add_runtime_dependency "azure", "~> 0.7"
24
- s.add_development_dependency "logstash-devutils"
21
+ s.add_runtime_dependency 'azure', '~> 0.7'
22
+ s.add_runtime_dependency 'logstash-codec-plain'
23
+ s.add_runtime_dependency 'logstash-core-plugin-api', '~> 2.0'
24
+ s.add_development_dependency 'logstash-devutils'
25
25
  end