fluent-plugin-application-insights-freiheit 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.travis.yml +19 -0
- data/CONTRIBUTING.md +33 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +89 -0
- data/Rakefile +13 -0
- data/fluent-plugin-application-insights.gemspec +29 -0
- data/lib/fluent/plugin/out_application_insights.rb +228 -0
- data/test/helper.rb +8 -0
- data/test/plugin/mock_client.rb +37 -0
- data/test/plugin/test_out_application_insights.rb +570 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 22e120f434eadfe8e0afcb31e81d478ee61b7bc2245188388bc9566d31d4422f
|
4
|
+
data.tar.gz: c24602a7155574a03e2513e02f4e29c871c5adedacaab5fcab37577989bfd3fc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b2dac0fc06d4e6a99c83f593620a2b1b46ff694aba9bd3344c7c752a6055cb68de5b42465ba3122ee0ef00bbcc348ab64863242dd99f872434ae66e69c7e6723
|
7
|
+
data.tar.gz: 8aa343099a55a76ad75557b81e3a0d325823053b98d07232ab8431ae4953d180a6b19f34a7982d5d104421c211a44c3917728e7481380d91172efb156708dbd8
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Development
|
2
|
+
## Build Gem
|
3
|
+
Run ```gem build fluent-plugin-application-insights.gemspec```.
|
4
|
+
|
5
|
+
## Run Test
|
6
|
+
Make sure you have bundler installed, you can install it by ```sudo gem install bundler```. And run ```bundler install``` once to install all dependencies.
|
7
|
+
|
8
|
+
Run ```rake test```.
|
9
|
+
|
10
|
+
## Release
|
11
|
+
If you are the current maintainer of this plugin:
|
12
|
+
1. Ensure all tests passed
|
13
|
+
2. Bump up version in ```fluent-plugin-application-insights.gemspec```
|
14
|
+
3. Build the gem, install it locally and test it
|
15
|
+
4. Create a PR with whatever changes needed before releasing, e.g., version bump up, documentation
|
16
|
+
5. Tag and push: ```git tag vx.xx.xx; git push --tags```
|
17
|
+
6. Create a github release with the pushed tag
|
18
|
+
7. Push to rubygems.org: ```gem push fluent-plugin-application-insights-<version>.gem```
|
19
|
+
|
20
|
+
# Contributing
|
21
|
+
|
22
|
+
This project welcomes contributions and suggestions. Most contributions require you to
|
23
|
+
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
|
24
|
+
and actually do, grant us the rights to use your contribution. For details, visit
|
25
|
+
https://cla.microsoft.com.
|
26
|
+
|
27
|
+
When you submit a pull request, a CLA-bot will automatically determine whether you need
|
28
|
+
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
|
29
|
+
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
|
30
|
+
|
31
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
32
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
33
|
+
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) Microsoft Corporation. All rights reserved.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE
|
data/README.md
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# [UNSUPPORTED] fluent-plugin-application-insights
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/fluent-plugin-application-insights.svg)](https://badge.fury.io/rb/fluent-plugin-application-insights)
|
4
|
+
[![Build Status](https://travis-ci.org/Microsoft/fluent-plugin-application-insights.svg?branch=master)](https://travis-ci.org/Microsoft/fluent-plugin-application-insights)
|
5
|
+
|
6
|
+
This is the [Fluentd](https://fluentd.org/) output plugin for [Azure Application Insights](https://docs.microsoft.com/azure/application-insights/)
|
7
|
+
|
8
|
+
Application Insights is an extensible Application Performance Management (APM) service for web developers on multiple platforms.
|
9
|
+
Use it to monitor your live web application. It will automatically detect performance anomalies. It includes powerful analytics
|
10
|
+
tools to help you diagnose issues and to understand what users actually do with your app.
|
11
|
+
It's designed to help you continuously improve performance and usability.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
```
|
16
|
+
$ gem install fluent-plugin-application-insights
|
17
|
+
```
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
To send data to Application Insights, add the following piece to your fluentd configuration file:
|
22
|
+
|
23
|
+
```
|
24
|
+
<match **>
|
25
|
+
@type application_insights
|
26
|
+
instrumentation_key <your instrumentation key>
|
27
|
+
</match>
|
28
|
+
```
|
29
|
+
|
30
|
+
Here is the configuration options for this plugin:
|
31
|
+
|
32
|
+
* `instrumentation_key` - Required, the Application Insights instrumentation key
|
33
|
+
* `service_endpoint_uri` - The endpoint of the Application Insights for Azure Sovereign Clouds
|
34
|
+
* `send_buffer_size` - The batch size to send data to Application Insights service (default `1000`). Setting this to a large size will usually result in better output throughput.
|
35
|
+
* `standard_schema` - The parameter indicating whether the record is in standard schema. i.e., the format that is recognized by Application Insights backend (default `false`).
|
36
|
+
If the record is not in standard schema, it will be tracked as Application Insights trace telemetry. Otherwise, the record is just forwarded to the backend. See [Standard Schema](#standard-schema) for more info.
|
37
|
+
* `message_property` - The property name for the trace message (default `message`).
|
38
|
+
* `time_property` - The property name for the timestamp (default `nil`).
|
39
|
+
Fluentd input plugin will assign a timestamp for each emitted record, and this timestamp is used as the telemetry creation time by default. Set the `time_property` if you want to use the value of this property instead of the one assigned by the input plugin.
|
40
|
+
* `severity_property` - The property name for severity level (default `severity`). If the severity property doesn't exist, the record will be treated as information level. See [Severity Level](https://docs.microsoft.com/azure/application-insights/application-insights-data-model-trace-telemetry#severity-level) for more info.
|
41
|
+
* `severity_level_verbose` - The value of severity property that maps to Application Insights' verbose severity level (default `verbose`).
|
42
|
+
* `severity_level_information` - The value of severity property that maps to Application Insights' information severity level (default `information`).
|
43
|
+
* `severity_level_warning` - The value of severity property that maps to Application Insights' warning severity level (default `warning`).
|
44
|
+
* `severity_level_error` - The value of severity property that maps to Application Insights' error severity level (default `error`).
|
45
|
+
* `severity_level_critical` - The value of severity property that maps to Application Insights' critical severity level (default `critical`).
|
46
|
+
* `context_tag_sources` - The dictionary that instructs the Application Insights plugin to set Application Insights context tags using record properties.
|
47
|
+
In this dictionary keys are Application Insights context tags to set, and values are the source properties that are used to set the context tags value. For the source property, you can specify the property name or jsonpath like syntax for nested property, see [record_accessor syntax](https://docs.fluentd.org/v1.0/articles/api-plugin-helper-record_accessor#syntax) for more info. For example:
|
48
|
+
```
|
49
|
+
context_tag_sources {
|
50
|
+
"ai.cloud.role": "kubernetes_container_name",
|
51
|
+
"ai.cloud.roleInstance": "$.docker.container_id"
|
52
|
+
}
|
53
|
+
```
|
54
|
+
Here is the list of all [context tag keys](https://github.com/Microsoft/ApplicationInsights-dotnet/blob/develop/Schema/PublicSchema/ContextTagKeys.bond)
|
55
|
+
|
56
|
+
## Standard Schema
|
57
|
+
|
58
|
+
The standard schema for Application Insights telemetry is defined [here](https://github.com/Microsoft/ApplicationInsights-Home/tree/master/EndpointSpecs/Schemas/Bond).
|
59
|
+
|
60
|
+
Below is an example of a Request telemetry in standard schema format. `data`, `data.baseType` and `data.baseData` are required properties. Different telemetry types will have different properties associated with the `baseData` object.
|
61
|
+
|
62
|
+
```
|
63
|
+
{
|
64
|
+
"name": "Microsoft.ApplicationInsights.Request",
|
65
|
+
"time": "2018-02-28T00:24:00.5676240Z",
|
66
|
+
"tags":{
|
67
|
+
"ai.cloud.role": "webfront",
|
68
|
+
"ai.cloud.roleInstance":"85a1e424491d07b6c1ed032f"
|
69
|
+
},
|
70
|
+
"data": {
|
71
|
+
"baseType": "RequestData",
|
72
|
+
"baseData": {
|
73
|
+
"ver":2,
|
74
|
+
"id":"|85a1e424-491d07b6c1ed032f.",
|
75
|
+
"name":"PUT Flights/StartNewFlightAsync",
|
76
|
+
"duration":"00:00:01.0782934",
|
77
|
+
"success":true,
|
78
|
+
"responseCode":"204",
|
79
|
+
"url":"http://localhost:5023/api/flights
|
80
|
+
}
|
81
|
+
}
|
82
|
+
```
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
Refer to [Contributing Guide](CONTRIBUTING.md).
|
86
|
+
|
87
|
+
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
88
|
+
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
89
|
+
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler"
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs.push("lib", "test")
|
8
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
9
|
+
t.verbose = true
|
10
|
+
t.warning = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: [:test]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "fluent-plugin-application-insights-freiheit"
|
6
|
+
spec.version = "0.2.5"
|
7
|
+
spec.authors = ["freiheit.com"]
|
8
|
+
spec.email = ["xyz@freiheit.com"]
|
9
|
+
|
10
|
+
spec.summary = "This is a fork of the fluentd output plugin for Azure Application Insights."
|
11
|
+
spec.description = "Fluentd output plugin for Azure Application Insights."
|
12
|
+
|
13
|
+
spec.homepage = "https://github.com/dennis-tra/fluent-plugin-application-insights"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
test_files, files = `git ls-files -z`.split("\x0").partition do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.files = files.push('lib/fluent/plugin/out_application_insights.rb')
|
20
|
+
spec.executables = files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.test_files = test_files
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
25
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
26
|
+
spec.add_development_dependency "test-unit", "~> 3.0"
|
27
|
+
spec.add_runtime_dependency "fluentd", [">= 1.0", "< 2"]
|
28
|
+
spec.add_runtime_dependency "application_insights", "~> 0.5.5"
|
29
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
2
|
+
# Licensed under the MIT License.
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
require 'fluent/plugin/output'
|
6
|
+
require 'application_insights'
|
7
|
+
|
8
|
+
module Fluent::Plugin
|
9
|
+
class ApplicationInsightsOutput < Output
|
10
|
+
Fluent::Plugin.register_output("application_insights", self)
|
11
|
+
|
12
|
+
attr_accessor :tc
|
13
|
+
|
14
|
+
helpers :record_accessor
|
15
|
+
|
16
|
+
# The Application Insights instrumentation key
|
17
|
+
config_param :instrumentation_key, :string
|
18
|
+
# The service endpoint uri to push the telemetry to
|
19
|
+
config_param :service_endpoint_uri, :string, default: nil
|
20
|
+
# The batch size to send data to Application Insights service.
|
21
|
+
config_param :send_buffer_size, :integer, default: 1000
|
22
|
+
# The parameter indication whether the record is in standard schema. i.e., the format that is recognized by Application Insights backend.
|
23
|
+
config_param :standard_schema, :bool, default: false
|
24
|
+
# The property name for the message. It will be ignored if the record is in standard schema.
|
25
|
+
config_param :message_property, :string, default: 'message'
|
26
|
+
# The property name for the timestamp. It will be ignored if the record is in standard schema.
|
27
|
+
config_param :time_property, :string, default: nil
|
28
|
+
# The property name for severity level. It will be ignored if the record is in standard schema.
|
29
|
+
config_param :severity_property, :string, default: 'severity'
|
30
|
+
# The value of severity property that maps to Application Insights' verbose severity level.
|
31
|
+
config_param :severity_level_verbose, :array, value_type: :string, default: ['verbose']
|
32
|
+
# The value of severity property that maps to Application Insights' information severity level.
|
33
|
+
config_param :severity_level_information, :array, value_type: :string, default: ['information']
|
34
|
+
# The value of severity property that maps to Application Insights' warning severity level.
|
35
|
+
config_param :severity_level_warning, :array, value_type: :string, default: ['warning']
|
36
|
+
# The value of severity property that maps to Application Insights' error severity level.
|
37
|
+
config_param :severity_level_error, :array, value_type: :string, default: ['error']
|
38
|
+
# The value of severity property that maps to Application Insights' critical severity level.
|
39
|
+
config_param :severity_level_critical, :array, value_type: :string, default: ['critical']
|
40
|
+
# The dictionary that instructs the Application Insights plugin to set Application Insights context tags using record properties.
|
41
|
+
# In this dictionary keys are Application Insights context tags to set, and values are names of properties to use as source of data.
|
42
|
+
config_param :context_tag_sources, :hash, default: {}, value_type: :string
|
43
|
+
|
44
|
+
TELEMETRY_TYPES = ["RequestData", "RemoteDependencyData", "MessageData", "ExceptionData", "EventData", "MetricData", "PageViewData", "AvailabilityData"]
|
45
|
+
|
46
|
+
def configure(conf)
|
47
|
+
super
|
48
|
+
|
49
|
+
@severity_level_mapping = {}
|
50
|
+
@severity_level_verbose.each { |l| @severity_level_mapping[l.downcase] = Channel::Contracts::SeverityLevel::VERBOSE }
|
51
|
+
@severity_level_information.each { |l| @severity_level_mapping[l.downcase] = Channel::Contracts::SeverityLevel::INFORMATION }
|
52
|
+
@severity_level_warning.each { |l| @severity_level_mapping[l.downcase] = Channel::Contracts::SeverityLevel::WARNING }
|
53
|
+
@severity_level_error.each { |l| @severity_level_mapping[l.downcase] = Channel::Contracts::SeverityLevel::ERROR }
|
54
|
+
@severity_level_critical.each { |l| @severity_level_mapping[l.downcase] = Channel::Contracts::SeverityLevel::CRITICAL }
|
55
|
+
|
56
|
+
context_tag_keys = []
|
57
|
+
context_tag_keys.concat Channel::Contracts::Application.json_mappings.values
|
58
|
+
context_tag_keys.concat Channel::Contracts::Cloud.json_mappings.values
|
59
|
+
context_tag_keys.concat Channel::Contracts::Device.json_mappings.values
|
60
|
+
context_tag_keys.concat Channel::Contracts::Internal.json_mappings.values
|
61
|
+
context_tag_keys.concat Channel::Contracts::Location.json_mappings.values
|
62
|
+
context_tag_keys.concat Channel::Contracts::Operation.json_mappings.values
|
63
|
+
context_tag_keys.concat Channel::Contracts::Session.json_mappings.values
|
64
|
+
context_tag_keys.concat Channel::Contracts::User.json_mappings.values
|
65
|
+
|
66
|
+
@context_tag_accessors = {}
|
67
|
+
context_tag_sources.each do |tag, property_path|
|
68
|
+
raise ArgumentError.new("Context tag '#{tag}' is invalid!") unless context_tag_keys.include?(tag)
|
69
|
+
|
70
|
+
@context_tag_accessors[tag] = record_accessor_create(property_path)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def start
|
75
|
+
super
|
76
|
+
|
77
|
+
sender = @service_endpoint_uri.nil? ? Channel::AsynchronousSender.new : Channel::AsynchronousSender.new(@service_endpoint_uri)
|
78
|
+
queue = Channel::AsynchronousQueue.new sender
|
79
|
+
channel = Channel::TelemetryChannel.new nil, queue
|
80
|
+
@tc = TelemetryClient.new @instrumentation_key, channel
|
81
|
+
@tc.channel.queue.max_queue_length = @send_buffer_size
|
82
|
+
@tc.channel.sender.send_buffer_size = @send_buffer_size
|
83
|
+
end
|
84
|
+
|
85
|
+
def shutdown
|
86
|
+
super
|
87
|
+
|
88
|
+
# Draining the events in the queue.
|
89
|
+
# We need to make sure the work thread has finished. Otherwise, it's possible that the queue is empty, but the http request to send the data is not finished.
|
90
|
+
# However, a drawback of waiting for the work thread to finish is that even if the events have been drained, it will still poll the queue for some time (default is 3 seconds, set by sender.send_time).
|
91
|
+
# This can be improved if the SDK exposes another variable indicating whether the work thread is sending data or just polling the queue.
|
92
|
+
while !@tc.channel.queue.empty? || @tc.channel.sender.work_thread != nil
|
93
|
+
# It's possible that the work thread has already exited but there are still items in the queue.
|
94
|
+
# https://github.com/Microsoft/ApplicationInsights-Ruby/blob/master/lib/application_insights/channel/asynchronous_sender.rb#L115
|
95
|
+
# Trigger flush to make the work thread working again in this case.
|
96
|
+
if @tc.channel.sender.work_thread == nil && !@tc.channel.queue.empty?
|
97
|
+
@tc.flush
|
98
|
+
end
|
99
|
+
|
100
|
+
sleep(1)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def process(tag, es)
|
105
|
+
es.each do |time, record|
|
106
|
+
# 'time' is a Fluent::EventTime object or an Integer. Convert it to ruby Time object.
|
107
|
+
time_ruby = time.is_a?(Fluent::EventTime) ? Time.at(time.sec, time.nsec / 1000).utc : Time.at(time)
|
108
|
+
if @standard_schema
|
109
|
+
process_standard_schema_log record, time_ruby
|
110
|
+
else
|
111
|
+
process_non_standard_schema_log record, time_ruby
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def process_standard_schema_log(record, time)
|
117
|
+
if record["data"] && record["data"].is_a?(Hash) && record["data"]["baseType"] && record["data"]["baseData"]
|
118
|
+
base_type = record["data"]["baseType"]
|
119
|
+
|
120
|
+
if TELEMETRY_TYPES.include? base_type
|
121
|
+
# If the record is processed by json parser plugin, e.g., in_http use it by default, the time property will be removed. Add it back in this case.
|
122
|
+
record["time"] ||= time.iso8601(7)
|
123
|
+
record["iKey"] = @instrumentation_key
|
124
|
+
set_name_property(record) if !record["name"]
|
125
|
+
set_context_standard_schema record
|
126
|
+
|
127
|
+
envelope = Channel::Contracts::Envelope.new
|
128
|
+
Channel::Contracts::Envelope.json_mappings.each do |attr, name|
|
129
|
+
property = record.delete(name)
|
130
|
+
envelope.send(:"#{attr}=", property) if property
|
131
|
+
end
|
132
|
+
|
133
|
+
# There could be extra properties added during the fluentd pipeline. Merge the extra properties so they are not lost.
|
134
|
+
merge_extra_properties_standard_schema record, envelope
|
135
|
+
|
136
|
+
@tc.channel.queue.push(envelope)
|
137
|
+
else
|
138
|
+
log.debug "Unknown telemetry type #{base_type}. Event will be treated as as non standard schema event."
|
139
|
+
process_non_standard_schema_log record, time
|
140
|
+
end
|
141
|
+
else
|
142
|
+
log.debug "The event does not meet the standard schema of Application Insights output. Missing data, baseType or baseData property. Event will be treated as as non standard schema event."
|
143
|
+
process_non_standard_schema_log record, time
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def set_name_property(record)
|
148
|
+
normalizedIKey = @instrumentation_key.gsub("-", "")
|
149
|
+
normalizedIKey = normalizedIKey.empty? ? "" : normalizedIKey + "."
|
150
|
+
type = record["data"]["baseType"][0..-5]
|
151
|
+
record["name"] = "Microsoft.ApplicationInsights.#{normalizedIKey}#{type}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def set_context_standard_schema(record)
|
155
|
+
return if @context_tag_sources.length == 0
|
156
|
+
|
157
|
+
record["tags"] = record["tags"] || {}
|
158
|
+
@context_tag_accessors.each do |tag, accessor|
|
159
|
+
tag_value = accessor.call(record)
|
160
|
+
record["tags"][tag] = tag_value if !tag_value.nil?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def merge_extra_properties_standard_schema(record, envelope)
|
165
|
+
return if record.empty?
|
166
|
+
|
167
|
+
envelope.data["baseData"]["properties"] ||= {}
|
168
|
+
envelope.data["baseData"]["properties"].merge!(record)
|
169
|
+
stringify_properties(envelope.data["baseData"]["properties"])
|
170
|
+
end
|
171
|
+
|
172
|
+
def process_non_standard_schema_log(record, time)
|
173
|
+
time = record.delete(@time_property) || time
|
174
|
+
context = get_context_non_standard_schema(record)
|
175
|
+
message = record.delete @message_property
|
176
|
+
severity_level_value = record.delete @severity_property
|
177
|
+
severity_level = severity_level_value ? @severity_level_mapping[severity_level_value.to_s.downcase] : nil
|
178
|
+
props = stringify_properties(record)
|
179
|
+
|
180
|
+
data = Channel::Contracts::MessageData.new(
|
181
|
+
:message => message || 'Null',
|
182
|
+
:severity_level => severity_level || Channel::Contracts::SeverityLevel::INFORMATION,
|
183
|
+
:properties => props || {}
|
184
|
+
)
|
185
|
+
|
186
|
+
@tc.channel.write(data, context, time)
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_context_non_standard_schema(record)
|
190
|
+
context = Channel::TelemetryContext.new
|
191
|
+
context.instrumentation_key = @instrumentation_key
|
192
|
+
return context if @context_tag_sources.length == 0
|
193
|
+
|
194
|
+
@context_tag_accessors.each do |tag, accessor|
|
195
|
+
set_context_tag context, tag, accessor.call(record)
|
196
|
+
end
|
197
|
+
|
198
|
+
return context
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_context_tag(context, tag_name, tag_value)
|
202
|
+
return if tag_value.nil?
|
203
|
+
|
204
|
+
context_set = [context.application, context.cloud, context.device, context.location, context.operation, context.session, context.user]
|
205
|
+
context_set.each do |c|
|
206
|
+
c.class.json_mappings.each do |attr, name|
|
207
|
+
if (name == tag_name)
|
208
|
+
c.send(:"#{attr}=", tag_value)
|
209
|
+
return
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def stringify_properties(record)
|
216
|
+
# If the property value is a json object or array, e.g., {"prop": {"inner_prop": value}}, it needs to be serialized.
|
217
|
+
# Otherwise, the property will become {"prop": "[object Object]"} in the final telemetry.
|
218
|
+
# The stringified property can be queried as described here: https://docs.loganalytics.io/docs/Language-Reference/Scalar-functions/parse_json()
|
219
|
+
record.each do |key, value|
|
220
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
221
|
+
record[key] = JSON.generate(value)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
record
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
|
2
|
+
require "test-unit"
|
3
|
+
require "fluent/test"
|
4
|
+
require "fluent/test/driver/output"
|
5
|
+
require "fluent/test/helpers"
|
6
|
+
|
7
|
+
Test::Unit::TestCase.include(Fluent::Test::Helpers)
|
8
|
+
Test::Unit::TestCase.extend(Fluent::Test::Helpers)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "application_insights"
|
2
|
+
|
3
|
+
class MockSender < Channel::SenderBase
|
4
|
+
def initialize
|
5
|
+
end
|
6
|
+
|
7
|
+
def start
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class MockQueue
|
12
|
+
attr_accessor :sender
|
13
|
+
attr_accessor :queue
|
14
|
+
|
15
|
+
def initialize(sender)
|
16
|
+
@sender = sender
|
17
|
+
@queue = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def push(item)
|
21
|
+
return unless item
|
22
|
+
@queue << item
|
23
|
+
end
|
24
|
+
|
25
|
+
def empty?
|
26
|
+
@queue.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](index)
|
30
|
+
@queue[index]
|
31
|
+
end
|
32
|
+
|
33
|
+
def flush
|
34
|
+
@queue = []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,570 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "fluent/plugin/out_application_insights.rb"
|
3
|
+
require_relative "mock_client.rb"
|
4
|
+
|
5
|
+
class ApplicationInsightsOutputTest < Test::Unit::TestCase
|
6
|
+
setup do
|
7
|
+
Fluent::Test.setup
|
8
|
+
end
|
9
|
+
|
10
|
+
CONFIG = %[
|
11
|
+
instrumentation_key ikey
|
12
|
+
]
|
13
|
+
|
14
|
+
def create_driver(conf = CONFIG)
|
15
|
+
driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::ApplicationInsightsOutput).configure(conf)
|
16
|
+
driver.instance.start
|
17
|
+
|
18
|
+
sender = MockSender.new
|
19
|
+
queue = MockQueue.new sender
|
20
|
+
channel = ApplicationInsights::Channel::TelemetryChannel.new nil, queue
|
21
|
+
driver.instance.tc = ApplicationInsights::TelemetryClient.new "iKey", channel
|
22
|
+
|
23
|
+
return driver
|
24
|
+
end
|
25
|
+
|
26
|
+
sub_test_case 'configure' do
|
27
|
+
test 'invalid context tag key' do
|
28
|
+
config = %[
|
29
|
+
instrumentation_key ikey
|
30
|
+
context_tag_sources invalid_tag_name:kubernetes_container_name
|
31
|
+
]
|
32
|
+
assert_raise ArgumentError.new("Context tag 'invalid_tag_name' is invalid!") do
|
33
|
+
create_driver config
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
sub_test_case 'process standard schema event' do
|
39
|
+
setup do
|
40
|
+
config = %[
|
41
|
+
instrumentation_key ikey
|
42
|
+
standard_schema true
|
43
|
+
]
|
44
|
+
|
45
|
+
@d = create_driver config
|
46
|
+
end
|
47
|
+
|
48
|
+
test 'ikey and timestamps are added if empty' do
|
49
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
50
|
+
@d.run(default_tag: 'test', shutdown: false) do
|
51
|
+
@d.feed(time, {"name" => "telemetry name", "data" => { "baseType" => "RequestData", "baseData" => {} }})
|
52
|
+
end
|
53
|
+
|
54
|
+
envelope = @d.instance.tc.channel.queue[0]
|
55
|
+
assert_equal "ikey", envelope.i_key
|
56
|
+
assert_equal "2011-01-02T13:14:15.0000000Z", envelope.time
|
57
|
+
end
|
58
|
+
|
59
|
+
test 'event missing required properties is treated as non standard schema' do
|
60
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
61
|
+
@d.instance.log.level = "debug"
|
62
|
+
@d.run(default_tag: 'test', shutdown: false) do
|
63
|
+
@d.feed(time, {})
|
64
|
+
@d.feed(time, {"data" => 2})
|
65
|
+
@d.feed(time, {"data" => {}})
|
66
|
+
@d.feed(time, {"data" => {"someprop" => "value"}})
|
67
|
+
@d.feed(time, {"data" => {"baseType" => "type"}})
|
68
|
+
@d.feed(time, {"data" => {"baseData" => "data"}})
|
69
|
+
end
|
70
|
+
|
71
|
+
logs = @d.instance.log.out.logs
|
72
|
+
assert_equal 6, logs.length
|
73
|
+
assert_true logs.all?{ |log| log.include?("The event does not meet the standard schema of Application Insights output. Missing data, baseType or baseData property.") }
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'event of standard schema will fill in the name property if it is empty' do
|
77
|
+
config = %[
|
78
|
+
instrumentation_key 000-000-000
|
79
|
+
standard_schema true
|
80
|
+
]
|
81
|
+
d = create_driver config
|
82
|
+
|
83
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
84
|
+
d.run(default_tag: 'test', shutdown: false) do
|
85
|
+
d.feed(time, {
|
86
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} }
|
87
|
+
})
|
88
|
+
end
|
89
|
+
|
90
|
+
envelope = d.instance.tc.channel.queue[0]
|
91
|
+
assert_equal "Microsoft.ApplicationInsights.000000000.Request", envelope.name
|
92
|
+
end
|
93
|
+
|
94
|
+
test 'event of standard schema will fill in the name property if it is empty and instrumentation key is empty' do
|
95
|
+
config = %[
|
96
|
+
instrumentation_key ""
|
97
|
+
standard_schema true
|
98
|
+
]
|
99
|
+
d = create_driver config
|
100
|
+
|
101
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
102
|
+
d.run(default_tag: 'test', shutdown: false) do
|
103
|
+
d.feed(time, {
|
104
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} }
|
105
|
+
})
|
106
|
+
end
|
107
|
+
|
108
|
+
envelope = d.instance.tc.channel.queue[0]
|
109
|
+
assert_equal "Microsoft.ApplicationInsights.Request", envelope.name
|
110
|
+
end
|
111
|
+
|
112
|
+
test 'event with unknown data type is treated as non standard schema' do
|
113
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
114
|
+
@d.instance.log.level = "debug"
|
115
|
+
@d.run(default_tag: 'test', shutdown: false) do
|
116
|
+
@d.feed(time, {"name" => "telemetry name", "data" => {"baseType" => "unknown", "baseData" => {}}})
|
117
|
+
end
|
118
|
+
|
119
|
+
logs = @d.instance.log.out.logs
|
120
|
+
assert_true logs.length > 0
|
121
|
+
assert_true logs.all?{ |log| log.include?("Unknown telemetry type unknown") }
|
122
|
+
end
|
123
|
+
|
124
|
+
test 'extra properties are not lost after processing' do
|
125
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
126
|
+
@d.run(default_tag: 'test', shutdown: false) do
|
127
|
+
@d.feed(time, {"name" => "telemetry name", "data" => { "baseType" => "RequestData", "baseData" => {} }, "kubernetes" => { "pod_name" => "frontend" }})
|
128
|
+
end
|
129
|
+
|
130
|
+
envelope = @d.instance.tc.channel.queue[0]
|
131
|
+
assert_equal({ "kubernetes" => "{\"pod_name\":\"frontend\"}" }, envelope.data["baseData"]["properties"])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
sub_test_case 'set context on standard schema event' do
|
136
|
+
test 'context tag sources is empty' do
|
137
|
+
config = %[
|
138
|
+
instrumentation_key ikey
|
139
|
+
standard_schema true
|
140
|
+
context_tag_sources {}
|
141
|
+
]
|
142
|
+
d = create_driver config
|
143
|
+
|
144
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
145
|
+
d.run(default_tag: 'test', shutdown: false) do
|
146
|
+
d.feed(time, {
|
147
|
+
"name" => "telemetry name",
|
148
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} },
|
149
|
+
"kubernetes_container_name" => "frontend"
|
150
|
+
})
|
151
|
+
end
|
152
|
+
|
153
|
+
envelope = d.instance.tc.channel.queue[0]
|
154
|
+
assert_true envelope.tags.length == 0
|
155
|
+
end
|
156
|
+
|
157
|
+
test 'context tag sources does not exist on record' do
|
158
|
+
config = %[
|
159
|
+
instrumentation_key ikey
|
160
|
+
standard_schema true
|
161
|
+
context_tag_sources ai.cloud.role:kubernetes_container_name
|
162
|
+
]
|
163
|
+
d = create_driver config
|
164
|
+
|
165
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
166
|
+
d.run(default_tag: 'test', shutdown: false) do
|
167
|
+
d.feed(time, {
|
168
|
+
"name" => "telemetry name",
|
169
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} }
|
170
|
+
})
|
171
|
+
end
|
172
|
+
|
173
|
+
envelope = d.instance.tc.channel.queue[0]
|
174
|
+
assert_not_nil envelope.tags
|
175
|
+
assert_nil envelope.tags["ai.cloud.role"]
|
176
|
+
end
|
177
|
+
|
178
|
+
test 'context is updated according to context tag keys' do
|
179
|
+
config = %[
|
180
|
+
instrumentation_key ikey
|
181
|
+
standard_schema true
|
182
|
+
context_tag_sources ai.cloud.role:kubernetes_container_name
|
183
|
+
]
|
184
|
+
d = create_driver config
|
185
|
+
|
186
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
187
|
+
d.run(default_tag: 'test', shutdown: false) do
|
188
|
+
d.feed(time, {
|
189
|
+
"name" => "telemetry name",
|
190
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} },
|
191
|
+
"kubernetes_container_name" => "frontend",
|
192
|
+
"other_prop" => "prop value"
|
193
|
+
})
|
194
|
+
end
|
195
|
+
|
196
|
+
envelope = d.instance.tc.channel.queue[0]
|
197
|
+
assert_not_nil envelope.tags
|
198
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
199
|
+
|
200
|
+
extra_properties = {
|
201
|
+
"kubernetes_container_name" => "frontend",
|
202
|
+
"other_prop" => "prop value"
|
203
|
+
}
|
204
|
+
assert_equal extra_properties, envelope.data["baseData"]["properties"]
|
205
|
+
end
|
206
|
+
|
207
|
+
test 'context tag source is nested property path' do
|
208
|
+
config = %[
|
209
|
+
instrumentation_key ikey
|
210
|
+
standard_schema true
|
211
|
+
context_tag_sources {
|
212
|
+
"ai.cloud.role": "$.kubernetes.container_name",
|
213
|
+
"ai.cloud.roleInstance": "$.kubernetes.not_exist"
|
214
|
+
}
|
215
|
+
]
|
216
|
+
d = create_driver config
|
217
|
+
|
218
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
219
|
+
d.run(default_tag: 'test', shutdown: false) do
|
220
|
+
d.feed(time, {
|
221
|
+
"name" => "telemetry name",
|
222
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} },
|
223
|
+
"kubernetes" => {
|
224
|
+
"container_name" => "frontend",
|
225
|
+
"container_id" => "c42c557e1615511dd3a3cb1d6e8f14984464bb0f"
|
226
|
+
}
|
227
|
+
})
|
228
|
+
end
|
229
|
+
|
230
|
+
envelope = d.instance.tc.channel.queue[0]
|
231
|
+
assert_not_nil envelope.tags
|
232
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
233
|
+
assert_nil envelope.tags["ai.cloud.roleInstance"]
|
234
|
+
end
|
235
|
+
|
236
|
+
test 'multiple context tag keys' do
|
237
|
+
config = %[
|
238
|
+
instrumentation_key ikey
|
239
|
+
standard_schema true
|
240
|
+
context_tag_sources {
|
241
|
+
"ai.cloud.role": "kubernetes_container_name",
|
242
|
+
"ai.cloud.roleInstance": "$.docker.container_id"
|
243
|
+
}
|
244
|
+
]
|
245
|
+
d = create_driver config
|
246
|
+
|
247
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
248
|
+
d.run(default_tag: 'test', shutdown: false) do
|
249
|
+
d.feed(time, {
|
250
|
+
"name" => "telemetry name",
|
251
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} },
|
252
|
+
"docker" => {
|
253
|
+
"container_id" => "c42c557e1615511dd3a3cb1d6e8f14984464bb0f"
|
254
|
+
},
|
255
|
+
"kubernetes_container_name" => "frontend"
|
256
|
+
})
|
257
|
+
end
|
258
|
+
|
259
|
+
envelope = d.instance.tc.channel.queue[0]
|
260
|
+
assert_not_nil envelope.tags
|
261
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
262
|
+
assert_equal "c42c557e1615511dd3a3cb1d6e8f14984464bb0f", envelope.tags["ai.cloud.roleInstance"]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
sub_test_case 'process non standard schema event' do
|
267
|
+
test 'empty message' do
|
268
|
+
d = create_driver
|
269
|
+
|
270
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
271
|
+
d.run(default_tag: 'test', shutdown: false) do
|
272
|
+
d.feed(time, {"prop" => "value"})
|
273
|
+
end
|
274
|
+
|
275
|
+
assert_equal 1, d.instance.tc.channel.queue.queue.length
|
276
|
+
envelope = d.instance.tc.channel.queue[0]
|
277
|
+
assert_equal "Null", envelope.data.base_data.message
|
278
|
+
assert_equal envelope.data.base_data.properties, {"prop" => "value"}
|
279
|
+
end
|
280
|
+
|
281
|
+
test 'custom timestamp take precedence over fluentd timestamp' do
|
282
|
+
config = %[
|
283
|
+
instrumentation_key ikey
|
284
|
+
time_property time
|
285
|
+
]
|
286
|
+
d = create_driver config
|
287
|
+
|
288
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
289
|
+
d.run(default_tag: 'test', shutdown: false) do
|
290
|
+
d.feed(time, {"time" => "2010-10-01"})
|
291
|
+
end
|
292
|
+
|
293
|
+
envelope = d.instance.tc.channel.queue[0]
|
294
|
+
assert_equal "2010-10-01", envelope.time
|
295
|
+
end
|
296
|
+
|
297
|
+
test 'custom timestamp format is not ensured to be valid' do
|
298
|
+
config = %[
|
299
|
+
instrumentation_key ikey
|
300
|
+
time_property time
|
301
|
+
]
|
302
|
+
d = create_driver config
|
303
|
+
|
304
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
305
|
+
d.run(default_tag: 'test', shutdown: false) do
|
306
|
+
d.feed(time, {"time" => "custom time"})
|
307
|
+
end
|
308
|
+
|
309
|
+
envelope = d.instance.tc.channel.queue[0]
|
310
|
+
assert_equal "custom time", envelope.time
|
311
|
+
end
|
312
|
+
|
313
|
+
test 'timestamp is in iso8601 format' do
|
314
|
+
d = create_driver
|
315
|
+
|
316
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
317
|
+
d.run(default_tag: 'test', shutdown: false) do
|
318
|
+
d.feed(time, {"message" => "log message"})
|
319
|
+
end
|
320
|
+
|
321
|
+
envelope = d.instance.tc.channel.queue[0]
|
322
|
+
assert_equal "2011-01-02T13:14:15.0000000Z", envelope.time
|
323
|
+
end
|
324
|
+
|
325
|
+
test 'custom message property' do
|
326
|
+
config = %[
|
327
|
+
instrumentation_key ikey
|
328
|
+
message_property custom_message_property
|
329
|
+
]
|
330
|
+
d = create_driver config
|
331
|
+
|
332
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
333
|
+
d.run(default_tag: 'test', shutdown: false) do
|
334
|
+
d.feed(time, {"custom_message_property" => "custom message", "message" => "my message"})
|
335
|
+
end
|
336
|
+
|
337
|
+
envelope = d.instance.tc.channel.queue[0]
|
338
|
+
assert_equal "custom message", envelope.data.base_data.message
|
339
|
+
end
|
340
|
+
|
341
|
+
test 'custom severity level mapping' do
|
342
|
+
config = %[
|
343
|
+
instrumentation_key ikey
|
344
|
+
severity_property custom_severity_property
|
345
|
+
severity_level_error 100
|
346
|
+
]
|
347
|
+
d = create_driver config
|
348
|
+
|
349
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
350
|
+
d.run(default_tag: 'test', shutdown: false) do
|
351
|
+
d.feed(time, {"custom_severity_property" => 100, "message" => "my message"})
|
352
|
+
end
|
353
|
+
|
354
|
+
envelope = d.instance.tc.channel.queue[0]
|
355
|
+
assert_equal ApplicationInsights::Channel::Contracts::SeverityLevel::ERROR, envelope.data.base_data.severity_level
|
356
|
+
end
|
357
|
+
|
358
|
+
test 'multiple severity levels' do
|
359
|
+
config = %[
|
360
|
+
instrumentation_key ikey
|
361
|
+
severity_property custom_severity_property
|
362
|
+
severity_level_critical fatal, panic
|
363
|
+
]
|
364
|
+
d = create_driver config
|
365
|
+
|
366
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
367
|
+
d.run(default_tag: 'test', shutdown: false) do
|
368
|
+
d.feed(time, {"custom_severity_property" => "panic", "message" => "my message"})
|
369
|
+
end
|
370
|
+
|
371
|
+
d.run(default_tag: 'test', shutdown: false) do
|
372
|
+
d.feed(time, {"custom_severity_property" => "fatal", "message" => "my message"})
|
373
|
+
end
|
374
|
+
|
375
|
+
d.run(default_tag: 'test', shutdown: false) do
|
376
|
+
d.feed(time, {"custom_severity_property" => "critical", "message" => "my message"})
|
377
|
+
end
|
378
|
+
|
379
|
+
envelope = d.instance.tc.channel.queue[0]
|
380
|
+
assert_equal ApplicationInsights::Channel::Contracts::SeverityLevel::CRITICAL, envelope.data.base_data.severity_level
|
381
|
+
|
382
|
+
envelope = d.instance.tc.channel.queue[1]
|
383
|
+
assert_equal ApplicationInsights::Channel::Contracts::SeverityLevel::CRITICAL, envelope.data.base_data.severity_level
|
384
|
+
|
385
|
+
envelope = d.instance.tc.channel.queue[2]
|
386
|
+
assert_not_equal ApplicationInsights::Channel::Contracts::SeverityLevel::CRITICAL, envelope.data.base_data.severity_level
|
387
|
+
end
|
388
|
+
|
389
|
+
test 'properties are stringified' do
|
390
|
+
d = create_driver
|
391
|
+
|
392
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
393
|
+
d.run(default_tag: 'test', shutdown: false) do
|
394
|
+
d.feed(time, {"prop" => {"inner_prop1" => "value1", "inner_prop2" => "value2"}})
|
395
|
+
end
|
396
|
+
|
397
|
+
envelope = d.instance.tc.channel.queue[0]
|
398
|
+
assert_equal 1, envelope.data.base_data.properties.length
|
399
|
+
assert_equal "{\"inner_prop1\":\"value1\",\"inner_prop2\":\"value2\"}", envelope.data.base_data.properties["prop"]
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
sub_test_case 'set context on non standard schema event' do
|
404
|
+
test 'context tag sources is empty' do
|
405
|
+
config = %[
|
406
|
+
instrumentation_key ikey
|
407
|
+
context_tag_sources {}
|
408
|
+
]
|
409
|
+
d = create_driver config
|
410
|
+
|
411
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
412
|
+
d.run(default_tag: 'test', shutdown: false) do
|
413
|
+
d.feed(time, {
|
414
|
+
"message" => "my message",
|
415
|
+
"kubernetes_container_name" => "frontend"
|
416
|
+
})
|
417
|
+
end
|
418
|
+
|
419
|
+
envelope = d.instance.tc.channel.queue[0]
|
420
|
+
|
421
|
+
# The only tag is "ai.internal.sdkVersion", which is irrelevant to contaxt_tag_sources
|
422
|
+
assert_true envelope.tags.length == 1
|
423
|
+
assert_equal "ai.internal.sdkVersion", envelope.tags.keys[0]
|
424
|
+
assert_equal "ikey", envelope.i_key
|
425
|
+
end
|
426
|
+
|
427
|
+
test 'context tag sources does not exist on record' do
|
428
|
+
config = %[
|
429
|
+
instrumentation_key ikey
|
430
|
+
context_tag_sources ai.cloud.role:kubernetes_container_name
|
431
|
+
]
|
432
|
+
d = create_driver config
|
433
|
+
|
434
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
435
|
+
d.run(default_tag: 'test', shutdown: false) do
|
436
|
+
d.feed(time, {
|
437
|
+
"message" => "my message"
|
438
|
+
})
|
439
|
+
end
|
440
|
+
|
441
|
+
envelope = d.instance.tc.channel.queue[0]
|
442
|
+
assert_not_nil envelope.tags
|
443
|
+
assert_nil envelope.tags["ai.cloud.role"]
|
444
|
+
end
|
445
|
+
|
446
|
+
test 'context is updated according to context tag keys' do
|
447
|
+
config = %[
|
448
|
+
instrumentation_key ikey
|
449
|
+
context_tag_sources ai.cloud.role:kubernetes_container_name
|
450
|
+
]
|
451
|
+
d = create_driver config
|
452
|
+
|
453
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
454
|
+
d.run(default_tag: 'test', shutdown: false) do
|
455
|
+
d.feed(time, {
|
456
|
+
"message" => "my message",
|
457
|
+
"kubernetes_container_name" => "frontend",
|
458
|
+
"other_prop" => "prop value"
|
459
|
+
})
|
460
|
+
end
|
461
|
+
|
462
|
+
envelope = d.instance.tc.channel.queue[0]
|
463
|
+
assert_not_nil envelope.tags
|
464
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
465
|
+
assert_not_nil envelope.data.base_data.properties["kubernetes_container_name"]
|
466
|
+
assert_not_nil envelope.data.base_data.properties["other_prop"]
|
467
|
+
end
|
468
|
+
|
469
|
+
test 'context tag source is nested property path' do
|
470
|
+
config = %[
|
471
|
+
instrumentation_key ikey
|
472
|
+
context_tag_sources {
|
473
|
+
"ai.cloud.role": "$.kubernetes.container_name",
|
474
|
+
"ai.cloud.roleInstance": "$.kubernetes.not_exist"
|
475
|
+
}
|
476
|
+
]
|
477
|
+
d = create_driver config
|
478
|
+
|
479
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
480
|
+
d.run(default_tag: 'test', shutdown: false) do
|
481
|
+
d.feed(time, {
|
482
|
+
"name" => "telemetry name",
|
483
|
+
"data" => { "baseType" => "RequestData", "baseData" => {} },
|
484
|
+
"kubernetes" => {
|
485
|
+
"container_name" => "frontend",
|
486
|
+
"container_id" => "c42c557e1615511dd3a3cb1d6e8f14984464bb0f"
|
487
|
+
}
|
488
|
+
})
|
489
|
+
end
|
490
|
+
|
491
|
+
envelope = d.instance.tc.channel.queue[0]
|
492
|
+
assert_not_nil envelope.tags
|
493
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
494
|
+
assert_nil envelope.tags["ai.cloud.roleInstance"]
|
495
|
+
end
|
496
|
+
|
497
|
+
test 'multiple context tag keys' do
|
498
|
+
config = %[
|
499
|
+
instrumentation_key ikey
|
500
|
+
context_tag_sources {
|
501
|
+
"ai.cloud.role": "kubernetes_container_name",
|
502
|
+
"ai.cloud.roleInstance": "$.docker.container_id"
|
503
|
+
}
|
504
|
+
]
|
505
|
+
d = create_driver config
|
506
|
+
|
507
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
508
|
+
d.run(default_tag: 'test', shutdown: false) do
|
509
|
+
d.feed(time, {
|
510
|
+
"message" => "my message",
|
511
|
+
"docker" => {
|
512
|
+
"container_id" => "c42c557e1615511dd3a3cb1d6e8f14984464bb0f"
|
513
|
+
},
|
514
|
+
"kubernetes_container_name" => "frontend"
|
515
|
+
})
|
516
|
+
end
|
517
|
+
|
518
|
+
envelope = d.instance.tc.channel.queue[0]
|
519
|
+
assert_not_nil envelope.tags
|
520
|
+
assert_true envelope.tags.length == 3
|
521
|
+
assert_equal "frontend", envelope.tags["ai.cloud.role"]
|
522
|
+
assert_equal "c42c557e1615511dd3a3cb1d6e8f14984464bb0f", envelope.tags["ai.cloud.roleInstance"]
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
sub_test_case 'stringify_properties' do
|
527
|
+
test 'simple data type are not stringified' do
|
528
|
+
plugin = create_driver.instance
|
529
|
+
|
530
|
+
record = {prop1: 1, prop2: true, prop3: "value"}
|
531
|
+
actual = plugin.stringify_properties(record)
|
532
|
+
expected = {prop1: 1, prop2: true, prop3: "value"}
|
533
|
+
assert_equal expected, actual
|
534
|
+
end
|
535
|
+
|
536
|
+
test 'json and array property values are stringified' do
|
537
|
+
plugin = create_driver.instance
|
538
|
+
|
539
|
+
record = {prop1: 1, prop2: [1, 2, 3], prop3: {inner_prop: "value"}}
|
540
|
+
actual = plugin.stringify_properties(record)
|
541
|
+
expected = {prop1: 1, prop2: "[1,2,3]", prop3: "{\"inner_prop\":\"value\"}"}
|
542
|
+
assert_equal expected, actual
|
543
|
+
end
|
544
|
+
|
545
|
+
test 'stringify complicated property value' do
|
546
|
+
plugin = create_driver.instance
|
547
|
+
|
548
|
+
record = {
|
549
|
+
arr: [1, [2, [3, {inner: "value"}]]],
|
550
|
+
obj: {
|
551
|
+
arr: [1, {inarr: "inarr"}],
|
552
|
+
inobj: {
|
553
|
+
ininobj: {
|
554
|
+
prop: "value"
|
555
|
+
},
|
556
|
+
num: 3.14
|
557
|
+
}
|
558
|
+
}
|
559
|
+
}
|
560
|
+
|
561
|
+
actual = plugin.stringify_properties(record)
|
562
|
+
expected = {
|
563
|
+
:arr=> "[1,[2,[3,{\"inner\":\"value\"}]]]",
|
564
|
+
:obj=> "{\"arr\":[1,{\"inarr\":\"inarr\"}],\"inobj\":{\"ininobj\":{\"prop\":\"value\"},\"num\":3.14}}"
|
565
|
+
}
|
566
|
+
assert_equal expected, actual
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-application-insights-freiheit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- freiheit.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: fluentd
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
- - "<"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '2'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '1.0'
|
72
|
+
- - "<"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: application_insights
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.5.5
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.5.5
|
89
|
+
description: Fluentd output plugin for Azure Application Insights.
|
90
|
+
email:
|
91
|
+
- xyz@freiheit.com
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- ".gitignore"
|
97
|
+
- ".travis.yml"
|
98
|
+
- CONTRIBUTING.md
|
99
|
+
- Gemfile
|
100
|
+
- LICENSE
|
101
|
+
- README.md
|
102
|
+
- Rakefile
|
103
|
+
- fluent-plugin-application-insights.gemspec
|
104
|
+
- lib/fluent/plugin/out_application_insights.rb
|
105
|
+
- test/helper.rb
|
106
|
+
- test/plugin/mock_client.rb
|
107
|
+
- test/plugin/test_out_application_insights.rb
|
108
|
+
homepage: https://github.com/dennis-tra/fluent-plugin-application-insights
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.7.7
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: This is a fork of the fluentd output plugin for Azure Application Insights.
|
132
|
+
test_files:
|
133
|
+
- test/helper.rb
|
134
|
+
- test/plugin/mock_client.rb
|
135
|
+
- test/plugin/test_out_application_insights.rb
|