fluent-plugin-zebrium_output 1.57.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|