logstash-output-azure 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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