logstash-output-application_insights 0.1.3

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