fluent-plugin-zebrium_output 1.57.0
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/.gitignore +3 -0
- data/Gemfile +9 -0
- data/Jenkinsfile +11 -0
- data/README.TXT +1 -0
- data/build.sh +15 -0
- data/config/td-agent.conf +23 -0
- data/fluent-plugin-zebrium_output.gemspec +28 -0
- data/lib/fluent/plugin/out_zebrium.rb +1131 -0
- data/version.txt +1 -0
- metadata +160 -0
@@ -0,0 +1,1131 @@
|
|
1
|
+
require 'fluent/plugin/output'
|
2
|
+
require 'net/https'
|
3
|
+
require 'yajl'
|
4
|
+
require 'httpclient'
|
5
|
+
require 'uri'
|
6
|
+
require 'json'
|
7
|
+
require 'docker'
|
8
|
+
require 'yaml'
|
9
|
+
require 'time'
|
10
|
+
|
11
|
+
$ZLOG_COLLECTOR_VERSION = '1.57.0'
|
12
|
+
|
13
|
+
class PathMappings
|
14
|
+
def initialize
|
15
|
+
@active = false
|
16
|
+
@patterns = Array.new
|
17
|
+
@ids = Hash.new
|
18
|
+
@cfgs = Hash.new
|
19
|
+
@tags = Hash.new
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :active
|
23
|
+
attr_accessor :patterns
|
24
|
+
attr_accessor :ids
|
25
|
+
attr_accessor :cfgs
|
26
|
+
attr_accessor :tags
|
27
|
+
end
|
28
|
+
|
29
|
+
class PodConfig
|
30
|
+
def initialize
|
31
|
+
@cfgs = Hash.new
|
32
|
+
@atime = Time.now()
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :cfgs
|
36
|
+
attr_accessor :atime
|
37
|
+
end
|
38
|
+
|
39
|
+
class PodConfigs
|
40
|
+
def initialize
|
41
|
+
@cfgs = Hash.new
|
42
|
+
end
|
43
|
+
attr_accessor :cfgs
|
44
|
+
end
|
45
|
+
|
46
|
+
class NamespaceToServiceGroup
|
47
|
+
def initialize
|
48
|
+
@active = false
|
49
|
+
@svcgrps = Hash.new
|
50
|
+
end
|
51
|
+
attr_accessor :active
|
52
|
+
attr_accessor :svcgrps
|
53
|
+
end
|
54
|
+
|
55
|
+
class Fluent::Plugin::Zebrium < Fluent::Plugin::Output
|
56
|
+
Fluent::Plugin.register_output('zebrium', self)
|
57
|
+
|
58
|
+
helpers :inject, :formatter, :compat_parameters
|
59
|
+
|
60
|
+
DEFAULT_LINE_FORMAT_TYPE = 'stdout'
|
61
|
+
DEFAULT_FORMAT_TYPE = 'json'
|
62
|
+
DEFAULT_BUFFER_TYPE = "memory"
|
63
|
+
DEFAULT_DEPLOYMENT_NAME = "default"
|
64
|
+
|
65
|
+
config_param :ze_log_collector_url, :string, :default => ""
|
66
|
+
config_param :ze_log_collector_token, :string, :default => ""
|
67
|
+
config_param :ze_log_collector_type, :string, :default => "kubernetes"
|
68
|
+
config_param :ze_host, :string, :default => ""
|
69
|
+
config_param :ze_timezone, :string, :default => ""
|
70
|
+
config_param :ze_deployment_name, :string, :default => ""
|
71
|
+
config_param :ze_host_tags, :string, :default => ""
|
72
|
+
config_param :use_buffer, :bool, :default => true
|
73
|
+
config_param :verify_ssl, :bool, :default => false
|
74
|
+
config_param :ze_send_json, :bool, :default => false
|
75
|
+
config_param :ze_support_data_send_intvl, :integer, :default => 600
|
76
|
+
config_param :log_forwarder_mode, :bool, :default => false
|
77
|
+
config_param :ec2_api_client_timeout_secs, :integer, :default => 1
|
78
|
+
config_param :disable_ec2_meta_data, :bool, :default => true
|
79
|
+
config_param :ze_host_in_logpath, :integer, :default => 0
|
80
|
+
config_param :ze_forward_tag, :string, :default => "ze_forwarded_logs"
|
81
|
+
config_param :ze_path_map_file, :string, :default => ""
|
82
|
+
config_param :ze_ns_svcgrp_map_file, :string, :default => ""
|
83
|
+
config_param :ze_handle_host_as_config, :bool, :default => false
|
84
|
+
|
85
|
+
config_section :format do
|
86
|
+
config_set_default :@type, DEFAULT_LINE_FORMAT_TYPE
|
87
|
+
config_set_default :output_type, DEFAULT_FORMAT_TYPE
|
88
|
+
end
|
89
|
+
|
90
|
+
config_section :buffer do
|
91
|
+
config_set_default :@type, DEFAULT_BUFFER_TYPE
|
92
|
+
config_set_default :chunk_keys, ['time']
|
93
|
+
end
|
94
|
+
|
95
|
+
def initialize
|
96
|
+
super
|
97
|
+
@etc_hostname = ""
|
98
|
+
@k8s_hostname = ""
|
99
|
+
if File.exist?("/mnt/etc/hostname")
|
100
|
+
# Inside fluentd container
|
101
|
+
# In that case that host /etc/hostname is a directory, we will
|
102
|
+
# get empty string (for example, on GKE hosts). We will
|
103
|
+
# try to get hostname from log record from kubernetes.
|
104
|
+
if File.file?("/mnt/etc/hostname")
|
105
|
+
File.open("/mnt/etc/hostname", "r").each do |line|
|
106
|
+
@etc_hostname = line.strip().chomp
|
107
|
+
end
|
108
|
+
end
|
109
|
+
else
|
110
|
+
if File.exist?("/etc/hostname")
|
111
|
+
# Run directly on host
|
112
|
+
File.open("/etc/hostname", "r").each do |line|
|
113
|
+
@etc_hostname = line.strip().chomp
|
114
|
+
end
|
115
|
+
end
|
116
|
+
if @etc_hostname.empty?
|
117
|
+
@etc_hostname = `hostname`.strip().chomp
|
118
|
+
end
|
119
|
+
end
|
120
|
+
# Pod names can have two formats:
|
121
|
+
# 1. <deployment_name>-84ff57c87c-pc6xm
|
122
|
+
# 2. <deployment_name>-pc6xm
|
123
|
+
# We use the following two regext to find deployment name. Ideally we want kubernetes filter
|
124
|
+
# to pass us deployment name, but currently it doesn't.
|
125
|
+
@pod_name_to_deployment_name_regexp_long_compiled = Regexp.compile('(?<deployment_name>[a-z0-9]([-a-z0-9]*))-[a-f0-9]{9,10}-[a-z0-9]{5}')
|
126
|
+
@pod_name_to_deployment_name_regexp_short_compiled = Regexp.compile('(?<deployment_name>[a-z0-9]([-a-z0-9]*))-[a-z0-9]{5}')
|
127
|
+
@stream_tokens = {}
|
128
|
+
@stream_token_req_sent = 0
|
129
|
+
@stream_token_req_success = 0
|
130
|
+
@data_post_sent = 0
|
131
|
+
@data_post_success = 0
|
132
|
+
@support_post_sent = 0
|
133
|
+
@support_post_success = 0
|
134
|
+
@last_support_data_sent = 0
|
135
|
+
end
|
136
|
+
|
137
|
+
def multi_workers_ready?
|
138
|
+
false
|
139
|
+
end
|
140
|
+
|
141
|
+
def prefer_buffered_processing
|
142
|
+
@use_buffer
|
143
|
+
end
|
144
|
+
|
145
|
+
attr_accessor :formatter
|
146
|
+
|
147
|
+
# This method is called before starting.
|
148
|
+
def configure(conf)
|
149
|
+
log.info("out_zebrium::configure() called")
|
150
|
+
compat_parameters_convert(conf, :inject, :formatter)
|
151
|
+
super
|
152
|
+
@formatter = formatter_create
|
153
|
+
@ze_tags = {}
|
154
|
+
kvs = conf.key?('ze_host_tags') ? conf['ze_host_tags'].split(','): []
|
155
|
+
for kv in kvs do
|
156
|
+
ary = kv.split('=')
|
157
|
+
if ary.length != 2 or ary[0].empty? or ary[1].empty?
|
158
|
+
log.error("Invalid tag in ze_host_tags: #{kv}")
|
159
|
+
continue
|
160
|
+
end
|
161
|
+
log.info("add ze_tag[" + ary[0] + "]=" + ary[1])
|
162
|
+
if ary[0] == "ze_deployment_name" and @ze_deployment_name.empty?
|
163
|
+
log.info("Use ze_deployment_name from ze_tags")
|
164
|
+
@ze_deployment_name = ary[1]
|
165
|
+
else
|
166
|
+
@ze_tags[ary[0]] = ary[1]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
if @ze_deployment_name.empty?
|
170
|
+
log.info("Set deployment name to default value " + DEFAULT_DEPLOYMENT_NAME)
|
171
|
+
@ze_deployment_name = DEFAULT_DEPLOYMENT_NAME
|
172
|
+
end
|
173
|
+
|
174
|
+
@path_mappings = PathMappings.new
|
175
|
+
@pod_configs = PodConfigs.new
|
176
|
+
@ns_to_svcgrp_mappings = NamespaceToServiceGroup.new
|
177
|
+
read_path_mappings()
|
178
|
+
read_ns_to_svcgrp_mappings()
|
179
|
+
@file_mappings = {}
|
180
|
+
if @log_forwarder_mode
|
181
|
+
log.info("out_zebrium running in log forwarder mode")
|
182
|
+
else
|
183
|
+
read_file_mappings()
|
184
|
+
if @disable_ec2_meta_data == false
|
185
|
+
ec2_host_meta = get_ec2_host_meta_data()
|
186
|
+
for k in ec2_host_meta.keys do
|
187
|
+
log.info("add ec2 meta data " + k + "=" + ec2_host_meta[k])
|
188
|
+
@ze_tags[k] = ec2_host_meta[k]
|
189
|
+
end
|
190
|
+
else
|
191
|
+
log.info("EC2 meta data collection is disabled")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
@http = HTTPClient.new()
|
195
|
+
if @verify_ssl
|
196
|
+
@http.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
197
|
+
@http.ssl_config.add_trust_ca "/usr/lib/ssl/certs"
|
198
|
+
else
|
199
|
+
@http.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
200
|
+
end
|
201
|
+
@http.connect_timeout = 60
|
202
|
+
@zapi_uri = URI(conf["ze_log_collector_url"])
|
203
|
+
@zapi_token_uri = @zapi_uri.clone
|
204
|
+
@zapi_token_uri.path = @zapi_token_uri.path + "/log/api/v2/token"
|
205
|
+
@zapi_post_uri = @zapi_uri.clone
|
206
|
+
@zapi_post_uri.path = @zapi_post_uri.path + "/log/api/v2/tmpost"
|
207
|
+
@zapi_ingest_uri = @zapi_uri.clone
|
208
|
+
@zapi_ingest_uri.path = @zapi_ingest_uri.path + "/log/api/v2/ingest"
|
209
|
+
@zapi_support_uri = @zapi_uri.clone
|
210
|
+
@zapi_support_uri.path = "/api/v2/support"
|
211
|
+
@auth_token = conf["ze_log_collector_token"]
|
212
|
+
log.info("ze_log_collector_vers=" + $ZLOG_COLLECTOR_VERSION)
|
213
|
+
log.info("ze_log_collector_type=" + @ze_log_collector_type)
|
214
|
+
log.info("ze_deployment_name=" + (conf["ze_deployment_name"].nil? ? "<not set>": conf["ze_deployment_name"]))
|
215
|
+
log.info("log_collector_url=" + conf["ze_log_collector_url"])
|
216
|
+
log.info("etc_hostname=" + @etc_hostname)
|
217
|
+
log.info("ze_forward_tag=" + @ze_forward_tag)
|
218
|
+
log.info("ze_path_map_file=" + @ze_path_map_file)
|
219
|
+
log.info("ze_host_in_logpath=#{@ze_host_in_logpath}")
|
220
|
+
log.info("ze_ns_svcgrp_map_file=" + @ze_ns_svcgrp_map_file)
|
221
|
+
data = {}
|
222
|
+
data['msg'] = "log collector starting"
|
223
|
+
send_support_data(data)
|
224
|
+
end
|
225
|
+
|
226
|
+
# def format(tag, time, record)
|
227
|
+
# record = inject_values_to_record(tag, time, record)
|
228
|
+
# @formatter.format(tag, time, record).chomp + "\n"
|
229
|
+
# end
|
230
|
+
|
231
|
+
def read_ns_to_svcgrp_mappings()
|
232
|
+
if ze_ns_svcgrp_map_file.length() == 0
|
233
|
+
@ns_to_svcgrp_mappings.active = false
|
234
|
+
return
|
235
|
+
end
|
236
|
+
ns_svcgrp_map_file = @ze_ns_svcgrp_map_file
|
237
|
+
if not File.exist?(ns_svcgrp_map_file)
|
238
|
+
log.info(ns_svcgrp_map_file + " ns_svcgrp_map_file does not exist.")
|
239
|
+
@ns_to_svcgrp_mappings.active = false
|
240
|
+
return
|
241
|
+
end
|
242
|
+
@ns_to_svcgrp_mappings.active = true
|
243
|
+
nsj = ""
|
244
|
+
log.info(ns_svcgrp_map_file + " exists, loading namespace to svcgrp maps")
|
245
|
+
file = File.read(ns_svcgrp_map_file)
|
246
|
+
begin
|
247
|
+
nsj = JSON.parse(file)
|
248
|
+
rescue Exception => e
|
249
|
+
log.error(ns_svcgrp_map_file + " does not appear to contain valid JSON: " + e.message)
|
250
|
+
@ns_to_svcgrp_mappings.active = false
|
251
|
+
return
|
252
|
+
end
|
253
|
+
log.info(nsj)
|
254
|
+
nsj.each { |key, value|
|
255
|
+
if( value != "" )
|
256
|
+
@ns_to_svcgrp_mappings.svcgrps.store(key, value)
|
257
|
+
end
|
258
|
+
}
|
259
|
+
if @ns_to_svcgrp_mappings.svcgrps.length() == 0
|
260
|
+
log.error("No ns/svcgrp mappings are defined in "+ns_svcgrp_map_file)
|
261
|
+
@ns_to_svcgrp_mappings.active = false
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def read_path_mappings()
|
266
|
+
if ze_path_map_file.length() == 0
|
267
|
+
return
|
268
|
+
end
|
269
|
+
path_map_cfg_file = @ze_path_map_file
|
270
|
+
if not File.exist?(path_map_cfg_file)
|
271
|
+
log.info(path_map_cfg_file + " does not exist.")
|
272
|
+
@path_mappings.active = false
|
273
|
+
return
|
274
|
+
end
|
275
|
+
@path_mappings.active = true
|
276
|
+
pmj = ""
|
277
|
+
log.info(path_map_cfg_file + " exists, loading path maps")
|
278
|
+
file = File.read(path_map_cfg_file)
|
279
|
+
begin
|
280
|
+
pmj = JSON.parse(file)
|
281
|
+
rescue
|
282
|
+
log.error(path_map_cfg_file+" does not appear to contain valid JSON")
|
283
|
+
@path_mappings.active = false
|
284
|
+
return
|
285
|
+
end
|
286
|
+
log.info(pmj)
|
287
|
+
pmj['mappings'].each { |key, value|
|
288
|
+
if key == 'patterns'
|
289
|
+
# patterns
|
290
|
+
value.each { |pattern|
|
291
|
+
begin
|
292
|
+
re = Regexp.compile(pattern, Regexp::EXTENDED)
|
293
|
+
@path_mappings.patterns.append(re)
|
294
|
+
rescue
|
295
|
+
log.error("Invalid path pattern '" + pattern + "' detected")
|
296
|
+
end
|
297
|
+
}
|
298
|
+
elsif key == 'ids'
|
299
|
+
# ids
|
300
|
+
value.each { |id|
|
301
|
+
@path_mappings.ids.store(id, id)
|
302
|
+
}
|
303
|
+
elsif key == 'configs'
|
304
|
+
# configs
|
305
|
+
value.each { |config|
|
306
|
+
@path_mappings.cfgs.store(config, config)
|
307
|
+
}
|
308
|
+
elsif key == 'tags'
|
309
|
+
# tags
|
310
|
+
value.each { |tag|
|
311
|
+
log.info(@path_mappings.tags)
|
312
|
+
@path_mappings.tags.store(tag, tag)
|
313
|
+
}
|
314
|
+
else
|
315
|
+
log.error("Invalid JSON key '"+key+"' detected")
|
316
|
+
end
|
317
|
+
}
|
318
|
+
if @path_mappings.patterns.length() == 0
|
319
|
+
log.info("No patterns are defined in "+path_map_cfg_file)
|
320
|
+
@path_mappings.active = false
|
321
|
+
elsif @path_mappings.ids.length() == 0 and
|
322
|
+
@path_mappings.cfgs.length() == 0 and
|
323
|
+
@path_mappings.tags.length() == 0
|
324
|
+
log.error("No ids/configs/tag mappings are defined in "+path_map_cfg_file)
|
325
|
+
@path_mappings.active = false
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
def read_file_mappings()
|
331
|
+
file_map_cfg_file = "/etc/td-agent/log-file-map.conf"
|
332
|
+
if not File.exist?(file_map_cfg_file)
|
333
|
+
log.info(file_map_cfg_file + " does not exist")
|
334
|
+
old_file_map_cfg_file = "/etc/zebrium/log-file-map.cfg"
|
335
|
+
if not File.exist?(old_file_map_cfg_file)
|
336
|
+
log.info(old_file_map_cfg_file + " does not exist")
|
337
|
+
return
|
338
|
+
end
|
339
|
+
log.warn(old_file_map_cfg_file + " is obsolete, please move it to " + file_map_cfg_file)
|
340
|
+
file_map_cfg_file = old_file_map_cfg_file
|
341
|
+
end
|
342
|
+
log.info(file_map_cfg_file + " exists")
|
343
|
+
file = File.read(file_map_cfg_file)
|
344
|
+
file_mappings = JSON.parse(file)
|
345
|
+
|
346
|
+
file_mappings['mappings'].each { |item|
|
347
|
+
if item.key?('file') and item['file'].length > 0 and item.key?('alias') and item['alias'].length > 0
|
348
|
+
if item['file'].index(',')
|
349
|
+
log.warn(item['file'] + " in " + file_map_cfg_file + " has comma, alias mapping must be one-to-one mapping ")
|
350
|
+
next
|
351
|
+
end
|
352
|
+
if item['file'].index('*')
|
353
|
+
log.warn(item['file'] + " in " + file_map_cfg_file + " has *, alias mapping must be one-to-one mapping ")
|
354
|
+
next
|
355
|
+
end
|
356
|
+
log.info("Adding mapping " + item['file'] + " => " + item['alias'])
|
357
|
+
@file_mappings[item['file']] = item['alias']
|
358
|
+
end
|
359
|
+
}
|
360
|
+
end
|
361
|
+
|
362
|
+
def map_path_ids(tailed_path, ids, cfgs, tags)
|
363
|
+
if not @path_mappings.active
|
364
|
+
return
|
365
|
+
end
|
366
|
+
@path_mappings.patterns.each { |re|
|
367
|
+
res = re.match(tailed_path)
|
368
|
+
if res
|
369
|
+
captures = res.named_captures
|
370
|
+
captures.each { |key, value|
|
371
|
+
if @path_mappings.ids[key] != nil
|
372
|
+
ids[key] = value
|
373
|
+
end
|
374
|
+
if @path_mappings.cfgs[key] != nil
|
375
|
+
cfgs[key] = value
|
376
|
+
end
|
377
|
+
if @path_mappings.tags[key] != nil
|
378
|
+
tags[key] = value
|
379
|
+
end
|
380
|
+
}
|
381
|
+
end
|
382
|
+
}
|
383
|
+
end
|
384
|
+
|
385
|
+
def get_ec2_host_meta_data()
|
386
|
+
host_meta = {}
|
387
|
+
token = ""
|
388
|
+
client = HTTPClient.new()
|
389
|
+
client.connect_timeout = @ec2_api_client_timeout_secs
|
390
|
+
begin
|
391
|
+
log.info("Getting ec2 api token")
|
392
|
+
resp = client.put('http://169.254.169.254/latest/api/token', :header => {'X-aws-ec2-metadata-token-ttl-seconds' => '21600'})
|
393
|
+
if resp.ok?
|
394
|
+
token = resp.body
|
395
|
+
log.info("Got ec2 host meta token=")
|
396
|
+
else
|
397
|
+
log.info("Failed to get AWS EC2 host meta data API token")
|
398
|
+
end
|
399
|
+
rescue
|
400
|
+
log.info("Exception: failed to get AWS EC2 host meta data API token")
|
401
|
+
return host_meta
|
402
|
+
end
|
403
|
+
|
404
|
+
begin
|
405
|
+
log.info("Calling ec2 instance meta data API")
|
406
|
+
meta_resp = client.get('http://169.254.169.254/latest/meta-data/', :header => {'X-aws-ec2-metadata-token' => token})
|
407
|
+
log.info("Returned from c2 instance meta call")
|
408
|
+
if meta_resp.ok?
|
409
|
+
meta_data_arr = meta_resp.body.split()
|
410
|
+
for k in ['ami-id', 'instance-id', 'instance-type', 'hostname', 'local-hostname', 'local-ipv4', 'mac', 'placement', 'public-hostname', 'public-ipv4'] do
|
411
|
+
if meta_data_arr.include?(k)
|
412
|
+
data_resp = client.get("http://169.254.169.254/latest/meta-data/" + k, :header => {'X-aws-ec2-metadata-token' => token})
|
413
|
+
if data_resp.ok?
|
414
|
+
log.info("#{k}=#{data_resp.body}")
|
415
|
+
host_meta['ec2-' + k] = data_resp.body
|
416
|
+
else
|
417
|
+
log.error("Failed to get meta data with key #{k}")
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
else
|
422
|
+
log.error("host meta data request failed: #{meta_resp}")
|
423
|
+
end
|
424
|
+
rescue
|
425
|
+
log.error("host meta data post request exception")
|
426
|
+
end
|
427
|
+
return host_meta
|
428
|
+
end
|
429
|
+
|
430
|
+
def get_host()
|
431
|
+
host = @k8s_hostname.empty? ? @etc_hostname : @k8s_hostname
|
432
|
+
unless @ze_tags["ze_tag_node"].nil? or @ze_tags["ze_tag_node"].empty?
|
433
|
+
host = @ze_tags["ze_tag_node"]
|
434
|
+
end
|
435
|
+
return host
|
436
|
+
end
|
437
|
+
|
438
|
+
def get_container_meta_data(container_id)
|
439
|
+
meta_data = {}
|
440
|
+
begin
|
441
|
+
container = Docker::Container.get(container_id)
|
442
|
+
json = container.json()
|
443
|
+
meta_data['name'] = json['Name'].sub(/^\//, '')
|
444
|
+
meta_data['image'] = json['Config']['Image']
|
445
|
+
meta_data['labels'] = json['Config']['Labels']
|
446
|
+
return meta_data
|
447
|
+
rescue
|
448
|
+
log.info("Exception: failed to get container (#{container_id} meta data")
|
449
|
+
return nil
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# save kubernetes configues, related to a specifc pod_id for
|
454
|
+
# potential use later for container file-based logs
|
455
|
+
def save_kubernetes_cfgs(cfgs)
|
456
|
+
if (not cfgs.key?("pod_id")) or cfgs.fetch("pod_id").nil?
|
457
|
+
return
|
458
|
+
end
|
459
|
+
pod_id = cfgs["pod_id"]
|
460
|
+
if @pod_configs.cfgs.key?(pod_id)
|
461
|
+
pod_cfg = @pod_configs.cfgs[pod_id]
|
462
|
+
else
|
463
|
+
pod_cfg = PodConfig.new()
|
464
|
+
end
|
465
|
+
pod_cfg.atime = Time.now()
|
466
|
+
# Select which config keys to save.
|
467
|
+
keys = [ "cmdb_name", "namespace_name", "namespace_id", "container_name", "pod_name" ]
|
468
|
+
for k in keys do
|
469
|
+
if cfgs.key?(k) and not cfgs.fetch(k).nil?
|
470
|
+
pod_cfg.cfgs[k] = cfgs[k]
|
471
|
+
end
|
472
|
+
end
|
473
|
+
@pod_configs.cfgs[pod_id]=pod_cfg
|
474
|
+
end
|
475
|
+
|
476
|
+
# If the current configuration has a pod_id matching one of the
|
477
|
+
# previously stored ones any associated k8s config info will be
|
478
|
+
# added.
|
479
|
+
def add_kubernetes_cfgs_for_pod_id(in_cfgs)
|
480
|
+
if (not in_cfgs.key?("pod_id")) or in_cfgs.fetch("pod_id").nil?
|
481
|
+
return in_cfgs
|
482
|
+
end
|
483
|
+
pod_id = in_cfgs["pod_id"]
|
484
|
+
|
485
|
+
if not @pod_configs.cfgs.key?(pod_id)
|
486
|
+
return in_cfgs
|
487
|
+
end
|
488
|
+
pod_cfgs = @pod_configs.cfgs(pod_id)
|
489
|
+
|
490
|
+
# Ruby times are UNIX time in seconds. Toss this if unused for
|
491
|
+
# 10 minutes as it may be outdated
|
492
|
+
if Time.now() - pod_cfgs.atime > 60*10
|
493
|
+
@pod_configs.cfgs.delete(pod_id)
|
494
|
+
# while paying the cost, do a quick check for old entries
|
495
|
+
@pod_configs.cfgs.each do |pod_id, cfg|
|
496
|
+
if Time.now() - cfg.atime > 60*10
|
497
|
+
@pod_configs.cfgs.delete(pod_id)
|
498
|
+
break
|
499
|
+
end
|
500
|
+
end
|
501
|
+
return in_cfgs
|
502
|
+
end
|
503
|
+
|
504
|
+
pod_cfgs.atime = Time.now()
|
505
|
+
pod_cfgs.cfgs.each do |key, value|
|
506
|
+
in_cfgs[key] = value
|
507
|
+
end
|
508
|
+
return in_cfgs
|
509
|
+
end
|
510
|
+
|
511
|
+
def get_request_headers(chunk_tag, record)
|
512
|
+
headers = {}
|
513
|
+
ids = {}
|
514
|
+
cfgs = {}
|
515
|
+
tags = {}
|
516
|
+
|
517
|
+
# Sometimes 'record' appears to be a simple number, which causes an exception when
|
518
|
+
# used as a hash. Until the underlying issue is addressed detect this and log.
|
519
|
+
if record.class.name != "Hash" or not record.respond_to?(:key?)
|
520
|
+
log.error("Record is not a hash, unable to process (class: ${record.class.name}).")
|
521
|
+
return false, nil, nil
|
522
|
+
end
|
523
|
+
|
524
|
+
if record.key?("docker") and not record.fetch("docker").nil?
|
525
|
+
container_id = record["docker"]["container_id"]
|
526
|
+
if record.key?("kubernetes") and not record.fetch("kubernetes").nil?
|
527
|
+
cfgs["container_id"] = container_id
|
528
|
+
else
|
529
|
+
ids["container_id"] = container_id
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
is_container_log = true
|
534
|
+
log_type = ""
|
535
|
+
forwarded_log = false
|
536
|
+
user_mapping = false
|
537
|
+
fpath = ""
|
538
|
+
override_deployment = ""
|
539
|
+
override_deployment_from_ns_svcgrp_map = ""
|
540
|
+
|
541
|
+
record_host = ""
|
542
|
+
if record.key?("host") and not record["host"].empty?
|
543
|
+
record_host = record["host"]
|
544
|
+
end
|
545
|
+
has_container_keys = false
|
546
|
+
if record.key?("container_id") and record.key?("container_name")
|
547
|
+
has_container_keys = true
|
548
|
+
end
|
549
|
+
if chunk_tag =~ /^sysloghost\./ or chunk_tag =~ /^#{ze_forward_tag}\./
|
550
|
+
if record_host.empty? and ze_host_in_logpath > 0 and record.key?("tailed_path")
|
551
|
+
tailed_path = record["tailed_path"]
|
552
|
+
path_components = tailed_path.split("/")
|
553
|
+
if path_components.length() < ze_host_in_logpath
|
554
|
+
log.info("Cannot find host at index #{ze_host_in_logpath} in '#{tailed_path}'")
|
555
|
+
else
|
556
|
+
# note .split has empty first element from initial '/'
|
557
|
+
record_host = path_components[ze_host_in_logpath]
|
558
|
+
end
|
559
|
+
end
|
560
|
+
log_type = "syslog"
|
561
|
+
forwarded_log = true
|
562
|
+
logbasename = "syslog"
|
563
|
+
ids["app"] = logbasename
|
564
|
+
ids["host"] = record_host
|
565
|
+
is_container_log = false
|
566
|
+
elsif record.key?("kubernetes") and not record.fetch("kubernetes").nil?
|
567
|
+
kubernetes = record["kubernetes"]
|
568
|
+
if kubernetes.key?("namespace_name") and not kubernetes.fetch("namespace_name").nil?
|
569
|
+
namespace = kubernetes.fetch("namespace_name")
|
570
|
+
if namespace.casecmp?("orphaned") or namespace.casecmp?(".orphaned")
|
571
|
+
return false, nil, nil
|
572
|
+
end
|
573
|
+
end
|
574
|
+
fpath = kubernetes["container_name"]
|
575
|
+
keys = [ "namespace_name", "host", "container_name" ]
|
576
|
+
for k in keys do
|
577
|
+
if kubernetes.key?(k) and not kubernetes.fetch(k).nil?
|
578
|
+
ids[k] = kubernetes[k]
|
579
|
+
if k == "host" and @k8s_hostname.empty?
|
580
|
+
@k8s_hostname = kubernetes[k]
|
581
|
+
end
|
582
|
+
# Requirement for ZS-2185 add cmdb_role, based on namespace_name
|
583
|
+
if k == "namespace_name"
|
584
|
+
cfgs["cmdb_role"] = kubernetes[k].gsub("-","_")
|
585
|
+
if @ns_to_svcgrp_mappings.active
|
586
|
+
if @ns_to_svcgrp_mappings.svcgrps.key?(kubernetes[k]) and not @ns_to_svcgrp_mappings.svcgrps.fetch(kubernetes[k]).nil?
|
587
|
+
override_deployment_from_ns_svcgrp_map = @ns_to_svcgrp_mappings.svcgrps[kubernetes[k]]
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
for pattern in [ @pod_name_to_deployment_name_regexp_long_compiled, @pod_name_to_deployment_name_regexp_short_compiled ] do
|
595
|
+
match_data = kubernetes["pod_name"].match(pattern)
|
596
|
+
if match_data
|
597
|
+
ids["deployment_name"] = match_data["deployment_name"]
|
598
|
+
break
|
599
|
+
end
|
600
|
+
end
|
601
|
+
keys = [ "namespace_id", "container_name", "pod_name", "pod_id", "container_image", "container_image_id" ]
|
602
|
+
for k in keys do
|
603
|
+
if kubernetes.key?(k) and not kubernetes.fetch(k).nil?
|
604
|
+
cfgs[k] = kubernetes[k]
|
605
|
+
end
|
606
|
+
end
|
607
|
+
unless kubernetes["labels"].nil?
|
608
|
+
cfgs.merge!(kubernetes["labels"])
|
609
|
+
end
|
610
|
+
# At this point k8s config should be set. Save these so a subsequent file-log
|
611
|
+
# record for the same pod_id can use them.
|
612
|
+
save_kubernetes_cfgs(cfgs)
|
613
|
+
unless kubernetes["namespace_annotations"].nil?
|
614
|
+
tags = kubernetes["namespace_annotations"]
|
615
|
+
for t in tags.keys
|
616
|
+
if t == "zebrium.com/ze_service_group" and not tags[t].empty?
|
617
|
+
override_deployment = tags[t]
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
unless kubernetes["annotations"].nil?
|
623
|
+
tags = kubernetes["annotations"]
|
624
|
+
for t in tags.keys
|
625
|
+
if t == "zebrium.com/ze_logtype" and not tags[t].empty?
|
626
|
+
user_mapping = true
|
627
|
+
logbasename = tags[t]
|
628
|
+
end
|
629
|
+
if t == "zebrium.com/ze_service_group" and not tags[t].empty?
|
630
|
+
override_deployment = tags[t]
|
631
|
+
end
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
unless kubernetes["labels"].nil?
|
636
|
+
for k in kubernetes["labels"].keys
|
637
|
+
if k == "zebrium.com/ze_logtype" and not kubernetes["labels"][k].empty?
|
638
|
+
user_mapping = true
|
639
|
+
logbasename = kubernetes["labels"][k]
|
640
|
+
end
|
641
|
+
if k == "zebrium.com/ze_service_group" and not kubernetes["labels"][k].empty?
|
642
|
+
override_deployment = kubernetes["labels"][k]
|
643
|
+
end
|
644
|
+
end
|
645
|
+
end
|
646
|
+
if not user_mapping
|
647
|
+
logbasename = kubernetes["container_name"]
|
648
|
+
end
|
649
|
+
elsif chunk_tag =~ /^containers\./
|
650
|
+
if record.key?("tailed_path")
|
651
|
+
fpath = record["tailed_path"]
|
652
|
+
fname = File.basename(fpath)
|
653
|
+
ary = fname.split('-')
|
654
|
+
container_id = ""
|
655
|
+
if ary.length == 2
|
656
|
+
container_id = ary[0]
|
657
|
+
cm = get_container_meta_data(container_id)
|
658
|
+
if cm.nil?
|
659
|
+
return false, headers, nil
|
660
|
+
end
|
661
|
+
cfgs["container_id"] = container_id
|
662
|
+
cfgs["container_name"] = cm['name']
|
663
|
+
labels = cm['labels']
|
664
|
+
for k in labels.keys do
|
665
|
+
cfgs[k] = labels[k]
|
666
|
+
if k == "zebrium.com/ze_logtype" and not labels[k].empty?
|
667
|
+
user_mapping = true
|
668
|
+
logbasename = labels[k]
|
669
|
+
end
|
670
|
+
if k == "zebrium.com/ze_service_group" and not labels[k].empty?
|
671
|
+
override_deployment = labels[k]
|
672
|
+
end
|
673
|
+
end
|
674
|
+
if not user_mapping
|
675
|
+
logbasename = cm['name']
|
676
|
+
end
|
677
|
+
ids["app"] = logbasename
|
678
|
+
cfgs["image"] = cm['image']
|
679
|
+
else
|
680
|
+
log.error("Wrong container log file: ", fpath)
|
681
|
+
end
|
682
|
+
else
|
683
|
+
log.error("Missing tailed_path on logs with containers.* tag")
|
684
|
+
end
|
685
|
+
elsif has_container_keys
|
686
|
+
logbasename = record['container_name'].sub(/^\//, '')
|
687
|
+
ids["app"] = logbasename
|
688
|
+
cfgs["container_id"] = record['container_id']
|
689
|
+
cfgs["container_name"] = logbasename
|
690
|
+
else
|
691
|
+
is_container_log = false
|
692
|
+
if record.key?("tailed_path")
|
693
|
+
fpath = record["tailed_path"]
|
694
|
+
fbname = File.basename(fpath, ".*")
|
695
|
+
if @file_mappings.key?(fpath)
|
696
|
+
logbasename = @file_mappings[fpath]
|
697
|
+
user_mapping = true
|
698
|
+
ids["ze_logname"] = fbname
|
699
|
+
else
|
700
|
+
logbasename = fbname.split('.')[0]
|
701
|
+
if logbasename != fbname
|
702
|
+
ids["ze_logname"] = fbname
|
703
|
+
end
|
704
|
+
end
|
705
|
+
elsif record.key?("_SYSTEMD_UNIT")
|
706
|
+
logbasename = record["_SYSTEMD_UNIT"].gsub(/\.service$/, '')
|
707
|
+
elsif chunk_tag =~ /^k8s\.events/
|
708
|
+
logbasename = "zk8s-events"
|
709
|
+
elsif chunk_tag =~ /^ztcp\.events\./
|
710
|
+
ids["host"] = record_host.empty? ? "ztcp_host": record["host"]
|
711
|
+
logbasename = record["logbasename"] ? record["logbasename"] : "ztcp_stream"
|
712
|
+
forwarded_log = true
|
713
|
+
log_type = "tcp_forward"
|
714
|
+
elsif chunk_tag =~ /^zhttp\.events\./
|
715
|
+
ids["host"] = record_host.empty? ? "ztttp_host" : record["host"]
|
716
|
+
logbasename = record["logbasename"] ? record["logbasename"] : "zhttp_stream"
|
717
|
+
forwarded_log = true
|
718
|
+
log_type = "http_forward"
|
719
|
+
else
|
720
|
+
# Default goes to zlog-collector. Usually there are fluentd generated message
|
721
|
+
# and our own log messages
|
722
|
+
# for these generic messages, we will send as json messages
|
723
|
+
return true, {}, nil
|
724
|
+
end
|
725
|
+
ids["app"] = logbasename
|
726
|
+
end
|
727
|
+
cfgs["ze_file_path"] = fpath
|
728
|
+
if not ids.key?("host") or ids.fetch("host").nil?
|
729
|
+
if record_host.empty?
|
730
|
+
ids["host"] = get_host()
|
731
|
+
else
|
732
|
+
ids["host"] = record_host
|
733
|
+
end
|
734
|
+
end
|
735
|
+
unless @ze_deployment_name.empty?
|
736
|
+
ids["ze_deployment_name"] = @ze_deployment_name
|
737
|
+
end
|
738
|
+
unless override_deployment_from_ns_svcgrp_map.empty?
|
739
|
+
log.debug("Updating ze_deployment_name ns_svcgrp_map '#{override_deployment_from_ns_svcgrp_map}'")
|
740
|
+
ids["ze_deployment_name"] = override_deployment_from_ns_svcgrp_map
|
741
|
+
end
|
742
|
+
unless override_deployment.empty?
|
743
|
+
log.debug("Updating ze_deployment_name to '#{override_deployment}'")
|
744
|
+
ids["ze_deployment_name"] = override_deployment
|
745
|
+
end
|
746
|
+
for k in @ze_tags.keys do
|
747
|
+
tags[k] = @ze_tags[k]
|
748
|
+
end
|
749
|
+
tags["fluentd_tag"] = chunk_tag
|
750
|
+
|
751
|
+
id_key = ""
|
752
|
+
keys = ids.keys.sort
|
753
|
+
keys.each do |k|
|
754
|
+
if ids.key?(k)
|
755
|
+
if id_key.empty?
|
756
|
+
id_key = k + "=" + ids[k]
|
757
|
+
else
|
758
|
+
id_key = id_key + "," + k + "=" + ids[k]
|
759
|
+
end
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
if record.key?("tailed_path")
|
764
|
+
map_path_ids(record["tailed_path"], ids, cfgs, tags)
|
765
|
+
add_kubernetes_cfgs_for_pod_id(cfgs)
|
766
|
+
end
|
767
|
+
|
768
|
+
# host should be handled as a config element instead of an id.
|
769
|
+
# This is used when host changes frequently, causing issues with
|
770
|
+
# detection. The actual host is stored in the cfgs metadata, and
|
771
|
+
# a constant is stored in the ids metadata.
|
772
|
+
# Note that a host entry must be present in ids for correct backend
|
773
|
+
# processing, it is simply a constant at this point.
|
774
|
+
if ze_handle_host_as_config && ids.key?("host")
|
775
|
+
cfgs["host"] = ids["host"]
|
776
|
+
ids["host"] = "host_in_config"
|
777
|
+
end
|
778
|
+
|
779
|
+
has_stream_token = false
|
780
|
+
if @stream_tokens.key?(id_key)
|
781
|
+
# Make sure there is no meta data change. If there is change, new stream token
|
782
|
+
# must be requested.
|
783
|
+
cfgs_tags_match = true
|
784
|
+
if (cfgs.length == @stream_tokens[id_key]['cfgs'].length &&
|
785
|
+
tags.length == @stream_tokens[id_key]['tags'].length)
|
786
|
+
@stream_tokens[id_key]['cfgs'].keys.each do |k|
|
787
|
+
old_cfg = @stream_tokens[id_key]['cfgs'][k]
|
788
|
+
if old_cfg != cfgs[k]
|
789
|
+
log.info("Stream " + id_key + " config has changed: old " + old_cfg + ", new " + cfgs[k])
|
790
|
+
cfgs_tags_match = false
|
791
|
+
break
|
792
|
+
end
|
793
|
+
end
|
794
|
+
@stream_tokens[id_key]['tags'].keys.each do |k|
|
795
|
+
old_tag = @stream_tokens[id_key]['tags'][k]
|
796
|
+
if old_tag != tags[k]
|
797
|
+
log.info("Stream " + id_key + " config has changed: old " + old_tag + ", new " + tags[k])
|
798
|
+
cfgs_tags_match = false
|
799
|
+
break
|
800
|
+
end
|
801
|
+
end
|
802
|
+
else
|
803
|
+
log.info("Stream " + id_key + " number of config or tag has changed")
|
804
|
+
cfgs_tags_match = false
|
805
|
+
end
|
806
|
+
if cfgs_tags_match
|
807
|
+
has_stream_token = true
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
if has_stream_token
|
812
|
+
stream_token = @stream_tokens[id_key]["token"]
|
813
|
+
else
|
814
|
+
log.info("Request new stream token with key " + id_key)
|
815
|
+
stream_token = get_stream_token(ids, cfgs, tags, logbasename, is_container_log, user_mapping,
|
816
|
+
log_type, forwarded_log)
|
817
|
+
@stream_tokens[id_key] = {
|
818
|
+
"token" => stream_token,
|
819
|
+
"cfgs" => cfgs,
|
820
|
+
"tags" => tags
|
821
|
+
}
|
822
|
+
end
|
823
|
+
|
824
|
+
# User can use node label on pod to override "host" meta data from kubernetes
|
825
|
+
headers["authtoken"] = stream_token
|
826
|
+
headers["Content-Type"] = "application/json"
|
827
|
+
headers["Transfer-Encoding"] = "chunked"
|
828
|
+
return true, headers, stream_token
|
829
|
+
end
|
830
|
+
|
831
|
+
def get_stream_token(ids, cfgs, tags, logbasename, is_container_log, user_mapping,
|
832
|
+
log_type, forwarded_log)
|
833
|
+
meta_data = {}
|
834
|
+
meta_data['stream'] = "native"
|
835
|
+
meta_data['logbasename'] = logbasename
|
836
|
+
meta_data['user_logbasename'] = user_mapping
|
837
|
+
meta_data['container_log'] = is_container_log
|
838
|
+
meta_data['log_type'] = log_type
|
839
|
+
meta_data['forwarded_log'] = forwarded_log
|
840
|
+
meta_data['ids'] = ids
|
841
|
+
meta_data['cfgs'] = cfgs
|
842
|
+
meta_data['tags'] = tags
|
843
|
+
meta_data['tz'] = @ze_timezone.empty? ? Time.now.zone : @ze_timezone
|
844
|
+
meta_data['ze_log_collector_vers'] = $ZLOG_COLLECTOR_VERSION + "-" + @ze_log_collector_type
|
845
|
+
|
846
|
+
headers = {}
|
847
|
+
headers["authtoken"] = @auth_token.to_s
|
848
|
+
headers["Content-Type"] = "application/json"
|
849
|
+
headers["Transfer-Encoding"] = "chunked"
|
850
|
+
@stream_token_req_sent = @stream_token_req_sent + 1
|
851
|
+
resp = post_data(@zapi_token_uri, meta_data.to_json, headers)
|
852
|
+
if resp.ok? == false
|
853
|
+
if resp.code == 401
|
854
|
+
raise RuntimeError, "Invalid auth token: #{resp.code} - #{resp.body}"
|
855
|
+
else
|
856
|
+
raise RuntimeError, "Failed to send data to HTTP Source. #{resp.code} - #{resp.body}"
|
857
|
+
end
|
858
|
+
else
|
859
|
+
@stream_token_req_success = @stream_token_req_success + 1
|
860
|
+
end
|
861
|
+
parse_resp = JSON.parse(resp.body)
|
862
|
+
if parse_resp.key?("token")
|
863
|
+
return parse_resp["token"]
|
864
|
+
else
|
865
|
+
raise RuntimeError, "Failed to get stream token from zapi. #{resp.code} - #{resp.body}"
|
866
|
+
end
|
867
|
+
end
|
868
|
+
|
869
|
+
def post_data(uri, data, headers)
|
870
|
+
log.trace("post_data to " + uri.to_s + ": headers: " + headers.to_s)
|
871
|
+
myio = StringIO.new(data)
|
872
|
+
class <<myio
|
873
|
+
undef :size
|
874
|
+
end
|
875
|
+
resp = @http.post(uri, myio, headers)
|
876
|
+
resp
|
877
|
+
end
|
878
|
+
|
879
|
+
def get_k8s_event_str(record)
|
880
|
+
evt_obj = record['object']
|
881
|
+
severity = evt_obj['type']
|
882
|
+
if severity == "Warning"
|
883
|
+
severity = "WARN"
|
884
|
+
end
|
885
|
+
if severity == "Normal"
|
886
|
+
severity = "INFO"
|
887
|
+
end
|
888
|
+
evt_str = "count=" + evt_obj['count'].to_s
|
889
|
+
if record.key?('type')
|
890
|
+
evt_str = evt_str + " type=" + record['type']
|
891
|
+
end
|
892
|
+
if evt_obj.key?('source') and evt_obj['source'].key('host')
|
893
|
+
evt_str = evt_str + " host=" + evt_obj['source']['host']
|
894
|
+
end
|
895
|
+
if evt_obj.key?('metadata')
|
896
|
+
if evt_obj['metadata'].key?('name')
|
897
|
+
evt_str = evt_str + " name=" + evt_obj['metadata']['name']
|
898
|
+
end
|
899
|
+
if evt_obj['metadata'].key('namespace')
|
900
|
+
evt_str = evt_str + " namespace=" + evt_obj['metadata']['namespace']
|
901
|
+
end
|
902
|
+
end
|
903
|
+
if evt_obj.key?('involvedObject')
|
904
|
+
in_obj = evt_obj['involvedObject']
|
905
|
+
for k in ["kind", "namespace", "name", "uid" ] do
|
906
|
+
if in_obj.key?(k)
|
907
|
+
evt_str = evt_str + " " + k + "=" + in_obj[k]
|
908
|
+
end
|
909
|
+
end
|
910
|
+
end
|
911
|
+
if evt_obj.key?('reason')
|
912
|
+
evt_str = evt_str + " reason=" + evt_obj['reason']
|
913
|
+
end
|
914
|
+
# log.info("Event obj:" + evt_obj.to_s)
|
915
|
+
|
916
|
+
if evt_obj.key?('lastTimestamp') and not evt_obj.fetch('lastTimestamp').nil?
|
917
|
+
timeStamp = evt_obj["lastTimestamp"]
|
918
|
+
elsif evt_obj.key('eventTime') and not evt_obj.fetch('eventTime').nil?
|
919
|
+
timeStamp = evt_obj["eventTime"]
|
920
|
+
else
|
921
|
+
timeStamp = ''
|
922
|
+
end
|
923
|
+
msg = timeStamp + " " + severity + " " + evt_str + " msg=" + evt_obj['message'].chomp
|
924
|
+
return msg
|
925
|
+
end
|
926
|
+
|
927
|
+
def process(tag, es)
|
928
|
+
es = inject_values_to_event_stream(tag, es)
|
929
|
+
es.each {|time,record|
|
930
|
+
if record.key?("kubernetes") and not record.fetch("kubernetes").nil?
|
931
|
+
str = ""
|
932
|
+
kubernetes = record["kubernetes"].clone
|
933
|
+
container_name = kubernetes["container_name"]
|
934
|
+
str = str + "container_name=" + container_name + ","
|
935
|
+
host = kubernetes["host"]
|
936
|
+
str = str + "host=" + host + ","
|
937
|
+
kubernetes["labels"].each do |k, v|
|
938
|
+
str = str + "label:" + k + "=" + v + ","
|
939
|
+
end
|
940
|
+
str = str + "\n"
|
941
|
+
end
|
942
|
+
}
|
943
|
+
end
|
944
|
+
|
945
|
+
def prepare_support_data()
|
946
|
+
data = {}
|
947
|
+
data['stream_token_req_sent'] = @stream_token_req_sent
|
948
|
+
data['stream_token_req_success'] = @stream_token_req_success
|
949
|
+
data['data_post_sent'] = @data_post_sent
|
950
|
+
data['data_post_success'] = @data_post_success
|
951
|
+
data['support_post_sent'] = @support_post_sent
|
952
|
+
data['support_post_success'] = @support_post_success
|
953
|
+
return data
|
954
|
+
end
|
955
|
+
|
956
|
+
def post_message_data(send_json, headers, messages)
|
957
|
+
@data_post_sent = @data_post_sent + 1
|
958
|
+
if send_json
|
959
|
+
req = {}
|
960
|
+
req['log_type'] = 'generic'
|
961
|
+
req['messages'] = messages
|
962
|
+
headers = {}
|
963
|
+
headers["authtoken"] = @auth_token
|
964
|
+
headers["Content-Type"] = "application/json"
|
965
|
+
resp = post_data(@zapi_ingest_uri, req.to_json, headers)
|
966
|
+
if resp.ok? == false
|
967
|
+
log.error("Server ingest API return error: code #{resp.code} - #{resp.body}")
|
968
|
+
else
|
969
|
+
@data_post_success = @data_post_success + 1
|
970
|
+
end
|
971
|
+
else
|
972
|
+
resp = post_data(@zapi_post_uri, messages.join("\n") + "\n", headers)
|
973
|
+
if resp.ok? == false
|
974
|
+
if resp.code == 401
|
975
|
+
# Our stream token becomes invalid for some reason, have to acquire new one.
|
976
|
+
# Usually this only happens in testing when server gets recreated.
|
977
|
+
# There is no harm to clear all stream tokens.
|
978
|
+
log.error("Server says stream token is invalid: #{resp.code} - #{resp.body}")
|
979
|
+
log.error("Delete all stream tokens")
|
980
|
+
@stream_tokens = {}
|
981
|
+
raise RuntimeError, "Delete stream token, and retry"
|
982
|
+
else
|
983
|
+
raise RuntimeError, "Failed to send data to HTTP Source. #{resp.code} - #{resp.body}"
|
984
|
+
end
|
985
|
+
else
|
986
|
+
@data_post_success = @data_post_success + 1
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|
990
|
+
|
991
|
+
def write(chunk)
|
992
|
+
epoch = Time.now.to_i
|
993
|
+
if epoch - @last_support_data_sent > @ze_support_data_send_intvl
|
994
|
+
data = prepare_support_data()
|
995
|
+
send_support_data(data)
|
996
|
+
@last_support_data_sent = epoch
|
997
|
+
end
|
998
|
+
tag = chunk.metadata.tag
|
999
|
+
messages_list = {}
|
1000
|
+
log.trace("out_zebrium: write() called tag=", tag)
|
1001
|
+
|
1002
|
+
headers = {}
|
1003
|
+
messages = []
|
1004
|
+
num_records = 0
|
1005
|
+
send_json = false
|
1006
|
+
host = ''
|
1007
|
+
meta_data = {}
|
1008
|
+
last_stoken = {}
|
1009
|
+
last_headers = {}
|
1010
|
+
chunk.each do |entry|
|
1011
|
+
record = entry[1]
|
1012
|
+
if @ze_send_json == false
|
1013
|
+
if entry[1].nil?
|
1014
|
+
log.warn("nil detected, ignoring remainder of chunk")
|
1015
|
+
return
|
1016
|
+
end
|
1017
|
+
should_send, headers, cur_stoken = get_request_headers(tag, record)
|
1018
|
+
if should_send == false
|
1019
|
+
return
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
# get_request_headers() returns empty header, it means
|
1024
|
+
# we should send json message to server
|
1025
|
+
if headers.empty? or @ze_send_json
|
1026
|
+
send_json = true
|
1027
|
+
if host.empty?
|
1028
|
+
if record.key?("host") and not record["host"].empty?
|
1029
|
+
host = record["host"]
|
1030
|
+
else
|
1031
|
+
host = get_host()
|
1032
|
+
end
|
1033
|
+
meta_data['collector'] = $ZLOG_COLLECTOR_VERSION
|
1034
|
+
meta_data['host'] = host
|
1035
|
+
meta_data['ze_deployment_name'] = @ze_deployment_name
|
1036
|
+
meta_data['tags'] = @ze_tags.dup
|
1037
|
+
meta_data['tags']['fluentd_tag'] = tag
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
if num_records == 0
|
1042
|
+
last_stoken = cur_stoken
|
1043
|
+
last_headers = headers
|
1044
|
+
elsif last_stoken != cur_stoken
|
1045
|
+
log.info("Streamtoken changed in chunk, num_records="+num_records.to_s)
|
1046
|
+
post_message_data(send_json, last_headers, messages)
|
1047
|
+
messages = []
|
1048
|
+
last_stoken = cur_stoken
|
1049
|
+
last_headers = headers
|
1050
|
+
num_records = 0
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
if entry[0].nil?
|
1054
|
+
epoch_ms = (Time.now.strftime('%s.%3N').to_f * 1000).to_i
|
1055
|
+
else
|
1056
|
+
epoch_ms = (entry[0].to_f * 1000).to_i
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
if send_json
|
1060
|
+
m = {}
|
1061
|
+
m['meta'] = meta_data
|
1062
|
+
m['line'] = record
|
1063
|
+
m['line']['timestamp'] = epoch_ms
|
1064
|
+
begin
|
1065
|
+
json_str = m.to_json
|
1066
|
+
rescue Encoding::UndefinedConversionError
|
1067
|
+
json_str = m.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
1068
|
+
end
|
1069
|
+
messages.push(json_str)
|
1070
|
+
else
|
1071
|
+
msg_key = nil
|
1072
|
+
if not tag =~ /^k8s\.events/
|
1073
|
+
# journald use key "MESSAGE" for log message
|
1074
|
+
for k in ["log", "message", "LOG", "MESSAGE" ]
|
1075
|
+
if record.key?(k) and not record.fetch(k).nil?
|
1076
|
+
msg_key = k
|
1077
|
+
break
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
if msg_key.nil?
|
1081
|
+
next
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
if tag =~ /^k8s\.events/ and record.key?('object') and record['object']['kind'] == "Event"
|
1086
|
+
line = "ze_tm=" + epoch_ms.to_s + ",msg=" + get_k8s_event_str(record)
|
1087
|
+
else
|
1088
|
+
line = "ze_tm=" + epoch_ms.to_s + ",msg=" + record[msg_key].chomp
|
1089
|
+
end
|
1090
|
+
messages.push(line)
|
1091
|
+
end
|
1092
|
+
num_records += 1
|
1093
|
+
end
|
1094
|
+
# Post remaining messages, if any
|
1095
|
+
if num_records == 0
|
1096
|
+
log.trace("Chunk has no record, no data to post")
|
1097
|
+
return
|
1098
|
+
end
|
1099
|
+
post_message_data(send_json, headers, messages)
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
def send_support_data(data)
|
1103
|
+
meta_data = {}
|
1104
|
+
meta_data['collector_vers'] = $ZLOG_COLLECTOR_VERSION
|
1105
|
+
meta_data['host'] = @etc_hostname
|
1106
|
+
meta_data['data'] = data
|
1107
|
+
|
1108
|
+
headers = {}
|
1109
|
+
headers["Authorization"] = "Token " + @auth_token.to_s
|
1110
|
+
headers["Content-Type"] = "application/json"
|
1111
|
+
headers["Transfer-Encoding"] = "chunked"
|
1112
|
+
@support_post_sent = @support_post_sent + 1
|
1113
|
+
resp = post_data(@zapi_support_uri, meta_data.to_json, headers)
|
1114
|
+
if resp.ok? == false
|
1115
|
+
log.error("Failed to send data to HTTP Source. #{resp.code} - #{resp.body}")
|
1116
|
+
else
|
1117
|
+
@support_post_success = @support_post_success + 1
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
# This method is called when starting.
|
1122
|
+
def start
|
1123
|
+
super
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
# This method is called when shutting down.
|
1127
|
+
def shutdown
|
1128
|
+
super
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
end
|