madhawk57-log 0.1.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.
@@ -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