logstash-output-azure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 643c02ca977f51e3c3d19164538a677b91d958b1
4
+ data.tar.gz: fd88a6019475e526e997079941a71c7a48ec92a5
5
+ SHA512:
6
+ metadata.gz: a175aafce0fe31532934d4c390a9fbfd58aafaaa635b9c59f5a4cbf2c9dabac1db6ca2145627b9c81537d6953914b6861c7c5974f5621aa9df5e0361c7260703
7
+ data.tar.gz: 96c2795dcb66d16f11676461d8a0f63604cf75b326c3c702f4e5575b49c967ed61d0a7462a68913bc48e476bb3ac0db30b40f9e53be6303cdc0e8e0a998de5ce
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0
2
+ - Plugin created with the logstash plugin generator
data/CONTRIBUTORS ADDED
@@ -0,0 +1,10 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * Tuffk - tuffkmulhall@gmail.com
6
+
7
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
8
+ Logstash, and you aren't on the list above and want to be, please let us know
9
+ and we'll make sure you're here. Contributions from folks like you are what make
10
+ open source awesome.
data/DEVELOPER.md ADDED
@@ -0,0 +1,2 @@
1
+ # logstash-output-Logstash_Azure_Blob_Output
2
+ Example output plugin. This should help bootstrap your effort to write your own output plugin!
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'pry'
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Logstash Plugin
2
+ [![Build Status](https://travis-ci.org/tuffk/Logstash-output-to-Azure-Blob.svg?branch=master)](https://travis-ci.org/tuffk/Logstash-output-to-Azure-Blob)
3
+
4
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
5
+
6
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
7
+
8
+ ## Documentation
9
+
10
+ Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
11
+
12
+ - For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
13
+ - For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
14
+
15
+ ## Need Help?
16
+
17
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
18
+
19
+ ## Developing
20
+
21
+ ### 1. Plugin Developement and Testing
22
+
23
+ #### Code
24
+ - To get started, you'll need JRuby with the Bundler gem installed.
25
+
26
+ - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
27
+
28
+ - Install dependencies
29
+ ```sh
30
+ bundle install
31
+ ```
32
+
33
+ #### Test
34
+
35
+ - Update your dependencies
36
+
37
+ ```sh
38
+ bundle install
39
+ ```
40
+
41
+ - Run tests
42
+
43
+ ```sh
44
+ bundle exec rspec
45
+ ```
46
+
47
+ ### 2. Running your unpublished Plugin in Logstash
48
+
49
+ #### 2.1 Run in a local Logstash clone
50
+
51
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
52
+ ```ruby
53
+ gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
54
+ ```
55
+ - Install plugin
56
+ ```sh
57
+ bin/logstash-plugin install --no-verify
58
+ ```
59
+ - Run Logstash with your plugin
60
+ ```sh
61
+ bin/logstash -e 'filter {awesome {}}'
62
+ ```
63
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
64
+
65
+ #### 2.2 Run in an installed Logstash
66
+
67
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
68
+
69
+ - Build your plugin gem
70
+ ```sh
71
+ gem build logstash-filter-awesome.gemspec
72
+ ```
73
+ - Install the plugin from the Logstash home
74
+ ```sh
75
+ bin/logstash-plugin install /your/local/plugin/logstash-filter-awesome.gem
76
+ ```
77
+ - Start Logstash and proceed to test the plugin
78
+
79
+ ## Contributing
80
+
81
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
82
+
83
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
84
+
85
+ It is more important to the community that you are able to contribute.
86
+
87
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,244 @@
1
+ # encoding: utf-8
2
+
3
+ require 'logstash/outputs/base'
4
+ require 'logstash/namespace'
5
+ require 'azure'
6
+ require 'tmpdir'
7
+ require 'pry'
8
+
9
+ # An Logstash_Azure_Blob_Output output that does nothing.
10
+ class LogStash::Outputs::LogstashAzureBlobOutput < LogStash::Outputs::Base
11
+ require 'logstash/outputs/blob/writable_directory_validator'
12
+ require 'logstash/outputs/blob/path_validator'
13
+ require 'logstash/outputs/blob/size_rotation_policy'
14
+ require 'logstash/outputs/blob/time_rotation_policy'
15
+ require 'logstash/outputs/blob/size_and_time_rotation_policy'
16
+ require 'logstash/outputs/blob/temporary_file'
17
+ require 'logstash/outputs/blob/temporary_file_factory'
18
+ require 'logstash/outputs/blob/uploader'
19
+ require 'logstash/outputs/blob/file_repository'
20
+
21
+ PREFIX_KEY_NORMALIZE_CHARACTER = "_"
22
+ PERIODIC_CHECK_INTERVAL_IN_SECONDS = 15
23
+ CRASH_RECOVERY_THREADPOOL = Concurrent::ThreadPoolExecutor.new({
24
+ :min_threads => 1,
25
+ :max_threads => 2,
26
+ :fallback_policy => :caller_runs
27
+ })
28
+
29
+ config_name 'azure'
30
+
31
+ # azure contianer
32
+ config :storage_account_name, valdiate: :string, required: false
33
+
34
+ # azure key
35
+ config :storage_access_key, valdiate: :string, required: false
36
+
37
+ # mamadas
38
+ config :size_file, validate: :number, default: 1024 * 1024 * 5
39
+ config :time_file, validate: :number, default: 15
40
+ config :restore, validate: :boolean, default: true
41
+ config :temporary_directory, validate: :string, default: File.join(Dir.tmpdir, 'logstash')
42
+ config :prefix, validate: :string, default: ''
43
+ config :upload_queue_size, validate: :number, default: 2 * (Concurrent.processor_count * 0.25).ceil
44
+ config :upload_workers_count, validate: :number, default: (Concurrent.processor_count * 0.5).ceil
45
+ config :rotation_strategy, validate: %w[size_and_time size time], default: 'size_and_time'
46
+ config :tags, :validate => :array, :default => []
47
+ config :encoding, :validate => ["none", "gzip"], :default => "none"
48
+
49
+ # elimindadas
50
+ # canned acl
51
+ # server side encryption
52
+ # server encryptopn algorithm
53
+ # ssekms
54
+ # storage class
55
+ # signature version
56
+ # tags
57
+ # encoding
58
+ # valdiate credentails on root
59
+
60
+ public
61
+
62
+ def register
63
+ unless @prefix.empty?
64
+ unless PathValidator.valid?(prefix)
65
+ raise LogStash::ConfigurationError, "Prefix must not contains: #{PathValidator::INVALID_CHARACTERS}"
66
+ end
67
+ end
68
+
69
+ unless WritableDirectoryValidator.valid?(@temporary_directory)
70
+ raise LogStash::ConfigurationError, "Logstash must have the permissions to write to the temporary directory: #{@temporary_directory}"
71
+ end
72
+
73
+ if @time_file.nil? && @size_file.nil? || @size_file == 0 && @time_file == 0
74
+ raise LogStash::ConfigurationError, 'at least one of time_file or size_file set to a value greater than 0'
75
+ end
76
+
77
+ @file_repository = FileRepository.new(@tags, @encoding, @temporary_directory)
78
+
79
+ @rotation = rotation_strategy
80
+
81
+ executor = Concurrent::ThreadPoolExecutor.new(min_threads: 1,
82
+ max_threads: @upload_workers_count,
83
+ max_queue: @upload_queue_size,
84
+ fallback_policy: :caller_runs)
85
+
86
+ @uploader = Uploader.new(blob_contianer_resource, @logger, executor)
87
+
88
+ restore_from_crash if @restore
89
+ start_periodic_check if @rotation.needs_periodic?
90
+ end # def register
91
+
92
+ def multi_receive_encoded(events_and_encoded)
93
+ prefix_written_to = Set.new
94
+
95
+ events_and_encoded.each do |event, encoded|
96
+ prefix_key = normalize_key(event.sprintf(@prefix))
97
+ prefix_written_to << prefix_key
98
+
99
+ begin
100
+ @file_repository.get_file(prefix_key) { |file| file.write(encoded) }
101
+ # The output should stop accepting new events coming in, since it cannot do anything with them anymore.
102
+ # Log the error and rethrow it.
103
+ rescue Errno::ENOSPC => e
104
+ @logger.error('S3: No space left in temporary directory', temporary_directory: @temporary_directory)
105
+ raise e
106
+ end
107
+ end
108
+
109
+ # Groups IO calls to optimize fstat checks
110
+ rotate_if_needed(prefix_written_to)
111
+ end
112
+
113
+ def close
114
+ stop_periodic_check if @rotation.needs_periodic?
115
+
116
+ @logger.debug('Uploading current workspace')
117
+
118
+ # The plugin has stopped receiving new events, but we still have
119
+ # data on disk, lets make sure it get to S3.
120
+ # If Logstash get interrupted, the `restore_from_crash` (when set to true) method will pickup
121
+ # the content in the temporary directly and upload it.
122
+ # This will block the shutdown until all upload are done or the use force quit.
123
+ @file_repository.each_files do |file|
124
+ upload_file(file)
125
+ end
126
+
127
+ @file_repository.shutdown
128
+
129
+ @uploader.stop # wait until all the current upload are complete
130
+ @crash_uploader.stop if @restore # we might have still work to do for recovery so wait until we are done
131
+ end
132
+
133
+ def normalize_key(prefix_key)
134
+ prefix_key.gsub(PathValidator.matches_re, PREFIX_KEY_NORMALIZE_CHARACTER)
135
+ end
136
+
137
+ def upload_options
138
+ {
139
+ }
140
+ end
141
+
142
+ def start_periodic_check
143
+ @logger.debug("Start periodic rotation check")
144
+
145
+ @periodic_check = Concurrent::TimerTask.new(:execution_interval => PERIODIC_CHECK_INTERVAL_IN_SECONDS) do
146
+ @logger.debug("Periodic check for stale files")
147
+
148
+ rotate_if_needed(@file_repository.keys)
149
+ end
150
+
151
+ @periodic_check.execute
152
+ end
153
+
154
+ def stop_periodic_check
155
+ @periodic_check.shutdown
156
+ end
157
+
158
+ def blob_contianer_resource
159
+ azure_blob_service = Azure::Blob::BlobService.new
160
+ list = azure_blob_service.list_containers()
161
+ list.each do |item|
162
+ @container = item if item.name == "jaime-test-1"
163
+ end
164
+ azure_blob_service.create_container("jaime-test-1") unless @container
165
+ end
166
+
167
+ def rotate_if_needed(prefixes)
168
+ prefixes.each do |prefix|
169
+ # Each file access is thread safe,
170
+ # until the rotation is done then only
171
+ # one thread has access to the resource.
172
+ @file_repository.get_factory(prefix) do |factory|
173
+ temp_file = factory.current
174
+
175
+ if @rotation.rotate?(temp_file)
176
+ @logger.debug("Rotate file",
177
+ :strategy => @rotation.class.name,
178
+ :key => temp_file.key,
179
+ :path => temp_file.path)
180
+
181
+ upload_file(temp_file)
182
+ factory.rotate!
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ def upload_file(temp_file)
189
+ @logger.debug("Queue for upload", :path => temp_file.path)
190
+
191
+ # if the queue is full the calling thread will be used to upload
192
+ temp_file.close # make sure the content is on disk
193
+ if temp_file.size > 0
194
+ @uploader.upload_async(temp_file,
195
+ :on_complete => method(:clean_temporary_file),
196
+ :upload_options => upload_options )
197
+ end
198
+ end
199
+
200
+
201
+ def rotation_strategy
202
+ case @rotation_strategy
203
+ when "size"
204
+ SizeRotationPolicy.new(size_file)
205
+ when "time"
206
+ TimeRotationPolicy.new(time_file)
207
+ when "size_and_time"
208
+ SizeAndTimeRotationPolicy.new(size_file, time_file)
209
+ end
210
+ end
211
+
212
+ def clean_temporary_file(file)
213
+ @logger.debug("Removing temporary file", :file => file.path)
214
+ file.delete!
215
+ end
216
+
217
+ def restore_from_crash
218
+ @crash_uploader = Uploader.new(blob_contianer_resource, @logger, CRASH_RECOVERY_THREADPOOL)
219
+
220
+ temp_folder_path = Pathname.new(@temporary_directory)
221
+ Dir.glob(::File.join(@temporary_directory, "**/*"))
222
+ .select { |file| ::File.file?(file) }
223
+ .each do |file|
224
+ temp_file = TemporaryFile.create_from_existing_file(file, temp_folder_path)
225
+ @logger.debug("Recovering from crash and uploading", :file => temp_file.path)
226
+ @crash_uploader.upload_async(temp_file, :on_complete => method(:clean_temporary_file), :upload_options => upload_options)
227
+ end
228
+ end
229
+
230
+
231
+ public
232
+
233
+ def receive(event)
234
+ azure_login
235
+ azure_blob_service = Azure::Blob::BlobService.new
236
+ containers = azure_blob_service.list_containers
237
+ blob = azure_blob_service.create_block_blob(containers[0].name, event.timestamp.to_s, event.to_json)
238
+ end # def event
239
+
240
+ def azure_login
241
+ Azure.config.storage_account_name ||= ENV['AZURE_STORAGE_ACCOUNT']
242
+ Azure.config.storage_access_key ||= ENV['AZURE_STORAGE_ACCESS_KEY']
243
+ end # def azure_login
244
+ end # class LogStash::Outputs::LogstashAzureBlobOutput
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ require "java"
3
+ require "concurrent"
4
+ require "concurrent/timer_task"
5
+ require "logstash/util"
6
+
7
+ ConcurrentHashMap = java.util.concurrent.ConcurrentHashMap
8
+
9
+ module LogStash
10
+ module Outputs
11
+ class LogstashAzureBlobOutput
12
+ class FileRepository
13
+ DEFAULT_STATE_SWEEPER_INTERVAL_SECS = 60
14
+ DEFAULT_STALE_TIME_SECS = 15 * 60
15
+ # Ensure that all access or work done
16
+ # on a factory is threadsafe
17
+ class PrefixedValue
18
+ def initialize(file_factory, stale_time)
19
+ @file_factory = file_factory
20
+ @lock = Mutex.new
21
+ @stale_time = stale_time
22
+ end
23
+
24
+ def with_lock
25
+ @lock.synchronize {
26
+ yield @file_factory
27
+ }
28
+ end
29
+
30
+ def stale?
31
+ with_lock { |factory| factory.current.size == 0 && (Time.now - factory.current.ctime > @stale_time) }
32
+ end
33
+
34
+ def apply(prefix)
35
+ return self
36
+ end
37
+
38
+ def delete!
39
+ with_lock{ |factory| factory.current.delete! }
40
+ end
41
+ end
42
+
43
+ class FactoryInitializer
44
+ def initialize(tags, encoding, temporary_directory, stale_time)
45
+ @tags = tags
46
+ @encoding = encoding
47
+ @temporary_directory = temporary_directory
48
+ @stale_time = stale_time
49
+ end
50
+
51
+ def apply(prefix_key)
52
+ PrefixedValue.new(TemporaryFileFactory.new(prefix_key, @tags, @encoding, @temporary_directory), @stale_time)
53
+ end
54
+ end
55
+
56
+ def initialize(tags, encoding, temporary_directory,
57
+ stale_time = DEFAULT_STALE_TIME_SECS,
58
+ sweeper_interval = DEFAULT_STATE_SWEEPER_INTERVAL_SECS)
59
+ # The path need to contains the prefix so when we start
60
+ # logtash after a crash we keep the remote structure
61
+ @prefixed_factories = ConcurrentHashMap.new
62
+
63
+ @sweeper_interval = sweeper_interval
64
+
65
+ @factory_initializer = FactoryInitializer.new(tags, encoding, temporary_directory, stale_time)
66
+
67
+ start_stale_sweeper
68
+ end
69
+
70
+ def keys
71
+ @prefixed_factories.keySet
72
+ end
73
+
74
+ def each_files
75
+ @prefixed_factories.elements.each do |prefixed_file|
76
+ prefixed_file.with_lock { |factory| yield factory.current }
77
+ end
78
+ end
79
+
80
+ # Return the file factory
81
+ def get_factory(prefix_key)
82
+ @prefixed_factories.computeIfAbsent(prefix_key, @factory_initializer).with_lock { |factory| yield factory }
83
+ end
84
+
85
+ def get_file(prefix_key)
86
+ get_factory(prefix_key) { |factory| yield factory.current }
87
+ end
88
+
89
+ def shutdown
90
+ stop_stale_sweeper
91
+ end
92
+
93
+ def size
94
+ @prefixed_factories.size
95
+ end
96
+
97
+ def remove_stale(k, v)
98
+ if v.stale?
99
+ @prefixed_factories.remove(k, v)
100
+ v.delete!
101
+ end
102
+ end
103
+
104
+ def start_stale_sweeper
105
+ @stale_sweeper = Concurrent::TimerTask.new(:execution_interval => @sweeper_interval) do
106
+ LogStash::Util.set_thread_name("LogstashAzureBlobOutput, Stale factory sweeper")
107
+
108
+ @prefixed_factories.forEach{|k,v| remove_stale(k,v)}
109
+ end
110
+
111
+ @stale_sweeper.execute
112
+ end
113
+
114
+ def stop_stale_sweeper
115
+ @stale_sweeper.shutdown
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end