%%%=================================================================== %%% @copyright 2019 Klarna Bank AB (publ) %%% %%% @doc This module publishes message sets to a Kafka %%% topic. `partition' key determines what partition the message set %%% will be produced to. %%% %%% Input message format: %%% ``` %%% #{ values => [ #{ key => K1 %%% , value => V1 %%% , headers => H1 %%% } %%% , #{ key => K2 %%% , value => V2 %%% , headers => H2 %%% } %%% , ... %%% ] %%% , partition => Partition %%% } %%% ''' %%% %%% Output message format: %%% %%% Messages are returned as is. %%% %%% == Example usage == %%% %%% ``` %%% {map, kflow_produce_to_kafka, #{ topic => <<"downstream_topic">> % Mandatory %%% , client => ?kflow_default_client %%% }} %%% ''' %%% @end %%%=================================================================== -module(kflow_produce_to_kafka). -behavior(kflow_gen_map). -include("kflow_int.hrl"). -export([init/1, map/3, terminate/1]). -export_type([config/0]). %%%=================================================================== %%% Types %%%=================================================================== -type config() :: #{ topic := brod:topic() , client => atom() }. -record(s, { client :: atom() , topic :: brod:topic() }). %%%=================================================================== %%% kflow_gen_map callbacks %%%=================================================================== %% @private init(Config = #{topic := Topic}) -> prometheus_gauge:declare([ {name, <<"kflow_kafka_producer_offset">>} , {help, "Offset of the last message produced to the topic"} , {labels, [topic, partition]} ]), prometheus_histogram:declare([ {name, <<"kflow_kafka_producer_time">>} , {help, "Time spent to pushing messages to Kafka"} , {labels, [topic, partition]} , {buckets, [10, 100, 1000, 10000, 30000]} ]), Client = maps:get(client, Config, ?default_brod_client), RequiredAcks = maps:get(required_acks, Config, -1), Timeout = maps:get(timeout, Config, 10000), Retries = maps:get(retries, Config, 10), ProducerConfig = [ {required_acks, RequiredAcks} , {ack_timeout, Timeout} , {max_retries, Retries} ], ok = brod:start_producer(Client, Topic, ProducerConfig), #s{ client = Client , topic = Topic }. %% @private map(Offset, Msg = #{partition := P, values := VV}, State) -> #s{ client = Client , topic = Topic } = State, TBefore = erlang:monotonic_time(millisecond), ?tp(info, pre_kafka_message_send, #{ topic => Topic , partition => P , offset => Offset }), case brod:produce_sync_offset(Client, Topic, P, <<>>, VV) of {ok, OutOffset} -> prometheus_gauge:set( <<"kflow_kafka_producer_offset">> , [Topic, P] , OutOffset ), TAfter = erlang:monotonic_time(millisecond), prometheus_histogram:observe( <<"kflow_kafka_producer_time">> , [Topic, P] , TAfter - TBefore ), ?tp(info, kafka_message_sent, #{ topic => Topic , partition => P , offset => Offset }), Msg; Err -> ?slog(critical, #{ what => "Brod produce failed" , topic => Topic , partition => P , input_offset => Offset , reason => Err , client => Client }), error(kafka_error) end. %% @private terminate(_State) -> void.