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.
- checksums.yaml +4 -4
- data/README.md +29 -16
- data/lib/logstash/outputs/application_insights.rb +47 -62
- data/lib/logstash/outputs/application_insights/blob.rb +224 -383
- data/lib/logstash/outputs/application_insights/channel.rb +4 -4
- data/lib/logstash/outputs/application_insights/channels.rb +24 -24
- data/lib/logstash/outputs/application_insights/client.rb +10 -4
- data/lib/logstash/outputs/application_insights/config.rb +34 -27
- data/lib/logstash/outputs/application_insights/constants.rb +16 -6
- data/lib/logstash/outputs/application_insights/notification_recovery.rb +101 -0
- data/lib/logstash/outputs/application_insights/shutdown.rb +3 -0
- data/lib/logstash/outputs/application_insights/shutdown_recovery.rb +140 -0
- data/lib/logstash/outputs/application_insights/storage_cleanup.rb +7 -7
- data/lib/logstash/outputs/application_insights/storage_recovery.rb +104 -0
- data/lib/logstash/outputs/application_insights/utils.rb +17 -0
- data/lib/logstash/outputs/application_insights/validate_notification.rb +37 -0
- data/lib/logstash/outputs/application_insights/validate_storage.rb +41 -0
- data/lib/logstash/outputs/application_insights/version.rb +1 -1
- data/logstash-output-application-insights.gemspec +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12c164429fdbd439168dab5277c28185ce2e4985
|
4
|
+
data.tar.gz: 44048dcef57a0bbe542eb8a3b68811f968eb8352
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8388e00a758efb2fb30215ace69730d28690b573c66ee901be7dd635f16660b5a2236ef0f1bb4c0665924b54e8a3150ea868ce8c1771f42409af3cd5ed251ec3
|
7
|
+
data.tar.gz: cc27c111592a9bd9aca93fb8f697219eb58698ae7691222b43ff98313ce1cd365263feb9fd19fbfbcaa07cca98a9a114c2e0eb3b1a25319b151d7f789f515880
|
data/README.md
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# Microsoft Application Insights Output Plugin for Logstash
|
2
|
-
|
3
2
|
[](https://badge.fury.io/gh/microsoft%2Flogstash-output-application-insights)
|
4
3
|
[](https://badge.fury.io/rb/logstash-output-application_insights)
|
4
|
+
|
5
|
+
* This project is a plugin for [Logstash](https://github.com/elastic/logstash).
|
6
|
+
|
7
|
+
* This plugin have to be installed on top of the Logstash core pipeline. It is not a stand-alone program.
|
5
8
|
|
6
|
-
This
|
9
|
+
* **This plugin outputs events to Microsoft Application Insights Analytics open schema tables.**
|
7
10
|
|
8
11
|
# Plugin Features
|
9
12
|
|
@@ -49,7 +52,7 @@ filter {
|
|
49
52
|
}
|
50
53
|
output {
|
51
54
|
application_insights {
|
52
|
-
|
55
|
+
instrumentation_key => "5a6714a3-ec7b-4999-ab96-232f1da92059"
|
53
56
|
table_id => "c24394e1-f077-420e-8a25-ef6fdf045938"
|
54
57
|
storage_account_name_key => [ "my-storage-account", "pfrYTwPgKyYNfKBY2QdF+v5sbgx8/eAQp+FFkGpPBnkMDE1k+ZNK3r3qIPqqw8UsOIUqaF3dXBdPDouGJuxNXQ==" ]
|
55
58
|
}
|
@@ -147,12 +150,12 @@ example:
|
|
147
150
|
azure_storage_blob_prefix => "myprefix"
|
148
151
|
```
|
149
152
|
|
150
|
-
###
|
151
|
-
Default Application Insights Analytics
|
153
|
+
### instrumentation_key
|
154
|
+
Default Application Insights Analytics instrumentation_key. No default
|
152
155
|
It will be used only in case the key is not specified in the tables property associated to a table_id, or as field or metadata fields in the event
|
153
156
|
example:
|
154
157
|
```ruby
|
155
|
-
|
158
|
+
instrumentation_key => "5A6714A3-EC7B-4999-AB96-232F1DA92059"
|
156
159
|
```
|
157
160
|
|
158
161
|
### table_id
|
@@ -358,11 +361,11 @@ example:
|
|
358
361
|
```ruby
|
359
362
|
delete_not_notified_blobs => true
|
360
363
|
```
|
361
|
-
###
|
364
|
+
### validate_notification
|
362
365
|
When set to true, access to application insights will be validated at initialization and if validation fail, logstash process will abort. Default false
|
363
366
|
example:
|
364
367
|
```ruby
|
365
|
-
|
368
|
+
validate_notification => true
|
366
369
|
```
|
367
370
|
|
368
371
|
### validate_storage
|
@@ -404,22 +407,32 @@ example:
|
|
404
407
|
stop_on_unknown_io_errors => true
|
405
408
|
```
|
406
409
|
|
407
|
-
###
|
408
|
-
when set
|
409
|
-
Used for troubleshooting
|
410
|
+
### azure_storage_host_suffix
|
411
|
+
when set an alternative storage service will be used. Default "core.windows.net"
|
410
412
|
example:
|
411
413
|
```ruby
|
412
|
-
|
414
|
+
azure_storage_host_suffix => "core.windows.net"
|
415
|
+
```
|
416
|
+
|
417
|
+
### application_insights_endpoint
|
418
|
+
when set blob ready notification are sent to an alternative endpoint. Default "https://dc.services.visualstudio.com/v2/track"
|
419
|
+
example:
|
420
|
+
```ruby
|
421
|
+
application_insights_endpoint => "https://dc.services.visualstudio.com/v2/track"
|
413
422
|
```
|
414
423
|
|
415
424
|
### notification_version
|
416
425
|
Advanced, internal, should not be set, the only current valid value is 1
|
426
|
+
example:
|
427
|
+
```ruby
|
428
|
+
notification_version => 1
|
429
|
+
```
|
417
430
|
|
418
431
|
### tables
|
419
432
|
Allow to support multiple tables, and to configure each table with its own parameters, using the global parameters as defaults.
|
420
433
|
It is only required if the plugin need to support mutiple table.
|
421
434
|
Tables is Hash, where the key is the table_id and the value is a has of specific properties, that their defualt value are the global properties.
|
422
|
-
The specific properties are:
|
435
|
+
The specific properties are: instrumentation_key, table_columns, blob_max_delay, csv_default_value, serialized_event_field, blob_serialization, csv_separator
|
423
436
|
template:
|
424
437
|
```ruby
|
425
438
|
tables => { "table_id1" => { properties } "table_id2" => { properties } }
|
@@ -427,12 +440,12 @@ tables => { "table_id1" => { properties } "table_id2" => { properties } }
|
|
427
440
|
|
428
441
|
Examples:
|
429
442
|
```ruby
|
430
|
-
tables => { "6f29a89e-1385-4317-85af-3ac1cea48058" => { "
|
443
|
+
tables => { "6f29a89e-1385-4317-85af-3ac1cea48058" => { "instrumentation_key" => "76c3b8e9-dfc6-4afd-8d4c-3b02fdadb19f", "blob_max_delay" => 60 } }
|
431
444
|
```
|
432
445
|
|
433
446
|
```ruby
|
434
|
-
tables => { "6f29a89e-1385-4317-85af-3ac1cea48058" => { "
|
435
|
-
"2e1b46aa-56d2-4e13-a742-d0db516d66fc" => { "
|
447
|
+
tables => { "6f29a89e-1385-4317-85af-3ac1cea48058" => { "instrumentation_key" => "76c3b8e9-dfc6-4afd-8d4c-3b02fdadb19f", "blob_max_delay" => 60 }
|
448
|
+
"2e1b46aa-56d2-4e13-a742-d0db516d66fc" => { "instrumentation_key" => "76c3b8e9-dfc6-4afd-8d4c-3b02fdadb19f", "blob_max_delay" => 120 "ext" => "csv" "serialized_event_field" => "message" }
|
436
449
|
}
|
437
450
|
```
|
438
451
|
|
@@ -54,6 +54,12 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
54
54
|
require "logstash/outputs/application_insights/blob"
|
55
55
|
autoload :Block, "logstash/outputs/application_insights/block"
|
56
56
|
autoload :Storage_cleanup, "logstash/outputs/application_insights/storage_cleanup"
|
57
|
+
autoload :Shutdown_recovery, "logstash/outputs/application_insights/shutdown_recovery"
|
58
|
+
autoload :Storage_recovery, "logstash/outputs/application_insights/storage_recovery"
|
59
|
+
autoload :Notification_recovery, "logstash/outputs/application_insights/notification_recovery"
|
60
|
+
autoload :Validate_storage, "logstash/outputs/application_insights/validate_storage"
|
61
|
+
autoload :Validate_notification, "logstash/outputs/application_insights/validate_notification"
|
62
|
+
|
57
63
|
|
58
64
|
autoload :Clients, "logstash/outputs/application_insights/clients"
|
59
65
|
autoload :Client, "logstash/outputs/application_insights/client"
|
@@ -101,10 +107,10 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
101
107
|
# string may include only characters that are allowed in any valid url
|
102
108
|
config :azure_storage_blob_prefix, :validate => :string
|
103
109
|
|
104
|
-
# Default Application Insights Analytics
|
110
|
+
# Default Application Insights Analytics instrumentation_key
|
105
111
|
# will be used only in case it is not specified as a table_id property in tables
|
106
112
|
# or as part of the event's fields or event's metadata fields
|
107
|
-
config :
|
113
|
+
config :instrumentation_key, :validate => :string
|
108
114
|
|
109
115
|
# Default Application Insights Analytics table_id
|
110
116
|
# will be used only in case it is not specified as part o
|
@@ -121,8 +127,8 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
121
127
|
# A hash of table_ids, where each table_id points to a set of properties
|
122
128
|
# the properties are a hash, where the keys are are the properties
|
123
129
|
# current supported properties per table_id are:
|
124
|
-
#
|
125
|
-
#
|
130
|
+
# instrumentation_key, ext, table_columns, csv_default_value, csv_separator, blob_max_delay, event_separator, serialized_event_field
|
131
|
+
# instrumentation_key, Application Insights Analytics instrumentation_key, will be used in case not specified in any of the event's fields or events's metadata fileds
|
126
132
|
# serialized_event_field, specifies the field that may contain the full serialized event (either as json or csv),
|
127
133
|
# when specified, the ext property should be set either to csv or to json (json is the default)
|
128
134
|
# if event. does not conatin the field, value will be created based on the fileds in the evnt, according to table_columns if configured, or all fileds in event
|
@@ -150,7 +156,7 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
150
156
|
# can be specified only together with table_columns
|
151
157
|
#
|
152
158
|
# Example json table_id
|
153
|
-
# tables => {"a679fbd2-702c-4c46-8548-80082c66ef28" => {"
|
159
|
+
# tables => {"a679fbd2-702c-4c46-8548-80082c66ef28" => {"instrumentation_key" => "abee940b-e648-4242-b6b3-f2826667bf96", "blob_max_delay" => 60} }
|
154
160
|
# Example json table_id, input in serialized_event_field
|
155
161
|
# {"ab6a3584-aef0-4a82-8725-2f2336e59f3e" => {"serialized_event_field" => "message". "ext" => "json"} }
|
156
162
|
# Example csv table_id, input in serialized_event_field
|
@@ -271,25 +277,30 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
271
277
|
# When set to true, process will stop if an unknown IO error is found
|
272
278
|
config :stop_on_unknown_io_errors, :validate => :boolean
|
273
279
|
|
274
|
-
# Advanced, internal, should not be set, the default is Application Insights production endpoint
|
275
280
|
# when set notification are sent to an alternative endpoint, used for internal testing
|
276
|
-
config :
|
281
|
+
config :application_insights_endpoint, :validate => :string
|
282
|
+
|
283
|
+
# when set an alternative storage service will be used.
|
284
|
+
config :azure_storage_host_suffix, :validate => :string
|
277
285
|
|
278
286
|
# Advanced, internal, should not be set, the only current valid value is 1
|
279
287
|
config :notification_version, :validate => :number
|
280
288
|
|
281
289
|
# When set to true, access to application insights will be validated at initialization
|
282
290
|
# and if validation fail, logstash process will abort
|
283
|
-
config :
|
291
|
+
config :validate_notification, :validate => :boolean
|
284
292
|
|
285
293
|
# When set to true, access to azure storage for each of the configured accounts will be validated at initialization
|
286
294
|
# and if validation fail, logstash process will abort
|
287
|
-
config :validate_storage, :validate => :boolean
|
295
|
+
config :validate_storage, :validate => :boolean
|
288
296
|
|
289
297
|
public
|
290
298
|
|
291
299
|
def register
|
292
300
|
|
301
|
+
# logstash define: @original_params = original_params
|
302
|
+
# logstash define: @config = params
|
303
|
+
|
293
304
|
# set configuration
|
294
305
|
Config.validate_and_adjust_configuration( default_configuration )
|
295
306
|
configuration = Config.current
|
@@ -305,9 +316,27 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
305
316
|
configuration[:telemetry_channel] = @telemetry.telemetry_channel
|
306
317
|
|
307
318
|
Timer.config( configuration )
|
308
|
-
|
309
|
-
|
310
|
-
|
319
|
+
|
320
|
+
if @validate_notification
|
321
|
+
status = Validate_notification.new.validate
|
322
|
+
raise ConfigurationError, "Failed to access application insights at #{configuration[:application_insights_endpoint]}, due to error #{status[:error].inspect}" unless status[:success]
|
323
|
+
end
|
324
|
+
|
325
|
+
if @validate_storage
|
326
|
+
result = Validate_storage.new.validate
|
327
|
+
result.each do |storage_account_name, status|
|
328
|
+
raise ConfigurationError, "Failed access azure storage account #{storage_account_name}, due to error #{status[:error].inspect}" unless status[:success]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
@notification_recovery = Notification_recovery.instance
|
333
|
+
@notification_recovery.start
|
334
|
+
|
335
|
+
@storage_recovery = Storage_recovery.instance
|
336
|
+
@storage_recovery.start
|
337
|
+
|
338
|
+
@shutdown_recovery = Shutdown_recovery.instance
|
339
|
+
@shutdown_recovery.start
|
311
340
|
|
312
341
|
@shutdown = Shutdown.instance
|
313
342
|
@channels = Channels.instance
|
@@ -320,7 +349,7 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
320
349
|
# @channels.receive( event, encoded_event )
|
321
350
|
# end
|
322
351
|
|
323
|
-
|
352
|
+
@telemetry.track_event("register", {:properties => configuration})
|
324
353
|
|
325
354
|
|
326
355
|
return "ok\n"
|
@@ -334,60 +363,16 @@ class LogStash::Outputs::Application_insights < LogStash::Outputs::Base
|
|
334
363
|
end
|
335
364
|
|
336
365
|
def close
|
337
|
-
|
338
|
-
|
366
|
+
@telemetry.track_event( "close" )
|
367
|
+
@telemetry.flush
|
368
|
+
@shutdown_recovery.close
|
369
|
+
@storage_recovery.close
|
370
|
+
@notification_recovery.close
|
339
371
|
@shutdown.submit
|
340
372
|
end
|
341
373
|
|
342
374
|
private
|
343
375
|
|
344
376
|
# -----------------------------------------------
|
345
|
-
|
346
|
-
|
347
|
-
def list_blob_names
|
348
|
-
blob_names = Set.new []
|
349
|
-
loop do
|
350
|
-
continuation_token = NIL
|
351
|
-
entries = @azure_blob.list_blobs(@container, { :timeout => 10, :marker => continuation_token})
|
352
|
-
@@logger.debug { 'blob entries: #{entries}' }
|
353
|
-
entries.each do |entry|
|
354
|
-
@@logger.debug { 'blob entry name: #{entry.name}' }
|
355
|
-
blob_names << entry.name
|
356
|
-
end
|
357
|
-
continuation_token = entries.continuation_token
|
358
|
-
break if continuation_token.empty?
|
359
|
-
end
|
360
|
-
return blob_names
|
361
|
-
end # def list_blobs
|
362
|
-
|
363
|
-
|
364
|
-
def list_container_names
|
365
|
-
container_names = Set.new []
|
366
|
-
loop do
|
367
|
-
continuation_token = NIL
|
368
|
-
containers = @azure_blob.list_containers()
|
369
|
-
@@logger.debug { 'containers: #{containers}' }
|
370
|
-
containers.each do |container|
|
371
|
-
@@logger.debug { 'container entry name:' + container.name }
|
372
|
-
container_names << container.name
|
373
|
-
upload(container.name, "blob-append-" + container.name, "test - " + container.name)
|
374
|
-
blobs = @azure_blob.list_blobs(container.name)
|
375
|
-
blobs.each do |blob|
|
376
|
-
@@logger.debug { 'blob name: ' + blob.name }
|
377
|
-
end
|
378
|
-
end
|
379
|
-
continuation_token = containers.continuation_token
|
380
|
-
break if continuation_token.empty?
|
381
|
-
end
|
382
|
-
return container_names
|
383
|
-
end # def list_blobs
|
384
|
-
|
385
|
-
def create_container (container_name)
|
386
|
-
begin
|
387
|
-
@azure_blob.create_container(container_name)
|
388
|
-
rescue
|
389
|
-
@@logger.debug { $! }
|
390
|
-
end
|
391
|
-
end
|
392
377
|
end
|
393
378
|
|
@@ -21,7 +21,7 @@
|
|
21
21
|
class LogStash::Outputs::Application_insights
|
22
22
|
class Blob
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :instrumentation_key
|
25
25
|
attr_reader :table_id
|
26
26
|
attr_reader :storage_account_name
|
27
27
|
attr_reader :container_name
|
@@ -36,37 +36,7 @@ class LogStash::Outputs::Application_insights
|
|
36
36
|
|
37
37
|
public
|
38
38
|
|
39
|
-
|
40
|
-
@@configuration = configuration
|
41
|
-
|
42
|
-
@@logger = configuration[:logger]
|
43
|
-
@@io_retry_delay = configuration[:io_retry_delay]
|
44
|
-
@@io_max_retries = configuration[:io_max_retries]
|
45
|
-
@@blob_max_bytesize = configuration[:blob_max_bytesize]
|
46
|
-
@@blob_max_events = configuration[:blob_max_events]
|
47
|
-
@@state_table_name = "#{configuration[:azure_storage_table_prefix]}#{STATE_TABLE_NAME}"
|
48
|
-
@@save_notified_blobs_records = configuration[:save_notified_blobs_records]
|
49
|
-
|
50
|
-
@@closing = false
|
51
|
-
|
52
|
-
# queues, per storage_account_name, for failed blob commit, will continue to try resending
|
53
|
-
@@failed_on_commit_retry_Qs = {}
|
54
|
-
launch_storage_recovery_threads( @@failed_on_commit_retry_Qs, :commit, :io_failure )
|
55
|
-
launch_storage_recovery_table_threads( :uploading )
|
56
|
-
|
57
|
-
# queues, per storage_account_name, for failed notify, will continue to try resending
|
58
|
-
@@failed_on_notify_retry_Qs = {}
|
59
|
-
launch_storage_recovery_threads( @@failed_on_notify_retry_Qs, :notify, :notify_failed_blob_not_accessible )
|
60
|
-
launch_storage_recovery_table_threads( :committed )
|
61
|
-
|
62
|
-
# for failed to notify due to endpoint, will continue to try resending
|
63
|
-
launch_endpoint_recovery_thread
|
64
|
-
|
65
|
-
# queues, per storage_account_name, for failed to log to table, will continue to try resending
|
66
|
-
@@failed_on_log_to_table_retry_Qs = {}
|
67
|
-
launch_storage_recovery_threads( @@failed_on_log_to_table_retry_Qs, :log_to_table_update, :io_failure )
|
68
|
-
|
69
|
-
end
|
39
|
+
@@closing = false
|
70
40
|
|
71
41
|
def self.close
|
72
42
|
@@closing = true
|
@@ -76,160 +46,22 @@ class LogStash::Outputs::Application_insights
|
|
76
46
|
@@closing
|
77
47
|
end
|
78
48
|
|
79
|
-
def self.launch_endpoint_recovery_thread
|
80
|
-
@@failed_on_notification_endpoint_retry_Q = Queue.new
|
81
|
-
storage_recovery_thread( nil, @@failed_on_notification_endpoint_retry_Q, :notify, :io_failure )
|
82
|
-
end
|
83
|
-
|
84
|
-
def self.launch_storage_recovery_threads ( queues, method, failure_reason )
|
85
|
-
@@configuration[:storage_account_name_key].each do |storage_account_name, storage_account_keys|
|
86
|
-
queues[storage_account_name] = Queue.new
|
87
|
-
# a threads, per storage account name
|
88
|
-
storage_recovery_thread( storage_account_name, queues[storage_account_name], method, failure_reason )
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.launch_storage_recovery_table_threads ( state )
|
93
|
-
@@configuration[:storage_account_name_key].each do |storage_account_name, storage_account_keys|
|
94
|
-
recovery_table_thread( storage_account_name, state)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
#return thread
|
99
|
-
def self.recovery_table_thread( storage_account_name, state )
|
100
|
-
Thread.new( storage_account_name, state ) do |storage_account_name, state|
|
101
|
-
|
102
|
-
blob = Blob.new
|
103
|
-
|
104
|
-
committed_tuples = [ ]
|
105
|
-
uncommitted_tuples = [ ]
|
106
|
-
upload_empty_tuples = [ ]
|
107
|
-
token = nil
|
108
|
-
finished = false
|
109
|
-
filter = "#{:PartitionKey} eq '#{@@configuration[:azure_storage_blob_prefix]}-#{state}'"
|
110
|
-
|
111
|
-
# should exit thread after fetching data from table, and submit recovery, the loop is only for case of failure
|
112
|
-
until finished || stopped? do
|
113
|
-
entities = blob.log_to_table_query( storage_account_name, filter, token )
|
114
|
-
if entities
|
115
|
-
token = entities.continuation_token
|
116
|
-
|
117
|
-
if :committed == state
|
118
|
-
entities.each do |entity|
|
119
|
-
State.instance.inc_pending_notifications
|
120
|
-
tuple = blob.table_entity_to_tuple( entity.properties )
|
121
|
-
@@failed_on_notification_endpoint_retry_Q << tuple
|
122
|
-
end
|
123
|
-
|
124
|
-
elsif :uploading == state
|
125
|
-
# first tuples are collected, before send to queues, to make sure blob states don't change in between
|
126
|
-
entities.each do |entity|
|
127
|
-
typed_tuple = nil
|
128
|
-
until typed_tuple || stopped?
|
129
|
-
typed_tuple = blob.update_commited_or_uncommited_list( entity.properties )
|
130
|
-
Stud.stoppable_sleep(60, 1) { stopped? } unless typed_tuple
|
131
|
-
end
|
132
|
-
next if stopped?
|
133
|
-
|
134
|
-
if typed_tuple[:committed]
|
135
|
-
committed_tuples << typed_tuple[:committed]
|
136
|
-
elsif typed_tuple[:uncommitted]
|
137
|
-
uncommitted_tuples << typed_tuple[:uncommitted]
|
138
|
-
else
|
139
|
-
upload_empty_tuples << typed_tuple[:upload_empty]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
next if token
|
145
|
-
committed_tuples.each do |tuple|
|
146
|
-
State.instance.inc_pending_commits
|
147
|
-
@@failed_on_log_to_table_retry_Qs[storage_account_name] << tuple
|
148
|
-
end
|
149
|
-
uncommitted_tuples.each do |tuple|
|
150
|
-
State.instance.inc_pending_commits
|
151
|
-
@@failed_on_commit_retry_Qs[storage_account_name] << tuple
|
152
|
-
end
|
153
|
-
upload_empty_tuples.each do |tuple|
|
154
|
-
@@failed_on_log_to_table_retry_Qs[storage_account_name] << tuple
|
155
|
-
end
|
156
|
-
finished = true
|
157
|
-
else
|
158
|
-
Stud.stoppable_sleep(60, 1) { stopped? }
|
159
|
-
end
|
160
|
-
end
|
161
|
-
@@logger.info { "exit table recovery thread, storage: #{storage_account_name}, state: #{state}, entities: #{entities ? entities.length : nil}" }
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def self.state_on? ( storage_account_name, blob, failure_reason )
|
166
|
-
if blob
|
167
|
-
if :io_failure == failure_reason
|
168
|
-
@@endpoint_state_on ||= blob.test_notification_endpoint( @@configuration[:storage_account_name_key][0][0] )
|
169
|
-
else
|
170
|
-
Clients.instance.storage_account_state_on?( storage_account_name )
|
171
|
-
end
|
172
|
-
elsif storage_account_name
|
173
|
-
Clients.instance.storage_account_state_on?( storage_account_name )
|
174
|
-
else
|
175
|
-
Clients.instance.storage_account_state_on?
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def self.storage_recovery_thread( storage_account_name, queue, method, failure_reason )
|
180
|
-
# a threads, per storage account name, that retries failed blob commits / notification / table updates
|
181
|
-
Thread.new( storage_account_name, queue, method, failure_reason ) do |storage_account_name, queue, method, failure_reason|
|
182
|
-
blob = Blob.new if :notify == method
|
183
|
-
semaphore = Mutex.new
|
184
|
-
action = {:method => method, :semaphore => semaphore, :counter => 0 }
|
185
|
-
loop do
|
186
|
-
tuple ||= queue.pop
|
187
|
-
until state_on?( storage_account_name, blob, failure_reason ) do sleep( 1 ) end
|
188
|
-
|
189
|
-
not_busy = nil
|
190
|
-
semaphore.synchronize {
|
191
|
-
not_busy = action[:counter] += 1 if 10 > action[:counter]
|
192
|
-
}
|
193
|
-
if not_busy
|
194
|
-
Thread.new( action, tuple ) do |action, tuple|
|
195
|
-
Blob.new.send( action[:method], tuple )
|
196
|
-
action[:semaphore].synchronize {
|
197
|
-
action[:counter] -= 1
|
198
|
-
}
|
199
|
-
end
|
200
|
-
tuple = nil # release for GC
|
201
|
-
else
|
202
|
-
Stud.stoppable_sleep(60, 1) { 10 > action[:counter] }
|
203
|
-
next
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
def self.validate_endpoint
|
210
|
-
io = Blob.new
|
211
|
-
raise ConfigurationError, "Failed to access application insights #{@@configuration[:notification_endpoint]}, due to error #{io.last_io_exception.inspect}" unless io.test_notification_endpoint( @@configuration[:storage_account_name_key][0][0] )
|
212
|
-
end
|
213
|
-
|
214
|
-
def self.validate_storage
|
215
|
-
io = Blob.new
|
216
|
-
@@configuration[:storage_account_name_key].each do |storage_account_name, storage_account_keys|
|
217
|
-
raise ConfigurationError, "Failed access azure storage account #{storage_account_name}, due to error #{io.last_io_exception.inspect}" unless io.test_storage( storage_account_name )
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
|
222
49
|
def initialize ( channel = nil, id = nil , no_queue = false )
|
50
|
+
@configuration = Config.current
|
51
|
+
@logger = @configuration[:logger]
|
52
|
+
@storage_recovery = Storage_recovery.instance
|
53
|
+
@notification_recovery = Notification_recovery.instance
|
54
|
+
@max_tries = @configuration[:io_max_retries] + 1
|
55
|
+
|
223
56
|
@uploaded_block_ids = [ ]
|
224
57
|
@uploaded_block_numbers = [ ]
|
225
58
|
@uploaded_bytesize = 0
|
226
59
|
@uploaded_events_count = 0
|
227
|
-
@max_tries = @@io_max_retries + 1
|
228
60
|
@sub_state = :none
|
229
61
|
|
230
62
|
if channel
|
231
63
|
@id = id
|
232
|
-
@
|
64
|
+
@instrumentation_key = channel.instrumentation_key
|
233
65
|
@table_id = channel.table_id
|
234
66
|
@blob_max_delay = channel.blob_max_delay
|
235
67
|
|
@@ -273,14 +105,13 @@ class LogStash::Outputs::Application_insights
|
|
273
105
|
|
274
106
|
unless to_commit
|
275
107
|
@timer.set( block_to_upload.oldest_event_time + @blob_max_delay, nil ) {|object| @io_queue << :wakeup if 0 == @io_queue.length } if blob_empty?
|
276
|
-
|
277
|
-
upload( block_to_upload, to_commit)
|
108
|
+
upload( block_to_upload )
|
278
109
|
block_to_upload = nil # release reference to resource for GC
|
279
|
-
|
280
|
-
commit unless @uploaded_block_ids.empty?
|
110
|
+
to_commit = :commit if blob_full?
|
281
111
|
end
|
282
112
|
|
283
113
|
if to_commit
|
114
|
+
commit unless @uploaded_block_ids.empty?
|
284
115
|
to_commit = nil
|
285
116
|
@uploaded_block_ids = [ ]
|
286
117
|
@timer.cancel
|
@@ -302,9 +133,9 @@ class LogStash::Outputs::Application_insights
|
|
302
133
|
|
303
134
|
def blob_full? ( next_block = nil )
|
304
135
|
if next_block
|
305
|
-
BLOB_MAX_BLOCKS < @uploaded_block_ids.length + 1 ||
|
136
|
+
BLOB_MAX_BLOCKS < @uploaded_block_ids.length + 1 || @configuration[:blob_max_events] < @uploaded_events_count + next_block.events_count || @configuration[:blob_max_bytesize] < @uploaded_bytesize + next_block.bytesize
|
306
137
|
else
|
307
|
-
BLOB_MAX_BLOCKS <= @uploaded_block_ids.length ||
|
138
|
+
BLOB_MAX_BLOCKS <= @uploaded_block_ids.length || @configuration[:blob_max_events] <= @uploaded_events_count || @configuration[:blob_max_bytesize] <= @uploaded_bytesize
|
308
139
|
end
|
309
140
|
end
|
310
141
|
|
@@ -334,7 +165,7 @@ class LogStash::Outputs::Application_insights
|
|
334
165
|
end
|
335
166
|
|
336
167
|
def table_entity_to_tuple( options = {} )
|
337
|
-
[ options[:start_time.to_s] || Time.now.utc, options[:action.to_s], options[:
|
168
|
+
[ options[:start_time.to_s] || Time.now.utc, options[:action.to_s], options[:instrumentation_key.to_s], options[:table_id.to_s],
|
338
169
|
options[:storage_account_name.to_s], options[:container_name.to_s], options[:blob_name.to_s],
|
339
170
|
eval( options[:uploaded_block_ids.to_s] ), eval( options[:uploaded_block_numbers.to_s] ),
|
340
171
|
options[:uploaded_events_count.to_s] || 0, options[:uploaded_bytesize.to_s] || 0, options[:oldest_event_time.to_s] || Time.now.utc,
|
@@ -344,7 +175,7 @@ class LogStash::Outputs::Application_insights
|
|
344
175
|
end
|
345
176
|
|
346
177
|
def state_to_tuple
|
347
|
-
[ @start_time || Time.now.utc, @action, @
|
178
|
+
[ @start_time || Time.now.utc, @action, @instrumentation_key, @table_id,
|
348
179
|
@storage_account_name, @container_name, @blob_name,
|
349
180
|
@uploaded_block_ids, @uploaded_block_numbers,
|
350
181
|
@uploaded_events_count, @uploaded_bytesize, @oldest_event_time,
|
@@ -354,7 +185,7 @@ class LogStash::Outputs::Application_insights
|
|
354
185
|
end
|
355
186
|
|
356
187
|
def tuple_to_state ( tuple )
|
357
|
-
( @start_time, @action, @
|
188
|
+
( @start_time, @action, @instrumentation_key, @table_id,
|
358
189
|
@storage_account_name, @container_name, @blob_name,
|
359
190
|
@uploaded_block_ids, @uploaded_block_numbers,
|
360
191
|
@uploaded_events_count, @uploaded_bytesize, @oldest_event_time,
|
@@ -363,7 +194,7 @@ class LogStash::Outputs::Application_insights
|
|
363
194
|
end
|
364
195
|
|
365
196
|
def state_to_table_entity
|
366
|
-
{ :start_time => @start_time, :
|
197
|
+
{ :start_time => @start_time, :instrumentation_key => @instrumentation_key, :table_id => @table_id,
|
367
198
|
:storage_account_name => @storage_account_name, :container_name => @container_name, :blob_name => @blob_name,
|
368
199
|
:uploaded_block_ids => @uploaded_block_ids.to_s, :uploaded_block_numbers => @uploaded_block_numbers.to_s,
|
369
200
|
:uploaded_events_count => @uploaded_events_count, :uploaded_bytesize => @uploaded_bytesize, :oldest_event_time => @oldest_event_time,
|
@@ -372,42 +203,32 @@ class LogStash::Outputs::Application_insights
|
|
372
203
|
end
|
373
204
|
|
374
205
|
|
375
|
-
def test_storage_recover
|
376
|
-
proc do |reason, e| @recovery = :ok if :container_exist == reason || :create_container == reason end
|
377
|
-
end
|
378
|
-
|
379
|
-
|
380
206
|
def test_storage ( storage_account_name )
|
381
207
|
@storage_account_name = storage_account_name
|
382
208
|
@action = :test_storage
|
383
209
|
@max_tries = 1
|
384
210
|
@force_client = true # to enable get a client even if all storage_accounts marked dead
|
385
|
-
@recoverable = [ :invalid_storage_key ]
|
386
|
-
storage_io_block
|
211
|
+
@recoverable = [ :invalid_storage_key, :container_exist, :create_container ]
|
212
|
+
storage_io_block {
|
387
213
|
if @recovery.nil? || :invalid_storage_key == @recovery
|
388
|
-
container_name = "logstash-test-container"
|
389
214
|
@info = "#{@action} #{@storage_account_name}"
|
390
|
-
@client.blobClient.create_container(
|
215
|
+
@client.blobClient.create_container( @configuration[:test_storage_container] ) unless @configuration[:disable_blob_upload]
|
391
216
|
end
|
392
217
|
}
|
393
218
|
end
|
394
219
|
|
395
|
-
def
|
396
|
-
proc do |reason, e| @recovery = :ok if :invalid_intrumentation_key == reason || :invalid_table_id == reason end
|
397
|
-
end
|
398
|
-
|
399
|
-
def test_notification_endpoint( storage_account_name )
|
220
|
+
def test_notification( storage_account_name )
|
400
221
|
@storage_account_name = storage_account_name
|
401
|
-
@action = :
|
222
|
+
@action = :test_notification
|
402
223
|
@max_tries = 1
|
403
224
|
@force_client = true # to enable get a client even if all storage_accounts marked dead
|
404
|
-
@recoverable = [
|
405
|
-
success = storage_io_block
|
225
|
+
@recoverable = [ :invalid_instrumentation_key, :invalid_table_id ]
|
226
|
+
success = storage_io_block {
|
406
227
|
if @recovery.nil?
|
407
228
|
@container_name = "logstash-test-container"
|
408
229
|
@blob_name = "logstash-test-blob"
|
409
230
|
@table_id = GUID_NULL
|
410
|
-
@
|
231
|
+
@instrumentation_key = GUID_NULL
|
411
232
|
@info = "#{@action}"
|
412
233
|
set_blob_sas_url
|
413
234
|
payload = create_payload
|
@@ -419,24 +240,21 @@ class LogStash::Outputs::Application_insights
|
|
419
240
|
end
|
420
241
|
|
421
242
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
Channels.instance.channel( @intrumentation_key, @table_id ).failed_on_notify_retry_Q << state_to_tuple
|
243
|
+
def notify_retry_later
|
244
|
+
if :notify_failed_blob_not_accessible == @recovery
|
245
|
+
@sub_state = @recovery
|
246
|
+
@storage_recovery.recover_later( state_to_tuple, :notify, @storage_account_name )
|
247
|
+
elsif :invalid_instrumentation_key == @recovery || :invalid_table_id == @recovery
|
248
|
+
@sub_state = @recovery
|
249
|
+
Channels.instance.channel( @instrumentation_key, @table_id ).failed_on_notify_retry_Q << state_to_tuple
|
430
250
|
|
251
|
+
else
|
252
|
+
if :notify_failed_blob_not_accessible == @sub_state
|
253
|
+
@storage_recovery.recover_later( state_to_tuple, :notify, @storage_account_name )
|
254
|
+
elsif :invalid_instrumentation_key == @sub_state || :invalid_table_id == @sub_state
|
255
|
+
Channels.instance.channel( @instrumentation_key, @table_id ).failed_on_notify_retry_Q << state_to_tuple
|
431
256
|
else
|
432
|
-
|
433
|
-
if :notify_failed_blob_not_accessible == @sub_state
|
434
|
-
@@failed_on_notify_retry_Qs[@storage_account_name] << state_to_tuple
|
435
|
-
elsif :invalid_intrumentation_key == @sub_state || :invalid_table_id == @sub_state
|
436
|
-
Channels.instance.channel( @intrumentation_key, @table_id ).failed_on_notify_retry_Q << state_to_tuple
|
437
|
-
else
|
438
|
-
@@failed_on_notification_endpoint_retry_Q << state_to_tuple
|
439
|
-
end
|
257
|
+
@notification_recovery.recover_later( state_to_tuple )
|
440
258
|
end
|
441
259
|
end
|
442
260
|
end
|
@@ -446,32 +264,36 @@ class LogStash::Outputs::Application_insights
|
|
446
264
|
@action = :notify
|
447
265
|
@force_client = true # to enable get a client even if all storage_accounts marked dead
|
448
266
|
@recoverable = [ :notify_failed_blob_not_accessible, :io_failure, :service_unavailable ]
|
449
|
-
success = storage_io_block
|
267
|
+
success = storage_io_block {
|
450
268
|
set_blob_sas_url
|
451
269
|
payload = create_payload
|
452
|
-
|
270
|
+
@logger.debug { "notification payload: #{payload}" }
|
453
271
|
@info = "#{@action.to_s} #{@storage_account_name}/#{@container_name}/#{@blob_name}, events: #{@uploaded_events_count}, size: #{@uploaded_bytesize}, blocks: #{@uploaded_block_numbers}, delay: #{Time.now.utc - @oldest_event_time}, blob_sas_url: #{@blob_sas_url}"
|
454
272
|
|
455
273
|
# assume that exceptions can be raised due to this method:
|
456
|
-
post_notification( @client.notifyClient, payload ) unless
|
274
|
+
post_notification( @client.notifyClient, payload ) unless @configuration[:disable_notification]
|
457
275
|
@log_state = :notified
|
458
276
|
}
|
459
|
-
|
277
|
+
if success
|
278
|
+
state_table_update
|
279
|
+
else
|
280
|
+
notify_retry_later
|
281
|
+
end
|
460
282
|
end
|
461
283
|
|
462
284
|
CREATE_EXIST_ERRORS = { :container => [ :create_container, :container_exist ], :table => [ :create_table, :table_exist ] }
|
463
285
|
def create_exist_recovery( type, name = nil )
|
464
286
|
prev_info = @info
|
465
287
|
if CREATE_EXIST_ERRORS[type][0] == @recovery
|
466
|
-
name ||= ( :table == type ?
|
288
|
+
name ||= ( :table == type ? @configuration[:state_table_name] : @container_name )
|
467
289
|
@info = "create #{type} #{@storage_account_name}/#{name}"
|
468
290
|
|
469
291
|
# assume that exceptions can be raised due to this method:
|
470
292
|
yield name
|
471
|
-
|
293
|
+
@logger.info { "Successed to #{@info}" }
|
472
294
|
@info = prev_info
|
473
295
|
elsif CREATE_EXIST_ERRORS[type][1] == @recovery
|
474
|
-
|
296
|
+
@logger.info { "Successed (already exist) to #{@info}" }
|
475
297
|
@info = prev_info
|
476
298
|
end
|
477
299
|
end
|
@@ -485,39 +307,38 @@ class LogStash::Outputs::Application_insights
|
|
485
307
|
end
|
486
308
|
|
487
309
|
# return true on success
|
488
|
-
def
|
489
|
-
@action = :
|
310
|
+
def state_table_insert
|
311
|
+
@action = :state_table_insert
|
490
312
|
@recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :table_exist, :create_table, :table_busy, :entity_exist ]
|
491
313
|
@info = "#{@action} #{@log_state} #{@storage_account_name}/#{@container_name}/#{@blob_name}"
|
492
|
-
success = storage_io_block
|
314
|
+
success = storage_io_block {
|
493
315
|
create_table_exist_recovery
|
494
316
|
if :entity_exist == @recovery
|
495
317
|
raise NotRecoverableError if :uploading == @log_state
|
496
318
|
else
|
497
319
|
entity_values = state_to_table_entity
|
498
|
-
entity_values[:PartitionKey] = "#{
|
499
|
-
entity_values[:RowKey] = @blob_name
|
500
|
-
@client.tableClient.insert_entity(
|
320
|
+
entity_values[:PartitionKey] = "#{@configuration[:partition_key_prefix]}-#{@log_state}"
|
321
|
+
entity_values[:RowKey] = @blob_name.gsub("/","_")
|
322
|
+
@client.tableClient.insert_entity( @configuration[:state_table_name], entity_values )
|
501
323
|
end
|
502
324
|
}
|
325
|
+
@storage_recovery.recover_later( state_to_tuple, :state_table_update, @storage_account_name ) unless success || :uploading == @log_state
|
326
|
+
success
|
503
327
|
end
|
504
328
|
|
505
|
-
def
|
506
|
-
proc do |reason, e| @@failed_on_log_to_table_retry_Qs[@storage_account_name] << state_to_tuple end
|
507
|
-
end
|
508
|
-
|
509
|
-
def log_to_table_update ( tuple = nil )
|
329
|
+
def state_table_update ( tuple = nil )
|
510
330
|
tuple_to_state( tuple ) if tuple
|
511
331
|
if :uploading == @log_state
|
512
|
-
|
332
|
+
state_table_delete
|
513
333
|
elsif :committed == @log_state
|
514
|
-
if
|
334
|
+
if state_table_insert && state_table_delete( nil, :uploading )
|
515
335
|
State.instance.dec_pending_commits
|
516
336
|
State.instance.inc_pending_notifications
|
517
|
-
|
337
|
+
# this is not a recovery, it is actually enqueue to notify
|
338
|
+
@notification_recovery.enqueue( state_to_tuple )
|
518
339
|
end
|
519
340
|
elsif :notified == @log_state
|
520
|
-
if (
|
341
|
+
if (!@configuration[:save_notified_blobs_records] || state_table_insert) && state_table_delete( nil, :committed )
|
521
342
|
State.instance.dec_pending_notifications
|
522
343
|
end
|
523
344
|
end
|
@@ -525,81 +346,81 @@ class LogStash::Outputs::Application_insights
|
|
525
346
|
|
526
347
|
|
527
348
|
# retturn tru on success
|
528
|
-
def
|
349
|
+
def state_table_delete ( tuple = nil, state = nil )
|
529
350
|
tuple_to_state( tuple ) if tuple
|
530
351
|
state ||= @log_state
|
531
|
-
@action = :
|
352
|
+
@action = :state_table_delete
|
532
353
|
@recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :table_exist, :create_table, :table_busy, :create_resource ]
|
533
354
|
@info = "#{@action} #{state} #{@storage_account_name}/#{@container_name}/#{@blob_name}"
|
534
355
|
|
535
|
-
success = storage_io_block
|
356
|
+
success = storage_io_block {
|
536
357
|
create_table_exist_recovery
|
537
358
|
if :create_resource == @recovery
|
538
|
-
|
359
|
+
@logger.info { "Note: delete entity failed, already deleted, #{@info}, state: #{state}, log_state: #{@log_state}" }
|
539
360
|
else
|
540
|
-
@client.tableClient.delete_entity(
|
361
|
+
@client.tableClient.delete_entity( @configuration[:state_table_name], "#{@configuration[:partition_key_prefix]}-#{state}", @blob_name.gsub( "/", "_" ) )
|
541
362
|
end
|
542
363
|
}
|
364
|
+
@storage_recovery.recover_later( state_to_tuple, :state_table_update, @storage_account_name ) unless success
|
365
|
+
success
|
543
366
|
end
|
544
367
|
|
545
368
|
# return entities
|
546
|
-
def
|
369
|
+
def state_table_query ( storage_account_name, filter , token )
|
547
370
|
@storage_account_name = storage_account_name
|
548
371
|
|
549
|
-
@action = :
|
372
|
+
@action = :state_table_query
|
550
373
|
@recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :table_exist, :create_table, :table_busy ]
|
551
|
-
@info = "#{@action} #{@storage_account_name}/#{
|
374
|
+
@info = "#{@action} #{@storage_account_name}/#{@configuration[:state_table_name]}"
|
552
375
|
|
553
376
|
entities = nil
|
554
|
-
success = storage_io_block
|
377
|
+
success = storage_io_block {
|
555
378
|
create_table_exist_recovery
|
556
379
|
options = { :filter => filter }
|
557
380
|
options[:continuation_token] = token if token
|
558
|
-
entities = @client.tableClient.query_entities(
|
381
|
+
entities = @client.tableClient.query_entities( @configuration[:state_table_name], options )
|
559
382
|
}
|
560
383
|
entities
|
561
384
|
end
|
562
385
|
|
563
|
-
def commit_recover
|
564
|
-
proc do |reason, e| @@failed_on_commit_retry_Qs[@storage_account_name] << state_to_tuple end
|
565
|
-
end
|
566
|
-
|
567
386
|
def commit ( tuple = nil )
|
568
387
|
tuple_to_state( tuple ) if tuple
|
569
388
|
|
570
389
|
unless @uploaded_block_ids.empty?
|
571
390
|
@action = :commit
|
572
391
|
@recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable ]
|
573
|
-
success = storage_io_block
|
392
|
+
success = storage_io_block {
|
574
393
|
@info = "#{@action.to_s} #{@storage_account_name}/#{@container_name}/#{@blob_name}, events: #{@uploaded_events_count}, size: #{@uploaded_bytesize}, blocks: #{@uploaded_block_numbers}, delay: #{Time.now.utc - @oldest_event_time}"
|
575
394
|
# assume that exceptions can be raised due to this method:
|
576
|
-
@client.blobClient.commit_blob_blocks( @container_name, @blob_name, @uploaded_block_ids ) unless
|
395
|
+
@client.blobClient.commit_blob_blocks( @container_name, @blob_name, @uploaded_block_ids ) unless @configuration[:disable_blob_upload]
|
577
396
|
@log_state = :committed
|
578
397
|
}
|
579
|
-
|
580
|
-
|
398
|
+
if success
|
399
|
+
# next stage
|
400
|
+
state_table_update
|
401
|
+
else
|
402
|
+
@storage_recovery.recover_later( state_to_tuple, :commit, @storage_account_name )
|
403
|
+
end
|
581
404
|
end
|
582
405
|
end
|
583
406
|
|
584
407
|
|
585
|
-
def
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
@block_to_upload = nil
|
598
|
-
end
|
408
|
+
def upload_retry_later
|
409
|
+
unless @uploaded_block_ids.empty?
|
410
|
+
info1 = "#{:commit} #{@storage_account_name}/#{@container_name}/#{@blob_name}, events: #{@uploaded_events_count}, size: #{@uploaded_bytesize}, blocks: #{@uploaded_block_numbers}, delay: #{Time.now.utc - @oldest_event_time}"
|
411
|
+
@logger.error { "Pospone to #{info1} (; retry later, error: #{@last_io_exception.inspect}" }
|
412
|
+
@storage_recovery.recover_later( state_to_tuple, :commit, @storage_account_name )
|
413
|
+
@uploaded_block_ids = [ ]
|
414
|
+
end
|
415
|
+
unless :io_all_dead == @recovery
|
416
|
+
raise UploadRetryError
|
417
|
+
else
|
418
|
+
Channels.instance.channel( @instrumentation_key, @table_id ).failed_on_upload_retry_Q << @block_to_upload
|
419
|
+
@block_to_upload = nil
|
599
420
|
end
|
600
421
|
end
|
601
422
|
|
602
|
-
def upload ( block
|
423
|
+
def upload ( block )
|
603
424
|
@storage_account_name = nil if @uploaded_block_ids.empty?
|
604
425
|
@block_to_upload = block
|
605
426
|
block = nil # remove reference for GC
|
@@ -615,7 +436,7 @@ class LogStash::Outputs::Application_insights
|
|
615
436
|
# remove record of previous upload that failed
|
616
437
|
if @storage_account_name
|
617
438
|
exclude_storage_account_names << @storage_account_name
|
618
|
-
|
439
|
+
@storage_recovery.recover_later( state_to_tuple, :state_table_update, @storage_account_name )
|
619
440
|
end
|
620
441
|
set_conatainer_and_blob_names
|
621
442
|
@storage_account_name = Clients.instance.get_random_active_storage( exclude_storage_account_names )
|
@@ -623,7 +444,7 @@ class LogStash::Outputs::Application_insights
|
|
623
444
|
upload_recover.call( :io_all_dead, nil )
|
624
445
|
return false
|
625
446
|
end
|
626
|
-
raise UploadRetryError unless
|
447
|
+
raise UploadRetryError unless state_table_insert
|
627
448
|
end
|
628
449
|
|
629
450
|
@action = :upload
|
@@ -631,12 +452,12 @@ class LogStash::Outputs::Application_insights
|
|
631
452
|
@info = "#{@action} #{@storage_account_name}/#{@container_name}/#{@blob_name}, #{@block_info}, commitId: [\"#{100001 + @uploaded_block_ids.length}\"]"
|
632
453
|
@recoverable = [ :invalid_storage_key, :invalid_storage_account, :io_failure, :service_unavailable, :container_exist, :create_container ]
|
633
454
|
|
634
|
-
success = storage_io_block
|
455
|
+
success = storage_io_block {
|
635
456
|
create_container_exist_recovery
|
636
457
|
block_id = "#{100001 + @uploaded_block_ids.length}"
|
637
458
|
|
638
459
|
# assume that exceptions can be raised due to this method:
|
639
|
-
@client.blobClient.put_blob_block( @container_name, @blob_name, block_id, @block_to_upload.bytes ) unless
|
460
|
+
@client.blobClient.put_blob_block( @container_name, @blob_name, block_id, @block_to_upload.bytes ) unless @configuration[:disable_blob_upload]
|
640
461
|
|
641
462
|
# upload success
|
642
463
|
first_block_in_blob = @uploaded_block_ids.empty?
|
@@ -655,8 +476,7 @@ class LogStash::Outputs::Application_insights
|
|
655
476
|
Telemetry.instance.track_event("uploading", {:properties => state_to_table_entity})
|
656
477
|
}
|
657
478
|
|
658
|
-
|
659
|
-
commit if success && to_commit
|
479
|
+
upload_retry_later unless success
|
660
480
|
rescue UploadRetryError
|
661
481
|
@recovery = nil
|
662
482
|
retry
|
@@ -670,7 +490,7 @@ class LogStash::Outputs::Application_insights
|
|
670
490
|
@action = :list_blob_blocks
|
671
491
|
@recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :container_exist, :create_container, :create_blob ]
|
672
492
|
list_blob_blocks = nil
|
673
|
-
success = storage_io_block
|
493
|
+
success = storage_io_block {
|
674
494
|
@info = "#{@action} #{@storage_account_name}/#{@container_name}/#{@blob_name}"
|
675
495
|
|
676
496
|
create_container_exist_recovery
|
@@ -705,15 +525,15 @@ class LogStash::Outputs::Application_insights
|
|
705
525
|
private
|
706
526
|
|
707
527
|
|
708
|
-
def storage_io_block
|
528
|
+
def storage_io_block
|
709
529
|
@recovery = nil
|
710
530
|
@try_count = 1
|
711
531
|
|
712
532
|
begin
|
713
533
|
@client ||= Client.new( @storage_account_name, @force_client )
|
714
534
|
yield
|
715
|
-
disabled = :notify == @action ?
|
716
|
-
|
535
|
+
disabled = :notify == @action ? @configuration[:disable_notification] : @configuration[:disable_blob_upload]
|
536
|
+
@logger.info { "Successed to #{disabled ? 'DISABLED ' : ''}#{@info}" }
|
717
537
|
true
|
718
538
|
|
719
539
|
rescue TypeError
|
@@ -721,8 +541,13 @@ class LogStash::Outputs::Application_insights
|
|
721
541
|
|
722
542
|
rescue StandardError => e
|
723
543
|
@last_io_exception = e
|
724
|
-
@recovery =
|
725
|
-
retry if
|
544
|
+
@recovery, reason = recover_retry?( e )
|
545
|
+
retry if @recovery || reason.nil?
|
546
|
+
|
547
|
+
puts " +++ recovery: #{@recovery}, reason: #{reason}"
|
548
|
+
|
549
|
+
@recovery = reason
|
550
|
+
@logger.error { "Failed to #{@info} ; retry later, error: #{e.inspect}" }
|
726
551
|
false
|
727
552
|
|
728
553
|
ensure
|
@@ -730,157 +555,173 @@ class LogStash::Outputs::Application_insights
|
|
730
555
|
end
|
731
556
|
end
|
732
557
|
|
733
|
-
|
734
|
-
def recover_retry? ( e, recover_later_proc )
|
735
|
-
# http error, probably server error
|
558
|
+
def error_to_sym ( e )
|
736
559
|
if e.is_a?( Azure::Core::Http::HTTPError )
|
560
|
+
if 404 == e.status_code
|
561
|
+
if "ContainerNotFound" == e.type
|
562
|
+
:create_container
|
737
563
|
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
elsif 404 == e.status_code && "TableNotFound" == e.type
|
742
|
-
@recovery = :create_table
|
564
|
+
elsif "TableNotFound" == e.type
|
565
|
+
:create_table
|
743
566
|
|
744
|
-
|
745
|
-
|
567
|
+
elsif "BlobNotFound" == e.type
|
568
|
+
:create_blob
|
746
569
|
|
747
|
-
|
748
|
-
|
570
|
+
elsif "ResourceNotFound" == e.type
|
571
|
+
:create_resource
|
749
572
|
|
750
|
-
|
751
|
-
|
573
|
+
else
|
574
|
+
:create_resource
|
575
|
+
end
|
752
576
|
|
753
|
-
elsif 409 == e.status_code
|
754
|
-
|
577
|
+
elsif 409 == e.status_code
|
578
|
+
if "ContainerAlreadyExists" == e.type
|
579
|
+
:container_exist
|
755
580
|
|
756
|
-
|
757
|
-
|
581
|
+
elsif "BlobAlreadyExists" == e.type
|
582
|
+
:blob_exist
|
758
583
|
|
759
|
-
|
760
|
-
|
584
|
+
elsif "TableAlreadyExists" == e.type
|
585
|
+
:table_exist
|
761
586
|
|
762
|
-
|
763
|
-
|
587
|
+
elsif "TableBeingDeleted" == e.type
|
588
|
+
:table_busy
|
764
589
|
|
765
|
-
|
766
|
-
|
590
|
+
elsif "EntityAlreadyExists" == e.type
|
591
|
+
:entity_exist
|
767
592
|
|
768
|
-
|
769
|
-
|
593
|
+
else
|
594
|
+
:http_unknown
|
595
|
+
end
|
596
|
+
|
597
|
+
elsif 403 == e.status_code
|
598
|
+
if "AuthenticationFailed" == e.type
|
599
|
+
:invalid_storage_key
|
600
|
+
|
601
|
+
elsif "Unknown" == e.type && e.description.include?("Blob does not exist or not accessible.")
|
602
|
+
:notify_failed_blob_not_accessible
|
603
|
+
|
604
|
+
else
|
605
|
+
:access_denied
|
606
|
+
end
|
770
607
|
|
771
608
|
elsif 400 == e.status_code && "Unknown" == e.type && e.description.include?("Invalid instrumentation key")
|
772
|
-
|
609
|
+
:invalid_instrumentation_key
|
773
610
|
|
774
611
|
elsif 500 == e.status_code && "Unknown" == e.type && e.description.include?("Processing error")
|
775
|
-
|
612
|
+
:notification_process_down
|
776
613
|
|
777
614
|
elsif 503 == e.status_code
|
778
|
-
|
779
|
-
|
780
|
-
@recovery = :create_resource
|
781
|
-
elsif 403 == e.status_code
|
782
|
-
# todo, came from updating the log_table, how to hnadle this
|
783
|
-
@recovery = :access_denied
|
615
|
+
:service_unavailable
|
616
|
+
|
784
617
|
else
|
785
|
-
|
786
|
-
@recovery = :http_unknown
|
787
|
-
raise e if @@configuration[:stop_on_unknown_io_errors]
|
618
|
+
:http_unknown
|
788
619
|
end
|
789
620
|
|
790
621
|
# communication error
|
791
622
|
elsif e.is_a?( Faraday::ClientError )
|
792
|
-
|
623
|
+
:io_failure
|
793
624
|
|
794
625
|
# communication error
|
795
626
|
elsif e.is_a?( IOError )
|
796
|
-
|
627
|
+
:io_failure
|
797
628
|
|
798
629
|
# all storage accounts are dead, couldn't get client (internal exception)
|
799
630
|
elsif e.is_a?( StorageAccountsOffError )
|
800
|
-
|
631
|
+
:io_all_dead
|
801
632
|
|
802
633
|
# all storage accounts are dead, couldn't get client (internal exception)
|
803
634
|
elsif e.is_a?( NotRecoverableError )
|
804
|
-
|
635
|
+
:not_recoverable
|
805
636
|
|
806
637
|
elsif e.is_a?( NameError ) && e.message.include?( "uninitialized constant Azure::Core::Auth::Signer::OpenSSL" )
|
807
|
-
|
808
|
-
@recovery = :io_failure
|
638
|
+
:init_error
|
809
639
|
|
810
640
|
elsif e.is_a?( NameError ) && e.message.include?( "uninitialized constant Azure::Storage::Auth::SharedAccessSignature" )
|
811
|
-
|
812
|
-
@recovery = :io_failure
|
641
|
+
:init_error
|
813
642
|
|
814
643
|
else
|
815
|
-
|
816
|
-
|
817
|
-
|
644
|
+
:unknown
|
645
|
+
end
|
646
|
+
end
|
818
647
|
|
648
|
+
|
649
|
+
def recover_retry? ( e )
|
650
|
+
recovery = error_to_sym( e )
|
651
|
+
if :init_error == recovery
|
652
|
+
@client = @client.dispose if @client
|
653
|
+
sleep( 1 )
|
654
|
+
recovery = nil
|
655
|
+
|
656
|
+
elsif :http_unknown == recovery || :unknown == recovery
|
657
|
+
puts "\n>>>> UNKNOWN error - #{e.inspect} <<<<\n"
|
658
|
+
raise e if @configuration[:stop_on_unknown_io_errors]
|
819
659
|
end
|
820
660
|
|
821
|
-
|
822
|
-
if @recovery && @recoverable.include?( @recovery )
|
823
|
-
case @recovery
|
824
|
-
when :container_exist, :table_exist, :entity_exist, :create_container, :create_table
|
825
|
-
# ignore log error
|
826
|
-
# @@logger.error { "Failed to #{@info} ;( recovery: continue, error: #{e.inspect}" }
|
661
|
+
return [recovery, recovery] unless recovery && @recoverable.include?( recovery )
|
827
662
|
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
@client = @client.dispose( :auth_to_storage_failed ) if @client && :invalid_storage_key == @recovery
|
833
|
-
@recovery = nil
|
834
|
-
end
|
663
|
+
case recovery
|
664
|
+
when :container_exist, :table_exist, :entity_exist, :create_container, :create_table
|
665
|
+
# ignore log error
|
666
|
+
# @logger.error { "Failed to #{@info} ;( recovery: continue, error: #{e.inspect}" }
|
835
667
|
|
836
|
-
|
668
|
+
when :invalid_storage_key, :notify_failed_blob_not_accessible
|
669
|
+
if @client.switch_storage_account_key!
|
670
|
+
@logger.error { "Failed to #{@info} ;( recovery: switched to secondary storage key, error: #{e.inspect}" }
|
671
|
+
else
|
672
|
+
@client = @client.dispose( :auth_to_storage_failed ) if @client && :invalid_storage_key == recovery
|
673
|
+
return [nil, recovery]
|
674
|
+
end
|
675
|
+
|
676
|
+
when :table_busy
|
677
|
+
@client = @client.dispose if @client
|
678
|
+
sleep( @configuration[:io_retry_delay] )
|
679
|
+
@logger.error { "Failed to #{@info} ;( recovery: retry, error: #{e.inspect}" }
|
680
|
+
|
681
|
+
when :io_failure, :service_unavailable, :notification_process_down
|
682
|
+
if @try_count < @max_tries
|
837
683
|
@client = @client.dispose if @client
|
838
|
-
sleep(
|
839
|
-
|
684
|
+
sleep( @configuration[:io_retry_delay] )
|
685
|
+
@logger.error { "Failed to #{@info} ;( recovery: retry, try #{@try_count} / #{@max_tries}, error: #{e.inspect}" }
|
686
|
+
@try_count += 1
|
687
|
+
else
|
688
|
+
if :io_failure == recovery || ( :service_unavailable == recovery && :notify != @action )
|
689
|
+
@client = @client.dispose( :io_to_storage_failed ) if @client
|
690
|
+
end
|
691
|
+
return [nil, recovery]
|
692
|
+
end
|
840
693
|
|
841
|
-
|
694
|
+
when :invalid_instrumentation_key, :invalid_table_id
|
695
|
+
if :notify == @action # only for notify, not for test endpoint
|
842
696
|
if @try_count < @max_tries
|
843
697
|
@client = @client.dispose if @client
|
844
|
-
sleep(
|
845
|
-
|
698
|
+
sleep( @configuration[:io_retry_delay] )
|
699
|
+
@logger.error { "Failed to #{@info} ;( recovery: retry, try #{@try_count} / #{@max_tries}, error: #{e.inspect}" }
|
846
700
|
@try_count += 1
|
847
701
|
else
|
848
|
-
if :
|
849
|
-
Channels.instance.
|
850
|
-
elsif :invalid_table_id ==
|
702
|
+
if :invalid_instrumentation_key == recovery
|
703
|
+
Channels.instance.mark_invalid_instrumentation_key( @instrumentation_key )
|
704
|
+
elsif :invalid_table_id == recovery
|
851
705
|
Channels.instance.mark_invalid_table_id( @table_id )
|
852
|
-
elsif :io_failure == @recovery || ( :service_unavailable == @recovery && :notify != @action )
|
853
|
-
@client = @client.dispose( :io_to_storage_failed ) if @client
|
854
706
|
end
|
855
|
-
|
707
|
+
return [nil, recovery]
|
856
708
|
end
|
857
709
|
end
|
858
|
-
else
|
859
|
-
@recovery = nil
|
860
710
|
end
|
711
|
+
[recovery, recovery]
|
712
|
+
end
|
861
713
|
|
862
|
-
if @recovery
|
863
|
-
true
|
864
|
-
else
|
865
|
-
recover_later_proc.call( reason, e )
|
866
|
-
@@logger.error { "Failed to #{@info} ; retry later, error: #{e.inspect}" } unless :ok == @recovery
|
867
|
-
:ok == @recovery
|
868
|
-
end
|
869
714
|
|
870
|
-
# Blob service error codes - msdn.microsoft.com/en-us/library/azure/dd179439.aspx
|
871
|
-
# ConnectionFailed - problem with connection
|
872
|
-
# ParsingError - problem with request/response payload
|
873
|
-
# ResourceNotFound, SSLError, TimeoutError
|
874
|
-
end
|
875
715
|
|
876
716
|
def set_conatainer_and_blob_names
|
877
717
|
time_utc = Time.now.utc
|
878
718
|
id = @id.to_s.rjust(4, "0")
|
879
719
|
strtime = time_utc.strftime( "%F" )
|
880
|
-
@container_name = "#{
|
720
|
+
@container_name = "#{AZURE_STORAGE_CONTAINER_LOGSTASH_PREFIX}#{@configuration[:azure_storage_container_prefix]}-#{strtime}"
|
881
721
|
|
882
722
|
strtime = time_utc.strftime( "%F-%H-%M-%S-%L" )
|
883
|
-
@blob_name = "#{
|
723
|
+
# @blob_name = "#{@configuration[:azure_storage_blob_prefix]}_ikey-#{@instrumentation_key}_table-#{@table_id}_id-#{id}_#{strtime}.#{@event_format_ext}"
|
724
|
+
@blob_name = "#{AZURE_STORAGE_BLOB_LOGSTASH_PREFIX}#{@configuration[:azure_storage_blob_prefix]}/ikey-#{@instrumentation_key}/table-#{@table_id}/#{strtime}_#{id}.#{@event_format_ext}"
|
884
725
|
end
|
885
726
|
|
886
727
|
|
@@ -892,30 +733,30 @@ class LogStash::Outputs::Application_insights
|
|
892
733
|
:ver => BASE_DATA_REQUIRED_VERSION,
|
893
734
|
:blobSasUri => @blob_sas_url.to_s,
|
894
735
|
:sourceName => @table_id,
|
895
|
-
:sourceVersion =>
|
736
|
+
:sourceVersion => @configuration[:notification_version].to_s
|
896
737
|
}
|
897
738
|
},
|
898
|
-
:ver =>
|
739
|
+
:ver => @configuration[:notification_version],
|
899
740
|
:name => REQUEST_NAME,
|
900
741
|
:time => Time.now.utc.iso8601,
|
901
|
-
:iKey => @
|
742
|
+
:iKey => @instrumentation_key
|
902
743
|
}
|
903
744
|
notification_hash.to_json
|
904
745
|
end
|
905
746
|
|
906
747
|
|
907
748
|
def post_notification ( http_client, body )
|
908
|
-
request = Azure::Core::Http::HttpRequest.new( :post,
|
749
|
+
request = Azure::Core::Http::HttpRequest.new( :post, @configuration[:application_insights_endpoint], { :body => body, :client => http_client } )
|
909
750
|
request.headers['Content-Type'] = 'application/json; charset=utf-8'
|
910
751
|
request.headers['Accept'] = 'application/json'
|
911
|
-
|
752
|
+
@logger.debug { "send notification : \n endpoint: #{@configuration[:application_insights_endpoint]}\n body : #{body}" }
|
912
753
|
response = request.call
|
913
754
|
end
|
914
755
|
|
915
756
|
|
916
757
|
def set_blob_sas_url
|
917
758
|
blob_url ="https://#{@storage_account_name}.blob.core.windows.net/#{@container_name}/#{@blob_name}"
|
918
|
-
options_and_constrains = {:permissions => "r", :resource => "b", :expiry => ( Time.now.utc +
|
759
|
+
options_and_constrains = {:permissions => "r", :resource => "b", :expiry => ( Time.now.utc + @configuration[:blob_access_expiry_time] ).iso8601 }
|
919
760
|
@blob_sas_url = @client.storage_auth_sas.signed_uri( URI( blob_url ), options_and_constrains )
|
920
761
|
end
|
921
762
|
|