fluent-plugin-lm-logs 1.2.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 745bc2d1d5288affa3b239860d5ee6b00c01df4e718fe21ebab62eec9ef2a211
4
- data.tar.gz: 54b54ed790d7d0c56af9eea61b24024e33755157396fbf71f2ae6bbe700f3e14
3
+ metadata.gz: 6acf353e0cbb1783eb22109eb555acad23540bade16fbca72a24bf96a030b906
4
+ data.tar.gz: f87a8c226c61db34ee0dd2b5b5fe2e77ab7e1f2f51bf9b9ac2e5e378dbacb714
5
5
  SHA512:
6
- metadata.gz: 84f747c7449f169e0283ca639cf7c9460700a93d247b4a52ca633002ffa522f66b12a3ca0d1576c88fe3b65161cd4d26a8a204b893c7b4032141d51196763203
7
- data.tar.gz: cbe53ee7f70aa06e220e99585b4ff5e5a1e826c3d30a264f67bf2675994faeb08d8c78476283456316c37ea8e6da8cae70294b377a920d7eb433257cecb75d0d
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
@@ -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
- lm_event["_resource.type"] = "Fluentd"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LmLogsFluentPlugin
4
- VERSION = '1.2.4'
4
+ VERSION = '1.2.5'
5
5
  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
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: 2024-12-24 00:00:00.000000000 Z
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.5.22
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: []