logstash-output-application_insights 0.1.3

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.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CONTRIBUTORS +9 -0
  4. data/DEVELOPER.md +0 -0
  5. data/Gemfile +26 -0
  6. data/LICENSE +17 -0
  7. data/README.md +495 -0
  8. data/Rakefile +22 -0
  9. data/lib/logstash/outputs/application_insights.rb +393 -0
  10. data/lib/logstash/outputs/application_insights/blob.rb +923 -0
  11. data/lib/logstash/outputs/application_insights/block.rb +118 -0
  12. data/lib/logstash/outputs/application_insights/channel.rb +259 -0
  13. data/lib/logstash/outputs/application_insights/channels.rb +142 -0
  14. data/lib/logstash/outputs/application_insights/client.rb +110 -0
  15. data/lib/logstash/outputs/application_insights/clients.rb +113 -0
  16. data/lib/logstash/outputs/application_insights/config.rb +341 -0
  17. data/lib/logstash/outputs/application_insights/constants.rb +208 -0
  18. data/lib/logstash/outputs/application_insights/exceptions.rb +55 -0
  19. data/lib/logstash/outputs/application_insights/flow_control.rb +80 -0
  20. data/lib/logstash/outputs/application_insights/multi_io_logger.rb +69 -0
  21. data/lib/logstash/outputs/application_insights/shutdown.rb +96 -0
  22. data/lib/logstash/outputs/application_insights/state.rb +89 -0
  23. data/lib/logstash/outputs/application_insights/storage_cleanup.rb +214 -0
  24. data/lib/logstash/outputs/application_insights/sub_channel.rb +75 -0
  25. data/lib/logstash/outputs/application_insights/telemetry.rb +99 -0
  26. data/lib/logstash/outputs/application_insights/timer.rb +90 -0
  27. data/lib/logstash/outputs/application_insights/utils.rb +139 -0
  28. data/lib/logstash/outputs/application_insights/version.rb +24 -0
  29. data/logstash-output-application-insights.gemspec +50 -0
  30. data/spec/outputs/application_insights_spec.rb +42 -0
  31. metadata +151 -0
@@ -0,0 +1,89 @@
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
+
22
+ class LogStash::Outputs::Application_insights
23
+ class State
24
+
25
+ public
26
+
27
+ def initialize
28
+ @bytes_in_memory = Concurrent::AtomicFixnum.new(0)
29
+ @pending_commits = Concurrent::AtomicFixnum.new(0)
30
+ @pending_notifications = Concurrent::AtomicFixnum.new(0)
31
+ end
32
+
33
+
34
+ def bytes_in_memory
35
+ @bytes_in_memory.value
36
+ end
37
+
38
+
39
+ def pending_commits
40
+ @pending_commits.value
41
+ end
42
+
43
+
44
+ def pending_notifications
45
+ @pending_notifications.value
46
+ end
47
+
48
+
49
+ def inc_upload_bytesize ( bytesize )
50
+ @bytes_in_memory.increment( bytesize )
51
+ end
52
+
53
+
54
+ def dec_upload_bytesize ( bytesize )
55
+ @bytes_in_memory.decrement( bytesize )
56
+ end
57
+
58
+
59
+ def inc_pending_commits
60
+ @pending_commits.increment
61
+ end
62
+
63
+
64
+ def dec_pending_commits
65
+ @pending_commits.decrement
66
+ end
67
+
68
+
69
+ def inc_pending_notifications
70
+ @pending_notifications.increment
71
+ end
72
+
73
+
74
+ def dec_pending_notifications
75
+ @pending_notifications.decrement
76
+ end
77
+
78
+ public
79
+
80
+ @@instance = State.new
81
+
82
+ def self.instance
83
+ @@instance
84
+ end
85
+
86
+ private_class_method :new
87
+ end
88
+ end
89
+
@@ -0,0 +1,214 @@
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
+
22
+ class LogStash::Outputs::Application_insights
23
+ class Storage_cleanup < Blob
24
+
25
+ public
26
+
27
+ def self.start
28
+ configuration = Config.current
29
+ @disable_cleanup = configuration[:disable_cleanup]
30
+ @delete_not_notified_blobs = configuration[:delete_not_notified_blobs]
31
+
32
+ unless @disable_cleanup
33
+ @@list = [ ]
34
+ configuration[:storage_account_name_key].each do |storage_account_name, storage_account_keys|
35
+ @@list << Storage_cleanup.new( storage_account_name )
36
+ end
37
+ end
38
+ private_class_method :new
39
+ end
40
+
41
+
42
+ def initialize ( storage_account_name )
43
+ # super first parameter must be nil. blob first parameter is channel, otherwise it will pass storage_account_name as channel
44
+ super( nil )
45
+ @storage_account_name = storage_account_name
46
+ configuration = Config.current
47
+ @logger = configuration[:logger]
48
+ @azure_storage_container_prefix = configuration[:azure_storage_container_prefix]
49
+ @retention_time = configuration[:blob_retention_time] + 24 * 60 * 60
50
+ @not_notified_container = "#{configuration[:azure_storage_container_prefix]}-#{AZURE_STORAGE_ORPHAN_BLOBS_CONTAINER_NAME}"
51
+ # launch tread that cleans the storage
52
+ periodic_storage_cleanup
53
+ end
54
+
55
+
56
+ def periodic_storage_cleanup
57
+ Thread.new do
58
+ loop do
59
+ container_names = list_containers_to_cleanup
60
+ container_names.each do |container_name|
61
+ container_cleanup( container_name )
62
+ end
63
+ sleep( 60 * 60 )
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ # return list of containers ready to be cleaned up, return empty list in case failed to get list
70
+ def list_containers_to_cleanup
71
+ continuation_token = nil
72
+ container_names_to_delete = [ ]
73
+ begin
74
+ containers = list_container_names( @azure_storage_container_prefix, continuation_token)
75
+ break unless containers
76
+ token = containers.continuation_token
77
+ containers.each do |container|
78
+ expiration_time = Time.parse( container.properties[:last_modified] ) + @retention_time
79
+ container_names_to_delete << container.name if expiration_time <= Time.now.utc
80
+ end
81
+ end while continuation_token
82
+ container_names_to_delete
83
+ end
84
+
85
+
86
+ # return blob containers
87
+ def list_container_names ( azure_storage_container_prefix = nil, token = nil )
88
+ @action = :list_container_names
89
+ @recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable ]
90
+ @info = "#{@action} #{@storage_account_name}"
91
+
92
+ containers = nil
93
+ success = storage_io_block( proc do |reason, e| end ) {
94
+ options = { :metadata => true }
95
+ options[:marker] = token if token
96
+ options[:prefix] = azure_storage_container_prefix if azure_storage_container_prefix
97
+ containers = @client.blobClient.list_containers( options )
98
+ }
99
+ containers
100
+ end
101
+
102
+
103
+ def container_cleanup ( container_name )
104
+ unless @delete_not_notified_blobs
105
+ return unless copy_not_notified_blobs( container_name )
106
+ return unless delete_container_entities( container_name )
107
+ end
108
+ delete_container( container_name )
109
+ end
110
+
111
+
112
+ # return true if all notified entities were copied
113
+ def copy_not_notified_blobs( container_name )
114
+ pending = nil
115
+ continuation_token = nil
116
+ filter = "#{:container_name} eq '#{container_name}' and #{:log_state} ne '#{:notified}'"
117
+ begin
118
+ entities = log_to_table_query( @storage_account_name, filter , continuation_token )
119
+ return nil unless entities
120
+ token = entities.continuation_token
121
+ entities.each do |entity|
122
+ blob_name = entity.properties[:blob_name.to_s]
123
+ return nil unless ( status = not_notified_blob_copy_status( blob_name ) )
124
+ if :pending == status
125
+ pending = true
126
+ elsif :success != status
127
+ return nil unless (status = copy_not_notified_blob( container_name, blob_name ) )
128
+ pending = true unless :success == status
129
+ end
130
+ @logger.warn { "copied blob: #{@storage_account_name}/#{container_name}/#{blob_name} to #{@not_notified_container} container because cannot notify" } if :success == status
131
+ end
132
+ end while continuation_token
133
+ pending.nil?
134
+ end
135
+
136
+
137
+ # return status or nil if failed
138
+ def copy_not_notified_blob( container_name, blob_name )
139
+ @action = :copy_blob_to_not_notified_container
140
+ @recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :blob_exit, :create_container, :container_exist ]
141
+ @info = "#{@action} #{@storage_account_name}/#{container_name}/#{blob_name}"
142
+ tuple = nil
143
+ success = storage_io_block( proc do |reason, e| end ) {
144
+ create_exist_recovery( :container, @not_notified_container ) { |name| @client.blobClient.create_container( name ) }
145
+ if :blob_exit == @recovery
146
+ tuple = ["", :pending]
147
+ else
148
+ tuple = @client.blobClient.copy_blob(@not_notified_container, blob_name, container_name, blob_name)
149
+ end
150
+ }
151
+ tuple ? tuple[1].to_sym : nil
152
+ end
153
+
154
+ # return copy status, if failed return nil
155
+ def not_notified_blob_copy_status ( blob_name )
156
+ @action = :check_not_notified_blob_copy_status
157
+ @recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :create_resource, :create_container, :container_exist ]
158
+ @info = "#{@action} #{@storage_account_name}/#{@not_notified_container}/#{blob_name}"
159
+ status = nil
160
+ success = storage_io_block( proc do |reason, e| end ) {
161
+ create_exist_recovery( :container, @not_notified_container ) { |name| @client.blobClient.create_container( name ) }
162
+ if :create_resource == @recovery
163
+ status = :not_started
164
+ elsif
165
+ result = @client.blobClient.get_blob_properties( @not_notified_container, blob_name )
166
+ if result
167
+ properties = result.properties
168
+ status = ( properties[:copy_status] || :success).to_sym
169
+ end
170
+ end
171
+ }
172
+ status
173
+ end
174
+
175
+ # return true if all container entities were removed from log table
176
+ def delete_container_entities( container_name )
177
+ continuation_token = nil
178
+ filter = "#{:container_name} eq '#{container_name}'"
179
+ begin
180
+ entities = log_to_table_query( @storage_account_name, filter , continuation_token )
181
+ return nil unless entities
182
+ token = entities.continuation_token
183
+ entities.each do |entity|
184
+ return nil unless log_to_table_delete( table_entity_to_tuple( entity.properties ) )
185
+ end
186
+ end while continuation_token
187
+ true
188
+ end
189
+
190
+
191
+ # return true if container deleted
192
+ def delete_container ( container_name )
193
+ @action = :delete_container
194
+ @recoverable = [ :invalid_storage_key, :io_failure, :service_unavailable, :create_container ]
195
+ @info = "#{@action} #{@storage_account_name}/#{container_name}"
196
+
197
+ success = storage_io_block( proc do |reason, e| end ) {
198
+ # delete container, if not found, skip
199
+ containers = @client.blobClient.delete_container( container_name ) unless :create_container == @recovery
200
+ }
201
+ success
202
+ end
203
+
204
+ =begin
205
+ # singleton pattern
206
+ public
207
+ @@instance = Storage_cleanup.new
208
+ def self.instance
209
+ @@instance
210
+ end
211
+ private_class_method :new
212
+ =end
213
+ end
214
+ end
@@ -0,0 +1,75 @@
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
+
22
+ class LogStash::Outputs::Application_insights
23
+ class Sub_channel
24
+
25
+ public
26
+
27
+ def initialize ( event_separator )
28
+ @semaphore = Mutex.new
29
+ @event_separator = event_separator
30
+ @event_separator_bytesize = @event_separator.bytesize
31
+ reset!
32
+ end
33
+
34
+ def reset!
35
+ @bytesize = 0
36
+ @reported_bytesize = 0
37
+ @block_list = [ ]
38
+ @block = nil
39
+ end
40
+ REPORT_BYTESIZE = 250 * 1024
41
+
42
+ def << serialized_event
43
+ @semaphore.synchronize {
44
+ begin
45
+ @block_list << ( @block = Block.new( @event_separator ) ) unless @block
46
+ @block << serialized_event
47
+ @block = nil if @block.is_full?
48
+
49
+ @bytesize += ( serialized_event.bytesize + @event_separator_bytesize )
50
+ unreported_bytesize = @bytesize - @reported_bytesize
51
+ if unreported_bytesize > REPORT_BYTESIZE
52
+ State.instance.inc_upload_bytesize( unreported_bytesize )
53
+ @reported_bytesize = @bytesize
54
+ end
55
+ rescue BlockOverflowError
56
+ @block = nil
57
+ retry
58
+ rescue BlockTooSmallError
59
+ @@logger.error { "failed to receive event - " + "event too big" }
60
+ end
61
+ }
62
+ end
63
+
64
+ def get_block_list!
65
+ @semaphore.synchronize {
66
+ unreported_bytesize = @bytesize - @reported_bytesize
67
+ State.instance.inc_upload_bytesize( unreported_bytesize ) if unreported_bytesize > 0
68
+ block_list = @block_list
69
+ reset!
70
+ block_list
71
+ }
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,99 @@
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
+
22
+ class LogStash::Outputs::Application_insights
23
+ class Telemetry
24
+
25
+ attr_reader :telemetry_channel
26
+
27
+ public
28
+
29
+ LOGSTASH_TELEMETRY_INSTRUMENTATION_KEY = "52fe6bb7-5528-4b4e-b2cc-12ef128633b4"
30
+
31
+ def initialize
32
+ configuration = Config.current
33
+ @disable_telemetry = configuration[:disable_telemetry]
34
+ unless @disable_telemetry
35
+ @telemetry_channel = create_async_channel( LOGSTASH_TELEMETRY_INSTRUMENTATION_KEY )
36
+ set_async_channel_properties( @telemetry_channel )
37
+ set_channel_context( @telemetry_channel )
38
+ end
39
+ end
40
+
41
+
42
+ def create_async_channel ( ikey )
43
+ sender = ApplicationInsights::Channel::AsynchronousSender.new
44
+ queue = ApplicationInsights::Channel::AsynchronousQueue.new( sender )
45
+ channel = ApplicationInsights::Channel::TelemetryChannel.new( nil, queue )
46
+ ApplicationInsights::TelemetryClient.new( ikey, channel )
47
+ end
48
+
49
+ def set_async_channel_properties ( tc )
50
+ # flush telemetry if we have 10 or more telemetry items in our queue
51
+ tc.channel.queue.max_queue_length = 10
52
+ # send telemetry to the service in batches of 5
53
+ tc.channel.sender.send_buffer_size = 5
54
+ # the background worker thread will be active for 5 seconds before it shuts down. if
55
+ # during this time items are picked up from the queue, the timer is reset.
56
+ tc.channel.sender.send_time = 5
57
+ # the background worker thread will poll the queue every 0.5 seconds for new items
58
+ tc.channel.sender.send_interval = 0.5
59
+ end
60
+
61
+ def set_channel_context ( tc )
62
+ # tc.context.application.id = 'logstash-output-Application-Insights plugin'
63
+ tc.context.application.ver = VERSION
64
+ tc.context.application.build = LOGSTASH_CORE_VERSION
65
+ tc.context.device.id = Socket.gethostname.strip
66
+ # tc.context.device.oem_name = 'Asus'
67
+ # tc.context.device.model = 'X31A'
68
+ tc.context.device.type = Utils.os
69
+ # tc.context.user.id = 'santa@northpole.net'
70
+ end
71
+
72
+ def track_event ( name, options={} )
73
+ @telemetry_channel.track_event( name, options ) unless @disable_telemetry
74
+ end
75
+
76
+ def track_metric ( name, value, options={} )
77
+ @telemetry_channel.track_metric( name, value, options ) unless @disable_telemetry
78
+ end
79
+
80
+ def track_request (id, start_time, duration, response_code, success, options = {} )
81
+ @telemetry_channel.track_request( id, start_time, duration, response_code, success, options ) unless @disable_telemetry
82
+ end
83
+
84
+ def flush
85
+ @telemetry_channel.flush
86
+ end
87
+
88
+ public
89
+
90
+ @@instance = Telemetry.new
91
+
92
+ def self.instance
93
+ @@instance
94
+ end
95
+
96
+ private_class_method :new
97
+ end
98
+ end
99
+