logstash-output-application_insights 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTORS +9 -0
- data/DEVELOPER.md +0 -0
- data/Gemfile +26 -0
- data/LICENSE +17 -0
- data/README.md +495 -0
- data/Rakefile +22 -0
- data/lib/logstash/outputs/application_insights.rb +393 -0
- data/lib/logstash/outputs/application_insights/blob.rb +923 -0
- data/lib/logstash/outputs/application_insights/block.rb +118 -0
- data/lib/logstash/outputs/application_insights/channel.rb +259 -0
- data/lib/logstash/outputs/application_insights/channels.rb +142 -0
- data/lib/logstash/outputs/application_insights/client.rb +110 -0
- data/lib/logstash/outputs/application_insights/clients.rb +113 -0
- data/lib/logstash/outputs/application_insights/config.rb +341 -0
- data/lib/logstash/outputs/application_insights/constants.rb +208 -0
- data/lib/logstash/outputs/application_insights/exceptions.rb +55 -0
- data/lib/logstash/outputs/application_insights/flow_control.rb +80 -0
- data/lib/logstash/outputs/application_insights/multi_io_logger.rb +69 -0
- data/lib/logstash/outputs/application_insights/shutdown.rb +96 -0
- data/lib/logstash/outputs/application_insights/state.rb +89 -0
- data/lib/logstash/outputs/application_insights/storage_cleanup.rb +214 -0
- data/lib/logstash/outputs/application_insights/sub_channel.rb +75 -0
- data/lib/logstash/outputs/application_insights/telemetry.rb +99 -0
- data/lib/logstash/outputs/application_insights/timer.rb +90 -0
- data/lib/logstash/outputs/application_insights/utils.rb +139 -0
- data/lib/logstash/outputs/application_insights/version.rb +24 -0
- data/logstash-output-application-insights.gemspec +50 -0
- data/spec/outputs/application_insights_spec.rb +42 -0
- 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
|
+
|