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.
- 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
|
+
|