logstash-output-application_insights 0.1.3 → 0.1.4

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.
@@ -22,7 +22,7 @@
22
22
  class LogStash::Outputs::Application_insights
23
23
  class Channel
24
24
 
25
- attr_reader :intrumentation_key
25
+ attr_reader :instrumentation_key
26
26
  attr_reader :table_id
27
27
  attr_reader :failed_on_upload_retry_Q
28
28
  attr_reader :failed_on_notify_retry_Q
@@ -31,14 +31,14 @@ class LogStash::Outputs::Application_insights
31
31
 
32
32
  public
33
33
 
34
- def initialize ( intrumentation_key, table_id )
34
+ def initialize ( instrumentation_key, table_id )
35
35
  @closing = false
36
36
  configuration = Config.current
37
37
 
38
38
  @logger = configuration[:logger]
39
39
 
40
- @logger.debug { "Create a new channel, intrumentation_key / table_id : #{intrumentation_key} / #{table_id}" }
41
- @intrumentation_key = intrumentation_key
40
+ @logger.debug { "Create a new channel, instrumentation_key / table_id : #{instrumentation_key} / #{table_id}" }
41
+ @instrumentation_key = instrumentation_key
42
42
  @table_id = table_id
43
43
  set_table_properties( configuration )
44
44
  @semaphore = Mutex.new
@@ -29,11 +29,11 @@ class LogStash::Outputs::Application_insights
29
29
 
30
30
  @logger = configuration[:logger]
31
31
 
32
- @intrumentation_key_table_id_db = {}
32
+ @instrumentation_key_table_id_db = {}
33
33
  @channels = [ ]
34
34
  @create_semaphore = Mutex.new
35
35
 
36
- @default_intrumentation_key = configuration[:intrumentation_key]
36
+ @default_instrumentation_key = configuration[:instrumentation_key]
37
37
  @default_table_id = configuration[:table_id]
38
38
  @tables = configuration[:tables]
39
39
 
@@ -46,29 +46,29 @@ class LogStash::Outputs::Application_insights
46
46
 
47
47
  def receive ( event, encoded_event )
48
48
  if LogStash::SHUTDOWN == event
49
- @logger.info { "received a LogStash::SHUTDOWN event, start shutdown" }
49
+ @logger.info { "received a LogStash::SHUTDOWN event" }
50
50
 
51
51
  elsif LogStash::FLUSH == event
52
- @logger.info { "received a LogStash::FLUSH event, start shutdown" }
53
- end
54
-
55
- table_id = event[METADATA_FIELD_TABLE_ID] || event[FIELD_TABLE_ID] || @default_table_id
56
- intrumentation_key = event[METADATA_FIELD_INSTRUMENTATION_KEY] || event[FIELD_INSTRUMENTATION_KEY] || ( @tables[table_id][TABLE_PROPERTY_INSTRUMENTATION_KEY] if @tables[table_id] ) || @default_intrumentation_key
52
+ @logger.info { "received a LogStash::FLUSH event" }
53
+ else
54
+ table_id = event[METADATA_FIELD_TABLE_ID] || event[FIELD_TABLE_ID] || @default_table_id
55
+ instrumentation_key = event[METADATA_FIELD_INSTRUMENTATION_KEY] || event[FIELD_INSTRUMENTATION_KEY] || ( @tables[table_id][:instrumentation_key] if @tables[table_id] ) || @default_instrumentation_key
57
56
 
58
- @flow_control.pass_or_wait
59
- channel( intrumentation_key, table_id ) << event
57
+ @flow_control.pass_or_wait
58
+ channel( instrumentation_key, table_id ) << event
59
+ end
60
60
  end
61
61
 
62
62
 
63
- def channel ( intrumentation_key, table_id )
63
+ def channel ( instrumentation_key, table_id )
64
64
  begin
65
- dispatch_channel( intrumentation_key, table_id )
65
+ dispatch_channel( instrumentation_key, table_id )
66
66
 
67
67
  rescue NoChannelError
68
68
  begin
69
- create_channel( intrumentation_key, table_id )
69
+ create_channel( instrumentation_key, table_id )
70
70
  rescue ChannelExistError # can happen due to race conditions
71
- dispatch_channel( intrumentation_key, table_id )
71
+ dispatch_channel( instrumentation_key, table_id )
72
72
  end
73
73
  end
74
74
  end
@@ -89,13 +89,13 @@ class LogStash::Outputs::Application_insights
89
89
  private
90
90
 
91
91
  # return channel
92
- def dispatch_channel ( intrumentation_key, table_id )
92
+ def dispatch_channel ( instrumentation_key, table_id )
93
93
  begin
94
- channel = @intrumentation_key_table_id_db[intrumentation_key][table_id]
95
- channel.intrumentation_key # don't remove it, it is to emit an exception in case channel not created yet'
94
+ channel = @instrumentation_key_table_id_db[instrumentation_key][table_id]
95
+ channel.instrumentation_key # don't remove it, it is to emit an exception in case channel not created yet'
96
96
  channel
97
97
  rescue => e
98
- raise NoChannelError if @intrumentation_key_table_id_db[intrumentation_key].nil? || @intrumentation_key_table_id_db[intrumentation_key][table_id].nil?
98
+ raise NoChannelError if @instrumentation_key_table_id_db[instrumentation_key].nil? || @instrumentation_key_table_id_db[instrumentation_key][table_id].nil?
99
99
  @logger.error { "Channel dispatch failed - error: #{e.inspect}" }
100
100
  raise e
101
101
  end
@@ -103,12 +103,12 @@ class LogStash::Outputs::Application_insights
103
103
 
104
104
 
105
105
  # return channel
106
- def create_channel ( intrumentation_key, table_id )
106
+ def create_channel ( instrumentation_key, table_id )
107
107
  @create_semaphore.synchronize {
108
- raise ChannelExistError if @intrumentation_key_table_id_db[intrumentation_key] && @intrumentation_key_table_id_db[intrumentation_key][table_id]
109
- @intrumentation_key_table_id_db[intrumentation_key] ||= {}
110
- channel = Channel.new( intrumentation_key, table_id )
111
- @intrumentation_key_table_id_db[intrumentation_key][table_id] = channel
108
+ raise ChannelExistError if @instrumentation_key_table_id_db[instrumentation_key] && @instrumentation_key_table_id_db[instrumentation_key][table_id]
109
+ @instrumentation_key_table_id_db[instrumentation_key] ||= {}
110
+ channel = Channel.new( instrumentation_key, table_id )
111
+ @instrumentation_key_table_id_db[instrumentation_key][table_id] = channel
112
112
  @channels << channel
113
113
  channel
114
114
  }
@@ -122,7 +122,7 @@ class LogStash::Outputs::Application_insights
122
122
  end
123
123
  end
124
124
 
125
- def mark_invalid_intrumentation_key ( intrumentation_key )
125
+ def mark_invalid_instrumentation_key ( instrumentation_key )
126
126
  # TODO should go to lost and found container
127
127
  end
128
128
 
@@ -98,12 +98,18 @@ class LogStash::Outputs::Application_insights
98
98
 
99
99
  def set_current_storage_account_client
100
100
  configuration = Config.current
101
- alt_storage_access_key = @storage_account[:keys][@current_storage_account_key_index]
102
- options = { :storage_account_name => @storage_account_name, :storage_access_key => alt_storage_access_key }
101
+ storage_access_key = @storage_account[:keys][@current_storage_account_key_index]
102
+
103
+ options = {
104
+ :storage_account_name => @storage_account_name,
105
+ :storage_access_key => storage_access_key,
106
+ :storage_blob_host => "https://#{@storage_account_name}.#{:blob}.#{configuration[:azure_storage_host_suffix]}",
107
+ :storage_table_host => "https://#{@storage_account_name}.#{:table}.#{configuration[:azure_storage_host_suffix]}"
108
+ }
103
109
  options[:ca_file] = configuration[:ca_file] unless configuration[:ca_file].empty?
104
- @current_azure_storage_client = Azure::Storage::Client.new( options )
105
110
 
106
- @current_azure_storage_auth_sas = Azure::Storage::Auth::SharedAccessSignature.new( @storage_account_name, alt_storage_access_key )
111
+ @current_azure_storage_client = Azure::Storage::Client.new( options )
112
+ @current_azure_storage_auth_sas = Azure::Storage::Auth::SharedAccessSignature.new( @storage_account_name, storage_access_key )
107
113
  end
108
114
 
109
115
  end
@@ -79,26 +79,25 @@ class LogStash::Outputs::Application_insights
79
79
  configuration[config_name] = config_value
80
80
 
81
81
  when :azure_storage_container_prefix
82
- azure_storage_container_prefix = validate_and_adjust( config_name, config_value, String )
83
- unless azure_storage_container_prefix.empty?
84
- len = 63 - "-#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}-yyyy-mm-dd".length
82
+ unless config_value.empty?
83
+ azure_storage_container_prefix = validate_and_adjust( config_name, config_value, String )
84
+ len = 63 - "#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}--yyyy-mm-dd".length
85
85
  validate_max( "azure_storage_container_prefix length", azure_storage_container_prefix.length, len )
86
- azure_storage_container_prefix += "-"
86
+ azure_storage_container_prefix = azure_storage_container_prefix.downcase
87
+ container_name = "#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}-#{azure_storage_container_prefix}-yyyy-mm-dd"
88
+ raise ConfigurationError, "#{config_name.to_s} must have only alphanumeric characters and dash, cannot start or end with a dash, and a dash cannot follow a dash" unless Utils.valid_container_name?( container_name )
89
+ configuration[config_name] = "-#{azure_storage_container_prefix}"
87
90
  end
88
- azure_storage_container_prefix = azure_storage_container_prefix.downcase
89
- container_name = "#{azure_storage_container_prefix}#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}-yyyy-mm-dd"
90
- raise ConfigurationError, "#{config_name.to_s} must have only alphanumeric and dash characters, cannot start or end with a dash, and a dash cannot follow a dash" unless Utils.valid_container_name?( container_name )
91
- configuration[config_name] = azure_storage_container_prefix + AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX
92
-
93
- when :azure_storage_azure_storage_table_prefix
94
- azure_storage_table_prefix = validate_and_adjust( config_name, config_value, String )
95
- unless azure_storage_table_prefix.empty?
96
- len = 63 - "-#{AZURE_STORAGE_TABLE_LOGSTASH_PREFIX}yyyymmdd".length
91
+
92
+ when :azure_storage_table_prefix
93
+ unless config_value.empty?
94
+ azure_storage_table_prefix = validate_and_adjust( config_name, config_value, String )
95
+ len = 63 - "#{AZURE_STORAGE_TABLE_LOGSTASH_PREFIX}yyyymmdd".length
97
96
  validate_max( "azure_storage_table_prefix length", azure_storage_table_prefix.length, len )
97
+ table_name = "#{AZURE_STORAGE_TABLE_LOGSTASH_PREFIX}#{azure_storage_table_prefix}yyyymmdd"
98
+ raise ConfigurationError, "#{config_name} must have only alphanumeric" unless Utils.valid_table_name?( table_name )
99
+ configuration[config_name] = azure_storage_table_prefix
98
100
  end
99
- table_name = "#{azure_storage_table_prefix}#{AZURE_STORAGE_TABLE_LOGSTASH_PREFIX}yyyymmdd"
100
- raise ConfigurationError, "#{config_name} must have only alphanumeric" unless Utils.valid_table_name?( table_name )
101
- configuration[config_name] = azure_storage_table_prefix + AZURE_STORAGE_TABLE_LOGSTASH_PREFIX
102
101
 
103
102
  when :ca_file
104
103
  config_value = validate_and_adjust( config_name, config_value, String )
@@ -107,17 +106,21 @@ class LogStash::Outputs::Application_insights
107
106
  end
108
107
  configuration[config_name] = validate_and_adjust( config_name, config_value, String )
109
108
 
109
+ when :azure_storage_host_suffix
110
+ config_value = validate_and_adjust( config_name, config_value, String )
111
+ unless config_value.empty?
112
+ raise ConfigurationError, "#{config_name} must have a valid host DNS address" unless Utils.dns_address?( config_value )
113
+ end
114
+ configuration[config_name] = validate_and_adjust( config_name, config_value, String )
115
+
110
116
  when :azure_storage_blob_prefix
111
- azure_storage_blob_prefix = validate_and_adjust( config_name, config_value, String )
112
- unless azure_storage_blob_prefix.empty?
113
- len = 1024 - "-#{AZURE_STORAGE_BLOB_LOGSTASH_PREFIX}_ikey-#{INSTRUMENTATION_KEY_TEMPLATE}_table-#{TABLE_ID_TEMPLATE}_yyyy-mm-dd-HH-MM-SS-LLL".length
117
+ unless config_value.empty?
118
+ azure_storage_blob_prefix = validate_and_adjust( config_name, config_value, String )
119
+ len = 1024 - "#{AZURE_STORAGE_BLOB_LOGSTASH_PREFIX}//ikey-#{INSTRUMENTATION_KEY_TEMPLATE}/table-#{TABLE_ID_TEMPLATE}/yyyy-mm-dd-HH-MM-SS-LLL_0000.json".length
114
120
  validate_max( "azure_storage_blob_prefix length", azure_storage_blob_prefix.length, len )
115
- azure_storage_blob_prefix += "-"
121
+ raise ConfigurationError, "#{config_name.to_s} doesn't meet url format" unless Utils.url?( "http://storage/container/#{azure_storage_blob_prefix}_ikey-#{INSTRUMENTATION_KEY_TEMPLATE}_table-#{TABLE_ID_TEMPLATE}.json" )
122
+ configuration[config_name] = "/#{azure_storage_blob_prefix}"
116
123
  end
117
- azure_storage_blob_prefix += AZURE_STORAGE_BLOB_LOGSTASH_PREFIX
118
-
119
- raise ConfigurationError, "#{config_name.to_s} doesn't meet url format" unless Utils.url?( "http://storage/container/#{azure_storage_blob_prefix}_ikey-#{INSTRUMENTATION_KEY_TEMPLATE}_table-#{TABLE_ID_TEMPLATE}.json" )
120
- configuration[config_name] = azure_storage_blob_prefix
121
124
 
122
125
  when :table_id
123
126
  configuration[config_name] = validate_and_adjust_guid( config_name, config_value )
@@ -200,6 +203,10 @@ class LogStash::Outputs::Application_insights
200
203
  end
201
204
  }
202
205
  validate_and_adjust_table_properties!( configuration, configuration )
206
+
207
+ configuration[:state_table_name] = "#{AZURE_STORAGE_TABLE_LOGSTASH_PREFIX}#{configuration[:azure_storage_table_prefix]}#{STATE_TABLE_NAME}"
208
+ configuration[:test_storage_container] = "#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}#{configuration[:azure_storage_container_prefix]}-#{STORAGE_TEST_CONTAINER_NAME}"
209
+ configuration[:partition_key_prefix] = configuration[:azure_storage_blob_prefix].gsub( "/", "" )
203
210
  @@configuration = configuration
204
211
  end
205
212
 
@@ -220,8 +227,8 @@ class LogStash::Outputs::Application_insights
220
227
 
221
228
  case property_name.downcase
222
229
 
223
- when :intrumentation_key
224
- properties[:intrumentation_key] = validate_and_adjust_guid( info, property_value )
230
+ when :instrumentation_key
231
+ properties[:instrumentation_key] = validate_and_adjust_guid( info, property_value )
225
232
  when :blob_serialization
226
233
  property_value = property_value.downcase
227
234
  raise ConfigurationError, "#{info}, can be set to only one of the following values: #{VALID_EXT_EVENT_FORMAT}" unless VALID_EXT_EVENT_FORMAT.include?( property_value )
@@ -281,7 +288,7 @@ class LogStash::Outputs::Application_insights
281
288
  def self.validate_and_adjust_ext ( property, ext, prefix )
282
289
  ext = validate_and_adjust( property, ext, String )
283
290
  raise ConfigurationError, "#{property.to_s} must be a valid extension string, have only alphanumeric, dash and underline characters" unless Utils.ext?( ext )
284
- len = 1024 - "#{prefix}-#{AZURE_STORAGE_BLOB_LOGSTASH_PREFIX}_ikey-#{INSTRUMENTATION_KEY_TEMPLATE}_table-#{TABLE_ID_TEMPLATE}_yyyy-mm-dd-HH-MM-SS-LLL".length
291
+ len = 1024 - "#{AZURE_STORAGE_BLOB_LOGSTASH_PREFIX}#{prefix}/ikey-#{INSTRUMENTATION_KEY_TEMPLATE}/table-#{TABLE_ID_TEMPLATE}/yyyy-mm-dd-HH-MM-SS-LLL_0000".length
285
292
  raise ConfigurationError, "#{property.to_s} length cannot be more than #{len} characters" unless ext.length <= len
286
293
  ext
287
294
  end
@@ -26,7 +26,8 @@ class LogStash::Outputs::Application_insights
26
26
  :notification_version => @notification_version || DEFAULT_NOTIFICATION_VERSION,
27
27
  :event_separator => @event_separator || DEFAULT_EVENT_SEPARATOR,
28
28
 
29
- :notification_endpoint => @notification_endpoint || DEFAULT_NOTIFICATION_ENDPOINT,
29
+ :azure_storage_host_suffix => @azure_storage_host_suffix || DEFAULT_AZURE_STORAGE_HOST_SUFFIX,
30
+ :application_insights_endpoint => @application_insights_endpoint || DEFAULT_APPLICATION_INSIGHTS_ENDPOINT,
30
31
  :azure_storage_blob_prefix => @azure_storage_blob_prefix || DEFAULT_AZURE_STORAGE_BLOB_PREFIX || Utils.to_storage_name( Socket.gethostname.strip ) || "",
31
32
  :azure_storage_container_prefix => @azure_storage_container_prefix || DEFAULT_AZURE_STORAGE_CONTAINER_PREFIX || Utils.to_storage_name( Socket.gethostname.strip ) || "",
32
33
  :azure_storage_table_prefix => @azure_storage_table_prefix || DEFAULT_AZURE_STORAGE_TABLE_PREFIX || Utils.to_storage_name( Socket.gethostname.strip ) || "",
@@ -43,6 +44,9 @@ class LogStash::Outputs::Application_insights
43
44
  :blob_retention_time => @blob_retention_time || DEFAULT_BLOB_RETENTION_TIME,
44
45
  :blob_access_expiry_time => @blob_access_expiry_time || DEFAULT_BLOB_ACCESS_EXPIRY_TIME,
45
46
 
47
+ :validate_notification => @validate_notification || DEFAULT_VALIDATE_NOTIFICATION,
48
+ :validate_storage => @validate_storage || DEFAULT_VALIDATE_STORAGE,
49
+
46
50
  :resurrect_delay => @resurrect_delay || DEFAULT_STORAGE_RESURRECT_DELAY,
47
51
  :io_retry_delay => @io_retry_delay || DEFAULT_IO_RETRY_DELAY,
48
52
  :io_max_retries => @io_max_retries || DEFAULT_IO_MAX_RETRIES,
@@ -61,7 +65,7 @@ class LogStash::Outputs::Application_insights
61
65
 
62
66
  :tables => @tables || { },
63
67
  :table_id => @table_id || DEFAULT_TABLE_ID,
64
- :intrumentation_key => @intrumentation_key || DEFAULT_INSTRUMENTATION_KEY,
68
+ :instrumentation_key => @instrumentation_key || DEFAULT_INSTRUMENTATION_KEY,
65
69
  :table_columns => @table_columns,
66
70
  :case_insensitive_columns => @case_insensitive_columns || DEFAULT_CASE_INSENSITIVE,
67
71
  :serialized_event_field => @serialized_event_field,
@@ -75,7 +79,8 @@ class LogStash::Outputs::Application_insights
75
79
 
76
80
  BOOLEAN_PROPERTIES = [ :disable_notification, :disable_blob_upload,
77
81
  :stop_on_unknown_io_errors, :disable_telemetry,
78
- :disable_cleanup, :delete_not_notified_blobs,
82
+ :disable_cleanup, :delete_not_notified_blobs,
83
+ :validate_notification, :validate_storage,
79
84
  :save_notified_blobs_records, :case_insensitive_columns,
80
85
  :table_columns, :serialized_event_field ]
81
86
 
@@ -135,12 +140,13 @@ class LogStash::Outputs::Application_insights
135
140
  MIN_FLOW_CONTROL_DELAY = 0.1 # in seconds, 1 seconds, can be less than 1 seconds, like 0.5, 0.1
136
141
  MAX_FLOW_CONTROL_DELAY = 0 # in seconds, 1 seconds, can be less than 1 seconds, like 0.5, 0.1
137
142
 
138
- METADATA_FIELD_INSTRUMENTATION_KEY = "[@metadata]intrumentation_key"
143
+ METADATA_FIELD_INSTRUMENTATION_KEY = "[@metadata]instrumentation_key"
139
144
  METADATA_FIELD_TABLE_ID = "[@metadata]table_id"
140
- FIELD_INSTRUMENTATION_KEY = "intrumentation_key"
145
+ FIELD_INSTRUMENTATION_KEY = "instrumentation_key"
141
146
  FIELD_TABLE_ID = "table_id"
142
147
 
143
148
  STATE_TABLE_NAME = "BlobsState"
149
+ STORAGE_TEST_CONTAINER_NAME = "test-container"
144
150
 
145
151
  AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX = "logstash" # lower case only, dash allowed
146
152
  AZURE_STORAGE_BLOB_LOGSTASH_PREFIX = "logstash"
@@ -181,7 +187,8 @@ class LogStash::Outputs::Application_insights
181
187
  DEFAULT_BLOB_RETENTION_TIME = 60 * 60 * 24 * 7 # in seconds - one week
182
188
  DEFAULT_BLOB_ACCESS_EXPIRY_TIME = 60 * 60 * 24 * 1 # in seconds - one day
183
189
  DEFAULT_STORAGE_RESURRECT_DELAY = 10
184
- DEFAULT_NOTIFICATION_ENDPOINT = "https://dc.services.visualstudio.com/v2/track"
190
+ DEFAULT_APPLICATION_INSIGHTS_ENDPOINT = "https://dc.services.visualstudio.com/v2/track"
191
+ DEFAULT_AZURE_STORAGE_HOST_SUFFIX = "core.windows.net"
185
192
  DEFAULT_NOTIFICATION_VERSION = 1
186
193
  DEFAULT_DISABLE_NOTIFICATION = false
187
194
  DEFAULT_DISABLE_BLOB_UPLOAD = false
@@ -193,6 +200,9 @@ class LogStash::Outputs::Application_insights
193
200
 
194
201
  DEFAULT_CASE_INSENSITIVE = false
195
202
 
203
+ DEFAULT_VALIDATE_NOTIFICATION = false
204
+ DEFAULT_VALIDATE_STORAGE = false
205
+
196
206
  DEFAULT_LOGGER_FILES = [ "logstash-output-application-insights.log" ]
197
207
  DEFAULT_LOG_LEVEL = "INFO"
198
208
  DEFAULT_LOGGER_PROGNAME = "AI"
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ # ----------------------------------------------------------------------------------
4
+ # Logstash Output Application Insights
5
+ #
6
+ # Copyright (c) Microsoft Corporation
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Licensed under the Apache License, Version 2.0 (the License);
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ #
18
+ # See the Apache Version 2.0 License for specific language governing
19
+ # permissions and limitations under the License.
20
+ # ----------------------------------------------------------------------------------
21
+ class LogStash::Outputs::Application_insights
22
+ class Notification_recovery
23
+
24
+ public
25
+
26
+ def initialize
27
+ configuration = Config.current
28
+ @logger = configuration[:logger]
29
+ @storage_account_name_key = configuration[:storage_account_name_key]
30
+ @queue = Queue.new
31
+
32
+ @closing = nil
33
+ @thread = nil
34
+ end
35
+
36
+ def start
37
+ @thread = recovery_thread
38
+ end
39
+
40
+ def recover_later ( tuple )
41
+ @notification_state_on = false
42
+ @queue << tuple
43
+ end
44
+
45
+ def enqueue ( tuple )
46
+ @queue << tuple
47
+ end
48
+
49
+ def close
50
+ @closing = true
51
+ # @thread.join
52
+ end
53
+
54
+ private
55
+
56
+ def stopped?
57
+ @closing
58
+ end
59
+
60
+ def init_queues ( storage_account_name_key, queues )
61
+ storage_account_name_key.each do |storage_account_name, storage_account_keys|
62
+ queues.each_key do |action|
63
+ queues[action][storage_account_name] = Queue.new
64
+ end
65
+ end
66
+ end
67
+
68
+ def recovery_thread
69
+ Thread.new do
70
+ blob = Blob.new
71
+ counter = Concurrent::AtomicFixnum.new(0)
72
+
73
+ loop do
74
+ tuple = @queue.pop
75
+ Stud.stoppable_sleep(Float::INFINITY, 1) { state_on?( blob ) && 10 > counter.value }
76
+
77
+ counter.increment
78
+ Thread.new( counter, tuple ) do |counter, tuple|
79
+ Blob.new.send( :notify, tuple )
80
+ counter.decrement
81
+ end
82
+ tuple = nil # release for GC
83
+ end
84
+ end
85
+ end
86
+
87
+ def state_on? ( blob )
88
+ @notification_state_on ||= blob.test_notification( @storage_account_name_key[0][0] )
89
+ end
90
+
91
+ public
92
+
93
+ @@instance = Notification_recovery.new
94
+
95
+ def self.instance
96
+ @@instance
97
+ end
98
+
99
+ private_class_method :new
100
+ end
101
+ end
@@ -48,7 +48,10 @@ class LogStash::Outputs::Application_insights
48
48
  end
49
49
  display_msg( "all events were uploaded to Azure storage" )
50
50
 
51
+ # close all blobs activity
51
52
  Blob.close
53
+
54
+ # close all channels activity
52
55
  @channels.close
53
56
 
54
57
  # wait for all uploads to commit
@@ -0,0 +1,140 @@
1
+ # encoding: utf-8
2
+
3
+ # ----------------------------------------------------------------------------------
4
+ # Logstash Output Application Insights
5
+ #
6
+ # Copyright (c) Microsoft Corporation
7
+ #
8
+ # All rights reserved.
9
+ #
10
+ # Licensed under the Apache License, Version 2.0 (the License);
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ #
18
+ # See the Apache Version 2.0 License for specific language governing
19
+ # permissions and limitations under the License.
20
+ # ----------------------------------------------------------------------------------
21
+ class LogStash::Outputs::Application_insights
22
+ class Shutdown_recovery
23
+
24
+ public
25
+
26
+ def initialize
27
+ configuration = Config.current
28
+ @logger = configuration[:logger]
29
+ @storage_account_name_key = configuration[:storage_account_name_key]
30
+ @partition_key_prefix =configuration[:azure_storage_blob_prefix].gsub( "/", "" )
31
+
32
+ @storage_recovery = Storage_recovery.instance
33
+ @notification_recovery = Notification_recovery.instance
34
+
35
+ @closing = nil
36
+ @threads = []
37
+ end
38
+
39
+ def start
40
+ @storage_account_name_key.each do |storage_account_name, storage_account_keys|
41
+ @threads << recovery_thread( storage_account_name, :uploading)
42
+ @threads << recovery_thread( storage_account_name, :committed)
43
+ end
44
+ end
45
+
46
+ def close
47
+ @closing = true
48
+ @threads.each do |thread|
49
+ thread.join
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def stopped?
56
+ @closing
57
+ end
58
+
59
+ def recovery_thread( storage_account_name, state )
60
+ Thread.new( storage_account_name, state ) do |storage_account_name, state|
61
+
62
+ blob = Blob.new
63
+
64
+ committed_tuples = [ ]
65
+ uncommitted_tuples = [ ]
66
+ upload_empty_tuples = [ ]
67
+ token = nil
68
+ finished = false
69
+ filter = "#{:PartitionKey} eq '#{@partition_key_prefix}-#{state}'"
70
+
71
+ # should exit thread after fetching data from table, and submit recovery, the loop is only for case of failure
72
+ until finished || stopped? do
73
+ entities = blob.state_table_query( storage_account_name, filter, token )
74
+ if entities
75
+ token = entities.continuation_token
76
+
77
+ if :committed == state
78
+ entities.each do |entity|
79
+ State.instance.inc_pending_notifications
80
+ tuple = blob.table_entity_to_tuple( entity.properties )
81
+ @notification_recovery.enqueue( tuple )
82
+ end
83
+
84
+ elsif :uploading == state
85
+ # first tuples are collected, before send to queues, to make sure blob states don't change in between
86
+ entities.each do |entity|
87
+ typed_tuple = nil
88
+ until typed_tuple || stopped?
89
+ typed_tuple = blob.update_commited_or_uncommited_list( entity.properties )
90
+ Stud.stoppable_sleep(60, 1) { stopped? } unless typed_tuple
91
+ end
92
+
93
+ next if stopped?
94
+
95
+ if typed_tuple[:committed]
96
+ committed_tuples << typed_tuple[:committed]
97
+ elsif typed_tuple[:uncommitted]
98
+ uncommitted_tuples << typed_tuple[:uncommitted]
99
+ else
100
+ upload_empty_tuples << typed_tuple[:upload_empty]
101
+ end
102
+ end
103
+ end
104
+
105
+ next if token
106
+
107
+ committed_tuples.each do |tuple|
108
+ State.instance.inc_pending_commits
109
+ @storage_recovery.recover_later( tuple, :state_table_update, storage_account_name )
110
+ end
111
+
112
+ uncommitted_tuples.each do |tuple|
113
+ State.instance.inc_pending_commits
114
+ @storage_recovery.recover_later( tuple, :commit, storage_account_name )
115
+ end
116
+
117
+ upload_empty_tuples.each do |tuple|
118
+ @storage_recovery.recover_later( tuple, :state_table_update, storage_account_name )
119
+ end
120
+
121
+ finished = true
122
+ else
123
+ Stud.stoppable_sleep(60, 1) { stopped? }
124
+ end
125
+ end
126
+ @logger.info { "exit table recovery thread, storage: #{storage_account_name}, state: #{state}, entities: #{entities ? entities.length : nil}" }
127
+ end
128
+ end
129
+
130
+ public
131
+
132
+ @@instance = Shutdown_recovery.new
133
+
134
+ def self.instance
135
+ @@instance
136
+ end
137
+
138
+ private_class_method :new
139
+ end
140
+ end