madhawk57-log 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,297 @@
1
+ require 'fluent/plugin/output'
2
+ require 'fluent/plugin/kafka_plugin_util'
3
+ require "google/cloud/secret_manager"
4
+ require 'kafka'
5
+ require 'fluent/plugin/kafka_producer_ext'
6
+
7
+ module Fluent::Plugin
8
+ class Fluent::Kafka2Output < Output
9
+ Fluent::Plugin.register_output('kafka2', self)
10
+
11
+ helpers :inject, :formatter, :event_emitter, :record_accessor
12
+
13
+ config_param :brokers, :array, :value_type => :string, :default => ['localhost:9092'],
14
+ :desc => <<-DESC
15
+ Set brokers directly:
16
+ <broker1_host>:<broker1_port>,<broker2_host>:<broker2_port>,..
17
+ DESC
18
+ config_param :topic_key, :string, :default => 'topic', :desc => "Field for kafka topic"
19
+ config_param :default_topic, :string, :default => nil,
20
+ :desc => "Default output topic when record doesn't have topic field"
21
+ config_param :message_key_key, :string, :default => 'message_key', :desc => "Field for kafka message key"
22
+ config_param :default_message_key, :string, :default => nil
23
+ config_param :partition_key_key, :string, :default => 'partition_key', :desc => "Field for kafka partition key"
24
+ config_param :default_partition_key, :string, :default => nil
25
+ config_param :partition_key, :string, :default => 'partition', :desc => "Field for kafka partition"
26
+ config_param :default_partition, :integer, :default => nil
27
+ config_param :use_default_for_unknown_topic, :bool, :default => false, :desc => "If true, default_topic is used when topic not found"
28
+ config_param :client_id, :string, :default => 'fluentd'
29
+ config_param :idempotent, :bool, :default => false, :desc => 'Enable idempotent producer'
30
+ config_param :sasl_over_ssl, :bool, :default => true,
31
+ :desc => <<-DESC
32
+ Set to false to prevent SSL strict mode when using SASL authentication
33
+ DESC
34
+ config_param :exclude_partition_key, :bool, :default => false,
35
+ :desc => 'Set true to remove partition key from data'
36
+ config_param :exclude_partition, :bool, :default => false,
37
+ :desc => 'Set true to remove partition from data'
38
+ config_param :exclude_message_key, :bool, :default => false,
39
+ :desc => 'Set true to remove partition key from data'
40
+ config_param :exclude_topic_key, :bool, :default => false,
41
+ :desc => 'Set true to remove topic name key from data'
42
+ config_param :use_event_time, :bool, :default => false, :desc => 'Use fluentd event time for kafka create_time'
43
+ config_param :headers, :hash, default: {}, symbolize_keys: true, value_type: :string,
44
+ :desc => 'Kafka message headers'
45
+ config_param :headers_from_record, :hash, default: {}, symbolize_keys: true, value_type: :string,
46
+ :desc => 'Kafka message headers where the header value is a jsonpath to a record value'
47
+
48
+ config_param :get_kafka_client_log, :bool, :default => false
49
+
50
+ config_param :ignore_exceptions, :array, :default => [], value_type: :string, :desc => "Ignorable exception list"
51
+ config_param :exception_backup, :bool, :default => true, :desc => "Chunk backup flag when ignore exception occured"
52
+
53
+ config_param :connect_timeout, :integer, :default => nil,
54
+ :desc => "The timeout setting for connecting to brokers"
55
+ config_param :socket_timeout, :integer, :default => nil,
56
+ :desc => "The timeout setting for socket connection"
57
+
58
+ # ruby-kafka producer options
59
+ config_param :max_send_retries, :integer, :default => 2,
60
+ :desc => "Number of times to retry sending of messages to a leader."
61
+ config_param :required_acks, :integer, :default => -1,
62
+ :desc => "The number of acks required per request."
63
+ config_param :ack_timeout, :time, :default => nil,
64
+ :desc => "How long the producer waits for acks."
65
+ config_param :compression_codec, :string, :default => nil,
66
+ :desc => <<-DESC
67
+ The codec the producer uses to compress messages.
68
+ Supported codecs depends on ruby-kafka: https://github.com/zendesk/ruby-kafka#compression
69
+ DESC
70
+ config_param :max_send_limit_bytes, :size, :default => nil
71
+ config_param :active_support_notification_regex, :string, :default => nil,
72
+ :desc => <<-DESC
73
+ Add a regular expression to capture ActiveSupport notifications from the Kafka client
74
+ requires activesupport gem - records will be generated under fluent_kafka_stats.**
75
+ DESC
76
+
77
+ config_section :buffer do
78
+ config_set_default :chunk_keys, ["topic"]
79
+ end
80
+ config_section :format do
81
+ config_set_default :@type, 'json'
82
+ end
83
+
84
+ include Fluent::KafkaPluginUtil::SSLSettings
85
+ include Fluent::KafkaPluginUtil::SaslSettings
86
+ include Fluent::KafkaPluginUtil::GsmSettings
87
+
88
+ def initialize
89
+ super
90
+
91
+ @kafka = nil
92
+ end
93
+
94
+ def refresh_client(raise_error = true)
95
+ begin
96
+ logger = @get_kafka_client_log ? log : nil
97
+ g = Fluent::KafkaPluginUtil::GsmSettings
98
+ if @secretName != nil && @projectId != nil
99
+ g.fetch(logger)
100
+ end
101
+ if @scram_mechanism != nil && @username != nil && @password != nil
102
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, connect_timeout: @connect_timeout, socket_timeout: @socket_timeout, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
103
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_client_cert_chain: read_ssl_file(@ssl_client_cert_chain),
104
+ ssl_ca_certs_from_system: @ssl_ca_certs_from_system, sasl_scram_username: @username, sasl_scram_password: @password,
105
+ sasl_scram_mechanism: @scram_mechanism, sasl_over_ssl: @sasl_over_ssl, ssl_verify_hostname: @ssl_verify_hostname)
106
+ elsif @username != nil && @password != nil
107
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, connect_timeout: @connect_timeout, socket_timeout: @socket_timeout, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
108
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_client_cert_chain: read_ssl_file(@ssl_client_cert_chain),
109
+ ssl_ca_certs_from_system: @ssl_ca_certs_from_system, sasl_plain_username: @username, sasl_plain_password: @password, sasl_over_ssl: @sasl_over_ssl,
110
+ ssl_verify_hostname: @ssl_verify_hostname)
111
+ else
112
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, connect_timeout: @connect_timeout, socket_timeout: @socket_timeout, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
113
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_client_cert_chain: read_ssl_file(@ssl_client_cert_chain),
114
+ ssl_ca_certs_from_system: @ssl_ca_certs_from_system, sasl_gssapi_principal: @principal, sasl_gssapi_keytab: @keytab, sasl_over_ssl: @sasl_over_ssl,
115
+ ssl_verify_hostname: @ssl_verify_hostname)
116
+ end
117
+ log.info "initialized kafka producer: #{@client_id}"
118
+ rescue Exception => e
119
+ if raise_error # During startup, error should be reported to engine and stop its phase for safety.
120
+ raise e
121
+ else
122
+ log.error e
123
+ end
124
+ end
125
+ end
126
+
127
+ def configure(conf)
128
+ super
129
+
130
+ if @brokers.size > 0
131
+ @seed_brokers = @brokers
132
+ log.info "brokers has been set: #{@seed_brokers}"
133
+ else
134
+ raise Fluent::Config, 'No brokers specified. Need one broker at least.'
135
+ end
136
+
137
+ formatter_conf = conf.elements('format').first
138
+ unless formatter_conf
139
+ raise Fluent::ConfigError, "<format> section is required."
140
+ end
141
+ unless formatter_conf["@type"]
142
+ raise Fluent::ConfigError, "format/@type is required."
143
+ end
144
+ @formatter_proc = setup_formatter(formatter_conf)
145
+
146
+ if @default_topic.nil?
147
+ if @use_default_for_unknown_topic
148
+ raise Fluent::ConfigError, "default_topic must be set when use_default_for_unknown_topic is true"
149
+ end
150
+ if @chunk_keys.include?('topic') && !@chunk_key_tag
151
+ log.warn "Use 'topic' field of event record for topic but no fallback. Recommend to set default_topic or set 'tag' in buffer chunk keys like <buffer topic,tag>"
152
+ end
153
+ else
154
+ if @chunk_key_tag
155
+ log.warn "default_topic is set. Fluentd's event tag is not used for topic"
156
+ end
157
+ end
158
+
159
+ @producer_opts = {max_retries: @max_send_retries, required_acks: @required_acks, idempotent: @idempotent}
160
+ @producer_opts[:ack_timeout] = @ack_timeout if @ack_timeout
161
+ @producer_opts[:compression_codec] = @compression_codec.to_sym if @compression_codec
162
+ if @active_support_notification_regex
163
+ require 'active_support/notifications'
164
+ require 'active_support/core_ext/hash/keys'
165
+ ActiveSupport::Notifications.subscribe(Regexp.new(@active_support_notification_regex)) do |*args|
166
+ event = ActiveSupport::Notifications::Event.new(*args)
167
+ message = event.payload.respond_to?(:stringify_keys) ? event.payload.stringify_keys : event.payload
168
+ @router.emit("fluent_kafka_stats.#{event.name}", Time.now.to_i, message)
169
+ end
170
+ end
171
+
172
+ @topic_key_sym = @topic_key.to_sym
173
+
174
+ @headers_from_record_accessors = {}
175
+ @headers_from_record.each do |key, value|
176
+ @headers_from_record_accessors[key] = record_accessor_create(value)
177
+ end
178
+ end
179
+
180
+ def multi_workers_ready?
181
+ true
182
+ end
183
+
184
+ def start
185
+ super
186
+ refresh_client
187
+ end
188
+
189
+ def close
190
+ super
191
+ @kafka.close if @kafka
192
+ end
193
+
194
+ def terminate
195
+ super
196
+ @kafka = nil
197
+ end
198
+
199
+ def setup_formatter(conf)
200
+ type = conf['@type']
201
+ case type
202
+ when 'json'
203
+ begin
204
+ require 'oj'
205
+ Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
206
+ Proc.new { |tag, time, record| Oj.dump(record) }
207
+ rescue LoadError
208
+ require 'yajl'
209
+ Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
210
+ end
211
+ when 'ltsv'
212
+ require 'ltsv'
213
+ Proc.new { |tag, time, record| LTSV.dump(record) }
214
+ else
215
+ @formatter = formatter_create(usage: 'kafka-plugin', conf: conf)
216
+ @formatter.method(:format)
217
+ end
218
+ end
219
+
220
+ # TODO: optimize write performance
221
+ def write(chunk)
222
+ tag = chunk.metadata.tag
223
+ topic = (chunk.metadata.variables && chunk.metadata.variables[@topic_key_sym]) || @default_topic || tag
224
+
225
+ messages = 0
226
+ record_buf = nil
227
+
228
+ base_headers = @headers
229
+ mutate_headers = !@headers_from_record_accessors.empty?
230
+
231
+ begin
232
+ producer = @kafka.topic_producer(topic, @producer_opts)
233
+ chunk.msgpack_each { |time, record|
234
+ begin
235
+ record = inject_values_to_record(tag, time, record)
236
+ record.delete(@topic_key) if @exclude_topic_key
237
+ partition_key = (@exclude_partition_key ? record.delete(@partition_key_key) : record[@partition_key_key]) || @default_partition_key
238
+ partition = (@exclude_partition ? record.delete(@partition_key) : record[@partition_key]) || @default_partition
239
+ message_key = (@exclude_message_key ? record.delete(@message_key_key) : record[@message_key_key]) || @default_message_key
240
+
241
+ if mutate_headers
242
+ headers = base_headers.clone
243
+ @headers_from_record_accessors.each do |key, header_accessor|
244
+ headers[key] = header_accessor.call(record)
245
+ end
246
+ else
247
+ headers = base_headers
248
+ end
249
+
250
+ record_buf = @formatter_proc.call(tag, time, record)
251
+ record_buf_bytes = record_buf.bytesize
252
+ if @max_send_limit_bytes && record_buf_bytes > @max_send_limit_bytes
253
+ log.warn "record size exceeds max_send_limit_bytes. Skip event:", :time => time, :record => record
254
+ next
255
+ end
256
+ rescue StandardError => e
257
+ log.warn "unexpected error during format record. Skip broken event:", :error => e.to_s, :error_class => e.class.to_s, :time => time, :record => record
258
+ next
259
+ end
260
+
261
+ log.trace { "message will send to #{topic} with partition_key: #{partition_key}, partition: #{partition}, message_key: #{message_key} and value: #{record_buf}." }
262
+ messages += 1
263
+
264
+ producer.produce(record_buf, key: message_key, partition_key: partition_key, partition: partition, headers: headers,
265
+ create_time: @use_event_time ? Time.at(time) : Time.now)
266
+ }
267
+
268
+ if messages > 0
269
+ log.debug { "#{messages} messages send." }
270
+ producer.deliver_messages
271
+ end
272
+ rescue Kafka::UnknownTopicOrPartition
273
+ if @use_default_for_unknown_topic && topic != @default_topic
274
+ producer.shutdown if producer
275
+ log.warn "'#{topic}' topic not found. Retry with '#{default_topic}' topic"
276
+ topic = @default_topic
277
+ retry
278
+ end
279
+ raise
280
+ end
281
+ rescue Exception => e
282
+ ignore = @ignore_exceptions.include?(e.class.name)
283
+
284
+ log.warn "Send exception occurred: #{e}"
285
+ log.warn "Exception Backtrace : #{e.backtrace.join("\n")}"
286
+ log.warn "Exception ignored in tag : #{tag}" if ignore
287
+ # For safety, refresh client and its producers
288
+ refresh_client(false)
289
+ # raise UnrecoverableError for backup ignored exception chunk
290
+ raise Fluent::UnrecoverableError if ignore && exception_backup
291
+ # Raise exception to retry sendind messages
292
+ raise e unless ignore
293
+ ensure
294
+ producer.shutdown if producer
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,372 @@
1
+ require 'thread'
2
+ require 'fluent/output'
3
+ require 'fluent/plugin/kafka_plugin_util'
4
+
5
+ class Fluent::KafkaOutputBuffered < Fluent::BufferedOutput
6
+ Fluent::Plugin.register_output('kafka_buffered', self)
7
+
8
+ config_param :brokers, :string, :default => 'localhost:9092',
9
+ :desc => <<-DESC
10
+ Set brokers directly:
11
+ <broker1_host>:<broker1_port>,<broker2_host>:<broker2_port>,..
12
+ Brokers: you can choose to use either brokers or zookeeper.
13
+ DESC
14
+ config_param :zookeeper, :string, :default => nil,
15
+ :desc => <<-DESC
16
+ Set brokers via Zookeeper:
17
+ <zookeeper_host>:<zookeeper_port>
18
+ DESC
19
+ config_param :zookeeper_path, :string, :default => '/brokers/ids', :desc => "Path in path for Broker id. Default to /brokers/ids"
20
+
21
+ config_param :topic_key, :string, :default => 'topic', :desc => "Field for kafka topic"
22
+ config_param :default_topic, :string, :default => nil, :desc => "Default output topic when record doesn't have topic field"
23
+ config_param :message_key_key, :string, :default => 'message_key', :desc => "Field for kafka message key"
24
+ config_param :default_message_key, :string, :default => nil
25
+ config_param :partition_key_key, :string, :default => 'partition_key', :desc => "Field for kafka partition key"
26
+ config_param :default_partition_key, :string, :default => nil
27
+ config_param :partition_key, :string, :default => 'partition', :desc => "Field for kafka partition"
28
+ config_param :default_partition, :integer, :default => nil
29
+ config_param :client_id, :string, :default => 'kafka'
30
+ config_param :idempotent, :bool, :default => false, :desc => 'Enable idempotent producer'
31
+ config_param :sasl_over_ssl, :bool, :default => true,
32
+ :desc => <<-DESC
33
+ Set to false to prevent SSL strict mode when using SASL authentication
34
+ DESC
35
+ config_param :output_data_type, :string, :default => 'json',
36
+ :desc => <<-DESC
37
+ Supported format: (json|ltsv|msgpack|attr:<record name>|<formatter name>)
38
+ DESC
39
+ config_param :output_include_tag, :bool, :default => false
40
+ config_param :output_include_time, :bool, :default => false
41
+ config_param :exclude_partition_key, :bool, :default => false,
42
+ :desc => <<-DESC
43
+ Set true to remove partition key from data
44
+ DESC
45
+ config_param :exclude_partition, :bool, :default => false,
46
+ :desc => <<-DESC
47
+ Set true to remove partition from data
48
+ DESC
49
+ config_param :exclude_message_key, :bool, :default => false,
50
+ :desc => <<-DESC
51
+ Set true to remove message key from data
52
+ DESC
53
+ config_param :exclude_topic_key, :bool, :default => false,
54
+ :desc => <<-DESC
55
+ Set true to remove topic name key from data
56
+ DESC
57
+ config_param :use_event_time, :bool, :default => false, :desc => 'Use fluentd event time for kafka create_time'
58
+
59
+ config_param :kafka_agg_max_bytes, :size, :default => 4*1024 #4k
60
+ config_param :kafka_agg_max_messages, :integer, :default => nil
61
+ config_param :get_kafka_client_log, :bool, :default => false
62
+
63
+ # ruby-kafka producer options
64
+ config_param :max_send_retries, :integer, :default => 2,
65
+ :desc => "Number of times to retry sending of messages to a leader."
66
+ config_param :required_acks, :integer, :default => -1,
67
+ :desc => "The number of acks required per request."
68
+ config_param :ack_timeout, :time, :default => nil,
69
+ :desc => "How long the producer waits for acks."
70
+ config_param :compression_codec, :string, :default => nil,
71
+ :desc => <<-DESC
72
+ The codec the producer uses to compress messages.
73
+ Supported codecs depends on ruby-kafka: https://github.com/zendesk/ruby-kafka#compression
74
+ DESC
75
+ config_param :max_send_limit_bytes, :size, :default => nil
76
+ config_param :discard_kafka_delivery_failed, :bool, :default => false
77
+
78
+ config_param :time_format, :string, :default => nil
79
+
80
+ config_param :active_support_notification_regex, :string, :default => nil,
81
+ :desc => <<-DESC
82
+ Add a regular expression to capture ActiveSupport notifications from the Kafka client
83
+ requires activesupport gem - records will be generated under fluent_kafka_stats.**
84
+ DESC
85
+
86
+ config_param :monitoring_list, :array, :default => [],
87
+ :desc => "library to be used to monitor. statsd and datadog are supported"
88
+
89
+ include Fluent::KafkaPluginUtil::SSLSettings
90
+ include Fluent::KafkaPluginUtil::SaslSettings
91
+ include Fluent::KafkaPluginUtil::GsmSettings
92
+
93
+ attr_accessor :output_data_type
94
+ attr_accessor :field_separator
95
+
96
+ unless method_defined?(:log)
97
+ define_method("log") { $log }
98
+ end
99
+
100
+ def initialize
101
+ super
102
+
103
+ require 'kafka'
104
+ require 'fluent/plugin/kafka_producer_ext'
105
+
106
+ @kafka = nil
107
+ @producers = {}
108
+ @producers_mutex = Mutex.new
109
+ end
110
+
111
+ def multi_workers_ready?
112
+ true
113
+ end
114
+
115
+ def refresh_client(raise_error = true)
116
+ if @zookeeper
117
+ @seed_brokers = []
118
+ z = Zookeeper.new(@zookeeper)
119
+ z.get_children(:path => @zookeeper_path)[:children].each do |id|
120
+ broker = Yajl.load(z.get(:path => @zookeeper_path + "/#{id}")[:data])
121
+ if @ssl_client_cert
122
+ @seed_brokers.push(pickup_ssl_endpoint(broker))
123
+ else
124
+ @seed_brokers.push("#{broker['host']}:#{broker['port']}")
125
+ end
126
+ end
127
+ z.close
128
+ log.info "brokers has been refreshed via Zookeeper: #{@seed_brokers}"
129
+ end
130
+ begin
131
+ if @seed_brokers.length > 0
132
+ logger = @get_kafka_client_log ? log : nil
133
+ g = Fluent::KafkaPluginUtil::GsmSettings
134
+ if @secretName != nil && @projectId != nil
135
+ g.fetch(logger)
136
+ end
137
+ if @scram_mechanism != nil && @username != nil && @password != nil
138
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
139
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_ca_certs_from_system: @ssl_ca_certs_from_system,
140
+ sasl_scram_username: @username, sasl_scram_password: @password, sasl_scram_mechanism: @scram_mechanism, sasl_over_ssl: @sasl_over_ssl,
141
+ ssl_verify_hostname: @ssl_verify_hostname)
142
+ elsif @username != nil && @password != nil
143
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
144
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_ca_certs_from_system: @ssl_ca_certs_from_system,
145
+ sasl_plain_username: @username, sasl_plain_password: @password, sasl_over_ssl: @sasl_over_ssl, ssl_verify_hostname: @ssl_verify_hostname)
146
+ else
147
+ @kafka = Kafka.new(seed_brokers: @seed_brokers, client_id: @client_id, logger: logger, ssl_ca_cert: read_ssl_file(@ssl_ca_cert),
148
+ ssl_client_cert: read_ssl_file(@ssl_client_cert), ssl_client_cert_key: read_ssl_file(@ssl_client_cert_key), ssl_ca_certs_from_system: @ssl_ca_certs_from_system,
149
+ sasl_gssapi_principal: @principal, sasl_gssapi_keytab: @keytab, sasl_over_ssl: @sasl_over_ssl, ssl_verify_hostname: @ssl_verify_hostname)
150
+ end
151
+ log.info "initialized kafka producer: #{@client_id}"
152
+ else
153
+ log.warn "No brokers found on Zookeeper"
154
+ end
155
+ rescue Exception => e
156
+ if raise_error # During startup, error should be reported to engine and stop its phase for safety.
157
+ raise e
158
+ else
159
+ log.error e
160
+ end
161
+ end
162
+ end
163
+
164
+ def configure(conf)
165
+ super
166
+
167
+ log.warn "Support of fluentd v0.12 has ended. Use kafka2 instead. kafka_buffered will be an alias of kafka2"
168
+
169
+ if @zookeeper
170
+ require 'zookeeper'
171
+ else
172
+ @seed_brokers = @brokers.split(",")
173
+ log.info "brokers has been set directly: #{@seed_brokers}"
174
+ end
175
+
176
+ if conf['ack_timeout_ms']
177
+ log.warn "'ack_timeout_ms' parameter is deprecated. Use second unit 'ack_timeout' instead"
178
+ @ack_timeout = conf['ack_timeout_ms'].to_i / 1000
179
+ end
180
+
181
+ @f_separator = case @field_separator
182
+ when /SPACE/i then ' '
183
+ when /COMMA/i then ','
184
+ when /SOH/i then "\x01"
185
+ else "\t"
186
+ end
187
+
188
+ @formatter_proc = setup_formatter(conf)
189
+
190
+ @producer_opts = {max_retries: @max_send_retries, required_acks: @required_acks, idempotent: @idempotent}
191
+ @producer_opts[:ack_timeout] = @ack_timeout if @ack_timeout
192
+ @producer_opts[:compression_codec] = @compression_codec.to_sym if @compression_codec
193
+
194
+ if @discard_kafka_delivery_failed
195
+ log.warn "'discard_kafka_delivery_failed' option discards events which cause delivery failure, e.g. invalid topic or something."
196
+ log.warn "If this is unexpected, you need to check your configuration or data."
197
+ end
198
+
199
+ if @active_support_notification_regex
200
+ require 'active_support/notifications'
201
+ require 'active_support/core_ext/hash/keys'
202
+ ActiveSupport::Notifications.subscribe(Regexp.new(@active_support_notification_regex)) do |*args|
203
+ event = ActiveSupport::Notifications::Event.new(*args)
204
+ message = event.payload.respond_to?(:stringify_keys) ? event.payload.stringify_keys : event.payload
205
+ @router.emit("fluent_kafka_stats.#{event.name}", Time.now.to_i, message)
206
+ end
207
+ end
208
+
209
+ @monitoring_list.each { |m|
210
+ require "kafka/#{m}"
211
+ log.info "#{m} monitoring started"
212
+ }
213
+ end
214
+
215
+ def start
216
+ super
217
+ refresh_client
218
+ end
219
+
220
+ def shutdown
221
+ super
222
+ shutdown_producers
223
+ @kafka = nil
224
+ end
225
+
226
+ def emit(tag, es, chain)
227
+ super(tag, es, chain, tag)
228
+ end
229
+
230
+ def format_stream(tag, es)
231
+ es.to_msgpack_stream
232
+ end
233
+
234
+ def shutdown_producers
235
+ @producers_mutex.synchronize {
236
+ @producers.each { |key, producer|
237
+ producer.shutdown
238
+ }
239
+ @producers = {}
240
+ }
241
+ end
242
+
243
+ def get_producer
244
+ @producers_mutex.synchronize {
245
+ producer = @producers[Thread.current.object_id]
246
+ unless producer
247
+ producer = @kafka.producer(@producer_opts)
248
+ @producers[Thread.current.object_id] = producer
249
+ end
250
+ producer
251
+ }
252
+ end
253
+
254
+ def setup_formatter(conf)
255
+ if @output_data_type == 'json'
256
+ begin
257
+ require 'oj'
258
+ Oj.default_options = Fluent::DEFAULT_OJ_OPTIONS
259
+ Proc.new { |tag, time, record| Oj.dump(record) }
260
+ rescue LoadError
261
+ require 'yajl'
262
+ Proc.new { |tag, time, record| Yajl::Encoder.encode(record) }
263
+ end
264
+ elsif @output_data_type == 'ltsv'
265
+ require 'ltsv'
266
+ Proc.new { |tag, time, record| LTSV.dump(record) }
267
+ elsif @output_data_type == 'msgpack'
268
+ require 'msgpack'
269
+ Proc.new { |tag, time, record| record.to_msgpack }
270
+ elsif @output_data_type =~ /^attr:(.*)$/
271
+ @custom_attributes = $1.split(',').map(&:strip).reject(&:empty?)
272
+ @custom_attributes.unshift('time') if @output_include_time
273
+ @custom_attributes.unshift('tag') if @output_include_tag
274
+ Proc.new { |tag, time, record|
275
+ @custom_attributes.map { |attr|
276
+ record[attr].nil? ? '' : record[attr].to_s
277
+ }.join(@f_separator)
278
+ }
279
+ else
280
+ @formatter = Fluent::Plugin.new_formatter(@output_data_type)
281
+ @formatter.configure(conf)
282
+ @formatter.method(:format)
283
+ end
284
+ end
285
+
286
+ def deliver_messages(producer, tag)
287
+ if @discard_kafka_delivery_failed
288
+ begin
289
+ producer.deliver_messages
290
+ rescue Kafka::DeliveryFailed => e
291
+ log.warn "DeliveryFailed occurred. Discard broken event:", :error => e.to_s, :error_class => e.class.to_s, :tag => tag
292
+ producer.clear_buffer
293
+ end
294
+ else
295
+ producer.deliver_messages
296
+ end
297
+ end
298
+
299
+ def write(chunk)
300
+ tag = chunk.key
301
+ def_topic = @default_topic || tag
302
+ producer = get_producer
303
+
304
+ records_by_topic = {}
305
+ bytes_by_topic = {}
306
+ messages = 0
307
+ messages_bytes = 0
308
+ record_buf = nil
309
+ record_buf_bytes = nil
310
+
311
+ begin
312
+ chunk.msgpack_each { |time, record|
313
+ begin
314
+ if @output_include_time
315
+ if @time_format
316
+ record['time'.freeze] = Time.at(time).strftime(@time_format)
317
+ else
318
+ record['time'.freeze] = time
319
+ end
320
+ end
321
+
322
+ record['tag'] = tag if @output_include_tag
323
+ topic = (@exclude_topic_key ? record.delete(@topic_key) : record[@topic_key]) || def_topic
324
+ partition_key = (@exclude_partition_key ? record.delete(@partition_key_key) : record[@partition_key_key]) || @default_partition_key
325
+ partition = (@exclude_partition ? record.delete(@partition_key) : record[@partition_key]) || @default_partition
326
+ message_key = (@exclude_message_key ? record.delete(@message_key_key) : record[@message_key_key]) || @default_message_key
327
+
328
+ records_by_topic[topic] ||= 0
329
+ bytes_by_topic[topic] ||= 0
330
+
331
+ record_buf = @formatter_proc.call(tag, time, record)
332
+ record_buf_bytes = record_buf.bytesize
333
+ if @max_send_limit_bytes && record_buf_bytes > @max_send_limit_bytes
334
+ log.warn "record size exceeds max_send_limit_bytes. Skip event:", :time => time, :record => record
335
+ next
336
+ end
337
+ rescue StandardError => e
338
+ log.warn "unexpected error during format record. Skip broken event:", :error => e.to_s, :error_class => e.class.to_s, :time => time, :record => record
339
+ next
340
+ end
341
+
342
+ if (messages > 0) and (messages_bytes + record_buf_bytes > @kafka_agg_max_bytes) or (@kafka_agg_max_messages && messages >= @kafka_agg_max_messages)
343
+ log.debug { "#{messages} messages send because reaches the limit of batch transmission." }
344
+ deliver_messages(producer, tag)
345
+ messages = 0
346
+ messages_bytes = 0
347
+ end
348
+ log.trace { "message will send to #{topic} with partition_key: #{partition_key}, partition: #{partition}, message_key: #{message_key} and value: #{record_buf}." }
349
+ messages += 1
350
+ producer.produce_for_buffered(record_buf, topic: topic, key: message_key, partition_key: partition_key, partition: partition,
351
+ create_time: @use_event_time ? Time.at(time) : Time.now)
352
+ messages_bytes += record_buf_bytes
353
+
354
+ records_by_topic[topic] += 1
355
+ bytes_by_topic[topic] += record_buf_bytes
356
+ }
357
+ if messages > 0
358
+ log.debug { "#{messages} messages send." }
359
+ deliver_messages(producer, tag)
360
+ end
361
+ log.debug { "(records|bytes) (#{records_by_topic}|#{bytes_by_topic})" }
362
+ end
363
+ rescue Exception => e
364
+ log.warn "Send exception occurred: #{e}"
365
+ log.warn "Exception Backtrace : #{e.backtrace.join("\n")}"
366
+ # For safety, refresh client and its producers
367
+ shutdown_producers
368
+ refresh_client(false)
369
+ # Raise exception to retry sendind messages
370
+ raise e
371
+ end
372
+ end