fluent-plugin-lm-logs 1.2.3 → 1.2.5
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 +4 -4
- data/fluent-plugin-lm-logs.gemspec +1 -1
- data/lib/fluent/plugin/environment_detector.rb +201 -0
- data/lib/fluent/plugin/out_lm.rb +44 -1
- data/lib/fluent/plugin/version.rb +1 -1
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6acf353e0cbb1783eb22109eb555acad23540bade16fbca72a24bf96a030b906
|
4
|
+
data.tar.gz: f87a8c226c61db34ee0dd2b5b5fe2e77ab7e1f2f51bf9b9ac2e5e378dbacb714
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 916a3d78c9c0ddaed1b9ed4fb943c61f9132cfffe97782339c0d7afa9d38cde0949251946264b167d5deb9f69752711f4d8a21ac57fb26b60eeafe3d1481d2bf
|
7
|
+
data.tar.gz: cd7f8a0388cf9f81c79638313bddeb4d01e69f3f072b803cd11d39f7b3dc5df7a9f8165fd225fc0cd8a938689ab6d1f92304746a97ea6a136dd55354876696e4
|
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.metadata["source_code_uri"] = "https://github.com/logicmonitor/lm-logs-fluentd"
|
22
22
|
spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/lm-logs-fluentd"
|
23
23
|
|
24
|
-
spec.files = [".gitignore", "Gemfile", "LICENSE", "README.md", "Rakefile", "fluent-plugin-lm-logs.gemspec", "lib/fluent/plugin/version.rb", "lib/fluent/plugin/out_lm.rb"]
|
24
|
+
spec.files = [".gitignore", "Gemfile", "LICENSE", "README.md", "Rakefile", "fluent-plugin-lm-logs.gemspec", "lib/fluent/plugin/version.rb", "lib/fluent/plugin/out_lm.rb", "lib/fluent/plugin/environment_detector.rb"]
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
spec.required_ruby_version = '>= 2.0.0'
|
27
27
|
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'timeout'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class EnvironmentDetector
|
6
|
+
METADATA_TIMEOUT = 1
|
7
|
+
|
8
|
+
def detect
|
9
|
+
if running_in_kubernetes?
|
10
|
+
{ runtime: 'kubernetes' }.merge(detect_node_info)
|
11
|
+
elsif running_in_docker?
|
12
|
+
{ runtime: 'docker' }.merge(detect_node_info)
|
13
|
+
else
|
14
|
+
detect_host_environment
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_environment(env_info)
|
19
|
+
runtime = env_info[:runtime]
|
20
|
+
provider = env_info[:provider] if env_info.key?(:provider)
|
21
|
+
|
22
|
+
case runtime
|
23
|
+
when 'kubernetes'
|
24
|
+
'Kubernetes/Node'
|
25
|
+
when 'docker'
|
26
|
+
'Docker/Host'
|
27
|
+
when 'vm'
|
28
|
+
case provider&.downcase
|
29
|
+
when 'azure'
|
30
|
+
'Azure/VirtualMachine'
|
31
|
+
when 'aws'
|
32
|
+
'AWS/EC2'
|
33
|
+
when 'gcp'
|
34
|
+
'GCP/ComputeEngine'
|
35
|
+
else
|
36
|
+
'Unknown/VirtualMachine'
|
37
|
+
end
|
38
|
+
when 'physical'
|
39
|
+
os = env_info[:os] || 'UnknownOS'
|
40
|
+
product = env_info[:product] || 'UnknownHardware'
|
41
|
+
"#{os} / #{product}"
|
42
|
+
else
|
43
|
+
'UnknownEnvironment'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def infer_resource_type(record, tag = nil)
|
48
|
+
return record['resource_type'] if record['resource_type']
|
49
|
+
|
50
|
+
host = (record['host'] || record['hostname'] || '').to_s
|
51
|
+
msg = (record['message'] || '').to_s
|
52
|
+
program = (record['syslog_program'] || '').to_s
|
53
|
+
tags = record['tags'] || []
|
54
|
+
tag_down = tag&.downcase || ''
|
55
|
+
|
56
|
+
host_down = host.downcase
|
57
|
+
msg_down = msg.downcase
|
58
|
+
program_down = program.downcase
|
59
|
+
# From tag pattern (case-insensitive)
|
60
|
+
return 'WindowsServer' if tag_down.include?('windows')
|
61
|
+
return 'LinuxServer' if tag_down.include?('linux')
|
62
|
+
return 'Kubernetes/Node' if tag_down.include?('k8s') || tag_down.include?('kubernetes')
|
63
|
+
return 'Docker/Host' if tag_down.include?('docker')
|
64
|
+
|
65
|
+
# Structured metadata
|
66
|
+
return 'Kubernetes/Node' if record.key?('kubernetes')
|
67
|
+
return 'Docker/Host' if record.key?('container_id') || record.dig('docker', 'container_id')
|
68
|
+
return 'AWS/VirtualMachine' if host_down.start_with?('ip-') || msg_down.include?('amazon')
|
69
|
+
return 'GCP/VirtualMachine' if host_down.include?('.c.') || host_down.include?('gcp')
|
70
|
+
return 'Azure/VirtualMachine' if host_down.include?('cloudapp.net') || msg_down.include?('azure')
|
71
|
+
return 'VMware/VirtualMachine' if msg_down.include?('vmware') || host_down.include?('vmware')
|
72
|
+
|
73
|
+
return 'WindowsServer' if record.key?('EventID') || record.key?('ProviderName') || record.key?('Computer')
|
74
|
+
return 'LinuxServer' if record.key?('syslog_facility') || program_down != ''
|
75
|
+
|
76
|
+
return 'Firewall' if program_down.downcase.include?('firewalld') || msg_down.downcase.include?('iptables') || msg_down.include?('blocked by policy')
|
77
|
+
return 'ACMEServer' if host_down.include?('acme') || msg_down.include?('ACME-Request') || tags.include?('acme')
|
78
|
+
return 'WebServer' if msg_down.include?('nginx') || msg_down.include?('apache')
|
79
|
+
return 'DatabaseServer' if msg_down.include?('mysql') || msg_down.include?('postgres') || msg_down.include?('oracle')
|
80
|
+
|
81
|
+
'Unknown'
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def running_in_kubernetes?
|
88
|
+
ENV.key?('KUBERNETES_SERVICE_HOST') || ENV.key?('KUBERNETES_PORT')
|
89
|
+
end
|
90
|
+
|
91
|
+
def running_in_docker?
|
92
|
+
return true if ENV['container'] == 'docker'
|
93
|
+
cgroup = File.read('/proc/1/cgroup') rescue ''
|
94
|
+
return true if cgroup.include?('docker') || cgroup.include?('containerd')
|
95
|
+
File.exist?('/.dockerenv')
|
96
|
+
end
|
97
|
+
|
98
|
+
def detect_host_environment
|
99
|
+
provider_info = detect_cloud_provider
|
100
|
+
return { runtime: 'vm', provider: provider_info[:provider], details: provider_info[:details] } if provider_info
|
101
|
+
|
102
|
+
os = detect_os
|
103
|
+
product = detect_product_info
|
104
|
+
|
105
|
+
if product.downcase.include?('xen hvm domu') && os.downcase.include?('amazon')
|
106
|
+
return { runtime: 'vm', provider: 'aws', details: { os: os, product: product } }
|
107
|
+
end
|
108
|
+
|
109
|
+
{ runtime: 'physical', os: os, product: product }
|
110
|
+
end
|
111
|
+
|
112
|
+
def detect_node_info
|
113
|
+
{ node_os: detect_os, node_product: detect_product_info }
|
114
|
+
end
|
115
|
+
|
116
|
+
def detect_cloud_provider
|
117
|
+
azure_metadata || aws_metadata || gcp_metadata
|
118
|
+
end
|
119
|
+
|
120
|
+
def azure_metadata
|
121
|
+
url = 'http://169.254.169.254/metadata/instance?api-version=2021-02-01'
|
122
|
+
headers = { 'Metadata' => 'true' }
|
123
|
+
response = fetch_metadata(url, headers)
|
124
|
+
return unless response
|
125
|
+
json = JSON.parse(response) rescue {}
|
126
|
+
{
|
127
|
+
provider: 'azure',
|
128
|
+
details: {
|
129
|
+
vm_id: json.dig('compute', 'vmId'),
|
130
|
+
location: json.dig('compute', 'location'),
|
131
|
+
name: json.dig('compute', 'name'),
|
132
|
+
vm_size: json.dig('compute', 'vmSize')
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def aws_metadata
|
138
|
+
url = 'http://169.254.169.254/latest/meta-data/instance-id'
|
139
|
+
response = fetch_metadata(url)
|
140
|
+
return unless response
|
141
|
+
{ provider: 'aws', details: { instance_id: response.strip } }
|
142
|
+
end
|
143
|
+
|
144
|
+
def gcp_metadata
|
145
|
+
url = 'http://169.254.169.254/computeMetadata/v1/instance/id'
|
146
|
+
headers = { 'Metadata-Flavor' => 'Google' }
|
147
|
+
response = fetch_metadata(url, headers)
|
148
|
+
return unless response
|
149
|
+
{ provider: 'gcp', details: { instance_id: response.strip } }
|
150
|
+
end
|
151
|
+
|
152
|
+
def fetch_metadata(url, headers = {}, timeout_sec = METADATA_TIMEOUT)
|
153
|
+
uri = URI(url)
|
154
|
+
Timeout.timeout(timeout_sec) do
|
155
|
+
req = Net::HTTP::Get.new(uri)
|
156
|
+
headers.each { |k, v| req[k] = v }
|
157
|
+
res = Net::HTTP.start(uri.host, uri.port, open_timeout: timeout_sec, read_timeout: timeout_sec) { |http| http.request(req) }
|
158
|
+
return res.body if res.is_a?(Net::HTTPSuccess)
|
159
|
+
end
|
160
|
+
rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, EOFError
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
def detect_os
|
165
|
+
if File.exist?('/etc/os-release')
|
166
|
+
os_info = {}
|
167
|
+
File.foreach('/etc/os-release') do |line|
|
168
|
+
key, value = line.strip.split('=', 2)
|
169
|
+
os_info[key] = value&.gsub('"', '')
|
170
|
+
end
|
171
|
+
"#{os_info['NAME']} #{os_info['VERSION']}"
|
172
|
+
elsif RUBY_PLATFORM.include?('darwin')
|
173
|
+
product_name = `sw_vers -productName`.strip
|
174
|
+
product_version = `sw_vers -productVersion`.strip
|
175
|
+
"#{product_name} #{product_version}"
|
176
|
+
else
|
177
|
+
`uname -a`.strip
|
178
|
+
end
|
179
|
+
rescue
|
180
|
+
'unknown'
|
181
|
+
end
|
182
|
+
|
183
|
+
def detect_product_info
|
184
|
+
if File.exist?('/sys/class/dmi/id/sys_vendor') && File.exist?('/sys/class/dmi/id/product_name')
|
185
|
+
vendor = read_file('/sys/class/dmi/id/sys_vendor')
|
186
|
+
product = read_file('/sys/class/dmi/id/product_name')
|
187
|
+
"#{vendor} #{product}".strip
|
188
|
+
elsif RUBY_PLATFORM.include?('darwin')
|
189
|
+
model = `system_profiler SPHardwareDataType | awk '/Model Identifier/ { print $3 }'`.strip
|
190
|
+
model.empty? ? 'Mac' : model
|
191
|
+
else
|
192
|
+
'unknown'
|
193
|
+
end
|
194
|
+
rescue
|
195
|
+
'unknown'
|
196
|
+
end
|
197
|
+
|
198
|
+
def read_file(path)
|
199
|
+
File.read(path).strip if File.exist?(path)
|
200
|
+
end
|
201
|
+
end
|
data/lib/fluent/plugin/out_lm.rb
CHANGED
@@ -10,6 +10,7 @@ require 'net/http'
|
|
10
10
|
require 'net/http/persistent'
|
11
11
|
require 'net/https'
|
12
12
|
require('zlib')
|
13
|
+
require_relative 'environment_detector'
|
13
14
|
|
14
15
|
require_relative "version"
|
15
16
|
|
@@ -51,6 +52,8 @@ module Fluent
|
|
51
52
|
|
52
53
|
config_param :company_domain , :string, :default => "logicmonitor.com"
|
53
54
|
|
55
|
+
config_param :resource_type, :string, :default => ""
|
56
|
+
|
54
57
|
# Use bearer token for auth.
|
55
58
|
config_param :bearer_token, :string, :default => nil, secret: true
|
56
59
|
|
@@ -81,6 +84,11 @@ module Fluent
|
|
81
84
|
@http_client.override_headers["User-Agent"] = log_source + "/" + LmLogsFluentPlugin::VERSION
|
82
85
|
@url = "https://#{@company_name}.#{@company_domain}/rest/log/ingest"
|
83
86
|
@uri = URI.parse(@url)
|
87
|
+
@detector = EnvironmentDetector.new
|
88
|
+
@environment_info = @detector.detect
|
89
|
+
@local_env_str = format_environment(@environment_info)
|
90
|
+
|
91
|
+
log.info("Environment detected: #{@environment_info}")
|
84
92
|
end
|
85
93
|
|
86
94
|
def configure_auth
|
@@ -166,9 +174,15 @@ module Fluent
|
|
166
174
|
else
|
167
175
|
lm_event["timestamp"] = Time.at(time).utc.to_datetime.rfc3339
|
168
176
|
end
|
169
|
-
|
170
177
|
lm_event["message"] = encode_if_necessary(record["message"])
|
171
178
|
|
179
|
+
resource_type = @resource_type || @detector.infer_resource_type(record, tag)
|
180
|
+
if resource_type.nil? || resource_type.strip.empty? || resource_type == 'Unknown'
|
181
|
+
resource_type = @local_env_str
|
182
|
+
end
|
183
|
+
|
184
|
+
lm_event['_resource.type'] = resource_type
|
185
|
+
|
172
186
|
return lm_event
|
173
187
|
end
|
174
188
|
|
@@ -255,5 +269,34 @@ module Fluent
|
|
255
269
|
end
|
256
270
|
end
|
257
271
|
|
272
|
+
def format_environment(env_info)
|
273
|
+
runtime = env_info[:runtime]
|
274
|
+
provider = env_info[:provider] if env_info.key?(:provider)
|
275
|
+
|
276
|
+
case runtime
|
277
|
+
when 'kubernetes'
|
278
|
+
'Kubernetes/Node'
|
279
|
+
when 'docker'
|
280
|
+
'Docker/Host'
|
281
|
+
when 'vm'
|
282
|
+
case provider&.downcase
|
283
|
+
when 'azure'
|
284
|
+
'Azure/VirtualMachine'
|
285
|
+
when 'aws'
|
286
|
+
'AWS/EC2'
|
287
|
+
when 'gcp'
|
288
|
+
'GCP/ComputeEngine'
|
289
|
+
else
|
290
|
+
'Unknown/VirtualMachine'
|
291
|
+
end
|
292
|
+
when 'physical'
|
293
|
+
os = env_info[:os] || 'UnknownOS'
|
294
|
+
product = env_info[:product] || 'UnknownHardware'
|
295
|
+
"#{os} / #{product}"
|
296
|
+
else
|
297
|
+
'UnknownEnvironment'
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
258
301
|
end
|
259
302
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-lm-logs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LogicMonitor
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: fluentd
|
@@ -57,6 +56,7 @@ files:
|
|
57
56
|
- README.md
|
58
57
|
- Rakefile
|
59
58
|
- fluent-plugin-lm-logs.gemspec
|
59
|
+
- lib/fluent/plugin/environment_detector.rb
|
60
60
|
- lib/fluent/plugin/out_lm.rb
|
61
61
|
- lib/fluent/plugin/version.rb
|
62
62
|
homepage: https://www.logicmonitor.com
|
@@ -65,7 +65,6 @@ licenses:
|
|
65
65
|
metadata:
|
66
66
|
source_code_uri: https://github.com/logicmonitor/lm-logs-fluentd
|
67
67
|
documentation_uri: https://www.rubydoc.info/gems/lm-logs-fluentd
|
68
|
-
post_install_message:
|
69
68
|
rdoc_options: []
|
70
69
|
require_paths:
|
71
70
|
- lib
|
@@ -80,8 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
79
|
- !ruby/object:Gem::Version
|
81
80
|
version: '0'
|
82
81
|
requirements: []
|
83
|
-
rubygems_version: 3.
|
84
|
-
signing_key:
|
82
|
+
rubygems_version: 3.6.7
|
85
83
|
specification_version: 4
|
86
84
|
summary: LogicMonitor logs fluentd output plugin
|
87
85
|
test_files: []
|