Add HTTP reporter

The reporter can be configured using the following:

```
% Define http client callback
Client = fun(Url, Method, Headers, Body, ReporterOptions) ->
    ibrowse:send_req(Url, Headers, Method, Body, [])
end.

[
    {protocol, http},
    {endpoint, "http://127.0.0.1:14268"},
    {http_client, HttpClient}
]
```
diff --git a/README.md b/README.md
index ddfd2a6..078eee6 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,23 @@
 $ firefox http://localhost:16686/
 ```
 
+Selecting reporter
+------------------
+
+By default 'compact' jaeger.thrift over UDP reporter is used. However it is
+possible to select different reporter. Bellow is a configuration matrics for
+available options:
+
+| protocol | thrift_protocol | jaeger port | description                      |
+|----------|-----------------|-------------|----------------------------------|
+| udp      | compact         | 6831        | accept jaeger.thrift over compact thrift protocol (default) |
+| udp      | binary          | 6832        | accept jaeger.thrift over binary thrift protocol |
+| http     | N/A             | 14268       | accept jaeger.thrift directly from clients |
+
+The HTTP version is beneficial if you don't have jaeger agents deployed or your
+spans are greater than max udp packet size (65Kb).
+Otherwise it is better to use default.
+
 References
 -----------
 
@@ -52,3 +69,4 @@
 - [Jaeger](https://uber.github.io/jaeger/)
 - [jaeger-client-go/README.md](https://github.com/jaegertracing/jaeger-client-go/blob/v2.9.0/README.md)
 - [Jaeger Client Library](https://github.com/jaegertracing/jaeger/blob/master/docs/client_libraries.md)
+- [Jaeger default ports](https://www.jaegertracing.io/docs/1.8/getting-started/)
\ No newline at end of file
diff --git a/src/jaeger_passage.erl b/src/jaeger_passage.erl
index 50c5aa5..cf27120 100644
--- a/src/jaeger_passage.erl
+++ b/src/jaeger_passage.erl
@@ -33,7 +33,7 @@
       Tracer :: passage:tracer_id(),
       Sampler :: passage_sampler:sampler().
 start_tracer(Tracer, Sampler) ->
-    start_tracer(Tracer, Sampler, []).
+    start_tracer(Tracer, Sampler, [{protocol, udp}]).
 
 %% @doc Starts a tracer for Jaeger.
 %%
diff --git a/src/jaeger_passage_reporter.erl b/src/jaeger_passage_reporter.erl
index 32f537e..1287f48 100644
--- a/src/jaeger_passage_reporter.erl
+++ b/src/jaeger_passage_reporter.erl
@@ -29,7 +29,6 @@
 -module(jaeger_passage_reporter).
 
 -behaviour(passage_reporter).
--behaviour(gen_server).
 
 -include("constants.hrl").
 
@@ -45,36 +44,11 @@
 -export_type([start_option/0, start_options/0]).
 
 %%------------------------------------------------------------------------------
-%% Application Internal API
-%%------------------------------------------------------------------------------
--export([start_link/2]).
-
-%%------------------------------------------------------------------------------
 %% 'passage_reporter' Callback API
 %%------------------------------------------------------------------------------
 -export([report/2]).
 
 %%------------------------------------------------------------------------------
-%% 'gen_server' Callback API
-%%------------------------------------------------------------------------------
--export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-
-%%------------------------------------------------------------------------------
-%% Macros & Records
-%%------------------------------------------------------------------------------
--define(STATE, ?MODULE).
-
--record(?STATE,
-        {
-          socket               :: gen_udp:socket(),
-          thrift_format        :: thrift_protocol:format(),
-          agent_host           :: inet:hostname(),
-          agent_port           :: inet:port_number(),
-          default_service_name :: atom(),
-          process_tags         :: passage:tags()
-        }).
-
-%%------------------------------------------------------------------------------
 %% Exported Types
 %%------------------------------------------------------------------------------
 -type reporter_id() :: atom().
@@ -83,28 +57,28 @@
 -type start_options() :: [start_option()].
 %% Options for {@link start/2}.
 
--type start_option() :: {thrift_format, thrift_protocol:format()}
-                      | {agent_host, inet:hostname()}
-                      | {agent_port, inet:port_number()}
+-type start_option() :: jaeger_passage_reporter_udp:start_option()
+                      | jaeger_passage_reporter_http:start_option()
+                      | {protocol, udp | http}
                       | {default_service_name, atom()}
                       | {process_tags, passage:tags()}.
+
+%% Common reporter options
+%% <ul>
+%%   <li><b>protocol</b>: Communication protocol used to connect to jaeger. The value is used to select reporter module. Possible values are: `udp' | `http'. The default value is `udp'.</li>
+%%   <li><b>default_service_name</b>: The default service name. If a reporting span has `location.application' tag, the value is used as the service name instead of this. The default value is `ReporterId'.</li>
+%%   <li><b>process_tags</b>: The tags of the reporting process. The default value is `#{}'.</li>
+%% </ul>
+%% UDP reporter specific options
 %% <ul>
 %%   <li><b>thrift_format</b>: The format for encoding thrift messages. The default value is `compact'.</li>
 %%   <li><b>agent_host</b>: The hostname of the jaeger agent. The default value is `"127.0.0.1"'.</li>
 %%   <li><b>agent_port</b>: The port of the jaeger agent. The default values for the thrift format `compact' and `binary' are `6831' and `6832' respectively.</li>
-%%   <li><b>default_service_name</b>: The default service name. If a reporting span has `location.application' tag, the value is used as the service name instead of this. The default value is `ReporterId'.</li>
-%%   <li><b>process_tags</b>: The tags of the reporting process. The default value is `#{}'.</li>
 %% </ul>
-
-%%------------------------------------------------------------------------------
-%% Application Internal Functions
-%%------------------------------------------------------------------------------
-%% @private
--spec start_link(reporter_id(), start_options()) -> {ok, pid()} | {error, Reason} when
-      Reason :: {already_started, pid()} | term().
-start_link(ReporterId, Options) ->
-    Name = jaeger_passage_local_ns:reporter_name(ReporterId),
-    gen_server:start_link(Name, ?MODULE, {ReporterId, Options}, []).
+%% HTTP reporter specific options
+%% <ul>
+%%   <li><b>endpoint</b>: The jaeger endpoint URL for sending thrift messages. The default value is `http://127.0.0.1:14268'.</li>
+%% </ul>
 
 %%------------------------------------------------------------------------------
 %% Exported Functions
@@ -113,7 +87,7 @@
 -spec start(reporter_id()) -> {ok, passage_reporter:reporter()} | {error, Reason} when
       Reason :: {already_started, pid()} | term().
 start(ReporterId) ->
-    start(ReporterId, []).
+    start(ReporterId, [{protocol, udp}]).
 
 %% @doc Starts a reporter process.
 -spec start(reporter_id(), start_options()) -> {ok, Reporter} | {error, Reason} when
@@ -123,8 +97,13 @@
     Args = [ReporterId, Options],
     is_atom(ReporterId) orelse error(badarg, Args),
     is_list(Options) orelse error(badarg, Args),
-
-    case jaeger_passage_reporter_sup:start_child(ReporterId, Options) of
+    ReporterModule = case proplists:get_value(protocol, Options, udp) of
+        udp -> jaeger_passage_reporter_udp;
+        http -> jaeger_passage_reporter_http;
+        _ -> error(badarg, Args)
+    end,
+    ReporterOptions = proplists:delete(protocol, Options),
+    case jaeger_passage_reporter_sup:start_child(ReporterId, ReporterModule, ReporterOptions) of
         {error, Reason} -> {error, Reason};
         {ok, _Pid}      -> {ok, passage_reporter:new(?MODULE, ReporterId)}
     end.
@@ -158,74 +137,3 @@
 report(ReporterId, Span) ->
     Server = jaeger_passage_local_ns:reporter_name(ReporterId),
     gen_server:cast(Server, {report, Span}).
-
-%%------------------------------------------------------------------------------
-%% 'gen_server' Callback Functions
-%%------------------------------------------------------------------------------
-%% @private
-init({ReporterId, Options}) ->
-    Format = proplists:get_value(thrift_format, Options, compact),
-    DefaultPort =
-        case Format of
-            compact -> 6831;
-            binary  -> 6832
-        end,
-    AgentHost = proplists:get_value(agent_host, Options, "127.0.0.1"),
-    AgentPort = proplists:get_value(agent_port, Options, DefaultPort),
-    DefaultServiceName = proplists:get_value(default_service_name, Options, ReporterId),
-    Tags0 = proplists:get_value(process_tags, Options, #{}),
-
-    {ok, Hostname} = inet:gethostname(),
-    {ok, Version} = application:get_key(vsn),
-    Tags1 =
-        maps:merge(
-          Tags0,
-          #{
-            ?JAEGER_CLIENT_VERSION_TAG_KEY => list_to_binary(["jaeger_passage-", Version]),
-            ?TRACER_HOSTNAME_TAG_KEY => list_to_binary(Hostname),
-            'erlang.node' => node()
-           }),
-    {ok, Socket} = gen_udp:open(0),
-    State =
-        #?STATE{
-            socket        = Socket,
-            thrift_format = Format,
-            agent_host    = AgentHost,
-            agent_port    = AgentPort,
-            default_service_name  = DefaultServiceName,
-            process_tags  = Tags1
-           },
-    {ok, State}.
-
-%% @private
-handle_call(_Request, _From, State) ->
-    {noreply, State}.
-
-%% @private
-handle_cast({report, Span}, State) ->
-    handle_report(Span, State);
-handle_cast(_Request, State) ->
-    {noreply, State}.
-
-%% @private
-handle_info(_Info, State) ->
-    {noreply, State}.
-
-%% @private
-terminate(_Reason, _State) ->
-    ok.
-
-%% @private
-code_change(_OldVsn, State, _Extra) ->
-    {ok, State}.
-
-%%------------------------------------------------------------------------------
-%% Internal Functions
-%%------------------------------------------------------------------------------
--spec handle_report(passage_span:span(), #?STATE{}) -> {noreply, #?STATE{}}.
-handle_report(Span, State = #?STATE{default_service_name = DefaultName, process_tags = Tags}) ->
-    Name = maps:get('location.application', passage_span:get_tags(Span), DefaultName),
-    Message = jaeger_passage_thrift:make_emit_batch_message(Name, Tags, [Span]),
-    Encoded = thrift_protocol:encode_message(Message, State#?STATE.thrift_format),
-    ok = gen_udp:send(State#?STATE.socket, State#?STATE.agent_host, State#?STATE.agent_port, Encoded),
-    {noreply, State}.
diff --git a/src/jaeger_passage_reporter_http.erl b/src/jaeger_passage_reporter_http.erl
new file mode 100644
index 0000000..095f3a1
--- /dev/null
+++ b/src/jaeger_passage_reporter_http.erl
@@ -0,0 +1,173 @@
+%% @doc A reporter that sends the spans to an jaeger agent
+%%
+%% === Examples ===
+%%
+%% ```
+%% %% Starts `example_reporter'
+%% {ok, Reporter} = jaeger_passage_reporter:start(example_reporter, [{protocol, http}]).
+%% [example_reporter] = jaeger_passage_reporter:which_reporters().
+%%
+%% %% Registers `example_tracer'
+%% Context = jaeger_passage_span_context.
+%% Sampler = passage_sampler_all:new().
+%% ok = passage_tracer_registry:register(example_tracer, Context, Sampler, Reporter).
+%%
+%% %% Starts and finishes a span
+%% Span = passage:start_span(example, [{tracer, example_tracer}]).
+%%
+%% passage:finish_span(Span). % The span will send to the jaeger agent on the localhost
+%% '''
+%%
+%% === Refereces ===
+%%
+%% <ul>
+%% <li><a href="http://jaeger.readthedocs.io/en/latest/architecture/#agent">Jaeger - Architecture - Agent</a></li>
+%% <li><a href="http://jaeger.readthedocs.io/en/latest/deployment/#agent">Jaeger - Deployment - Agent</a></li>
+%% </ul>
+-module(jaeger_passage_reporter_http).
+
+-behaviour(gen_server).
+
+-include("constants.hrl").
+
+%%------------------------------------------------------------------------------
+%% Exported API
+%%------------------------------------------------------------------------------
+-export_type([start_option/0, start_options/0]).
+
+%%------------------------------------------------------------------------------
+%% Application Internal API
+%%------------------------------------------------------------------------------
+-export([start_link/2]).
+
+%%------------------------------------------------------------------------------
+%% 'gen_server' Callback API
+%%------------------------------------------------------------------------------
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%%------------------------------------------------------------------------------
+%% Macros & Records
+%%------------------------------------------------------------------------------
+-define(STATE, ?MODULE).
+
+-record(?STATE,
+        {
+          endpoint             :: string(),
+          options              :: start_options(),
+          http_client          :: http_client(),
+          default_service_name :: atom(),
+          process_tags         :: passage:tags()
+        }).
+
+-define(CONTENT_TYPE, {"Content-Type", "application/x-thrift"}).
+
+%%------------------------------------------------------------------------------
+%% Exported Types
+%%------------------------------------------------------------------------------
+
+-type start_options() :: [start_option()].
+%% Options for {@link start/2}.
+
+-type start_option() :: {endpoint, string()}
+                      | {http_client, http_client()}
+                      | {default_service_name, atom()}
+                      | {process_tags, passage:tags()}.
+%% <ul>
+%%   <li><b>endpoint</b>: The jaeger endpoint URL for sending thrift messages. The default value is `http://127.0.0.1:14268'.</li>
+%%   <li><b>http_client</b>: The callback to call to send span to jaeger.</li>
+%%   <li><b>default_service_name</b>: The default service name. If a reporting span has `location.application' tag, the value is used as the service name instead of this. The default value is `ReporterId'.</li>
+%%   <li><b>process_tags</b>: The tags of the reporting process. The default value is `#{}'.</li>
+%% </ul>
+%% Example of a http_client calback
+%% Client = fun(Url, Method, Headers, Body, ReporterOptions) ->
+%%    User = proplists:get_value(user, ReporterOptions),
+%%    Password = proplists:get_value(password, ReporterOptions),
+%%    ibrowse:send_req(Url, Headers, Method, Body, [{basic_auth, {User,  Password}}])
+%% end.
+
+
+-type http_client() :: fun((
+        Url    :: string(),
+        Method :: post,
+        Headers :: [{string(), string()}],
+        Body :: string() | binary(),
+        ReporterOptions :: start_options()) ->
+    ok).
+
+%%------------------------------------------------------------------------------
+%% Application Internal Functions
+%%------------------------------------------------------------------------------
+%% @private
+-spec start_link(jaeger_passage_reporter:reporter_id(), start_options()) -> {ok, pid()} | {error, Reason} when
+      Reason :: {already_started, pid()} | term().
+start_link(ReporterId, Options) ->
+    Name = jaeger_passage_local_ns:reporter_name(ReporterId),
+    gen_server:start_link(Name, ?MODULE, {ReporterId, Options}, []).
+
+%%------------------------------------------------------------------------------
+%% 'gen_server' Callback Functions
+%%------------------------------------------------------------------------------
+%% @private
+init({ReporterId, Options}) ->
+    Endpoint = proplists:get_value(endpoint, Options, "http://127.0.0.1:14268"),
+    EndpointURL = Endpoint ++ "/api/traces",
+
+    HttpClient = proplists:get_value(http_client, Options),
+    HttpClient =/= undefined orelse error(badarg, [ReporterId, Options]),
+
+    DefaultServiceName = proplists:get_value(default_service_name, Options, ReporterId),
+    Tags0 = proplists:get_value(process_tags, Options, #{}),
+
+    {ok, Hostname} = inet:gethostname(),
+    {ok, Version} = application:get_key(vsn),
+    Tags1 =
+        maps:merge(
+          Tags0,
+          #{
+            ?JAEGER_CLIENT_VERSION_TAG_KEY => list_to_binary(["jaeger_passage-", Version]),
+            ?TRACER_HOSTNAME_TAG_KEY => list_to_binary(Hostname),
+            'erlang.node' => node()
+           }),
+    State =
+        #?STATE{
+            endpoint              = EndpointURL,
+            http_client           = HttpClient,
+            options               = Options,
+            default_service_name  = DefaultServiceName,
+            process_tags          = Tags1
+           },
+    {ok, State}.
+
+%% @private
+handle_call(_Request, _From, State) ->
+    {noreply, State}.
+
+%% @private
+handle_cast({report, Span}, State) ->
+    handle_report(Span, State);
+handle_cast(_Request, State) ->
+    {noreply, State}.
+
+%% @private
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+    ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%------------------------------------------------------------------------------
+%% Internal Functions
+%%------------------------------------------------------------------------------
+-spec handle_report(passage_span:span(), #?STATE{}) -> {noreply, #?STATE{}}.
+handle_report(Span, State = #?STATE{default_service_name = DefaultName, process_tags = Tags, endpoint = URI, http_client = HttpClient, options = Options}) ->
+    Name = maps:get('location.application', passage_span:get_tags(Span), DefaultName),
+    Message = jaeger_passage_thrift:make_batch(Name, Tags, [Span]),
+    Encoded = thrift_protocol:encode_struct(Message, binary),
+    Headers = [?CONTENT_TYPE],
+    HttpClient(URI, post, Headers, Encoded, Options),
+    {noreply, State}.
diff --git a/src/jaeger_passage_reporter_sup.erl b/src/jaeger_passage_reporter_sup.erl
index 529fde3..77f23ba 100644
--- a/src/jaeger_passage_reporter_sup.erl
+++ b/src/jaeger_passage_reporter_sup.erl
@@ -9,7 +9,7 @@
 %% Application Internal API
 %%------------------------------------------------------------------------------
 -export([start_link/0]).
--export([start_child/2]).
+-export([start_child/3]).
 -export([stop_child/1]).
 -export([which_children/0]).
 
@@ -25,14 +25,15 @@
 start_link() ->
     supervisor:start_link({local, ?MODULE}, ?MODULE, []).
 
--spec start_child(ReporterId, Options) -> {ok, pid()} | {error, Reason} when
+-spec start_child(ReporterId, ReporterModule, Options) -> {ok, pid()} | {error, Reason} when
       ReporterId :: jaeger_passage_reporter:reporter_id(),
+      ReporterModule :: module(),
       Options :: jaeger_passage_reporter:start_options(),
       Reason :: {already_started, pid()} | term().
-start_child(ReporterId, Options) ->
+start_child(ReporterId, ReporterModule, Options) ->
     Child = #{
       id      => ReporterId,
-      start   => {jaeger_passage_reporter, start_link, [ReporterId, Options]},
+      start   => {ReporterModule, start_link, [ReporterId, Options]},
       restart => permanent
      },
     supervisor:start_child(?MODULE, Child).
diff --git a/src/jaeger_passage_reporter_udp.erl b/src/jaeger_passage_reporter_udp.erl
new file mode 100644
index 0000000..485c258
--- /dev/null
+++ b/src/jaeger_passage_reporter_udp.erl
@@ -0,0 +1,160 @@
+%% @doc A reporter that sends the spans to an jaeger agent
+%%
+%% === Examples ===
+%%
+%% ```
+%% %% Starts `example_reporter'
+%% {ok, Reporter} = jaeger_passage_reporter:start(example_reporter, [{protocol, udp}]).
+%% [example_reporter] = jaeger_passage_reporter:which_reporters().
+%%
+%% %% Registers `example_tracer'
+%% Context = jaeger_passage_span_context.
+%% Sampler = passage_sampler_all:new().
+%% ok = passage_tracer_registry:register(example_tracer, Context, Sampler, Reporter).
+%%
+%% %% Starts and finishes a span
+%% Span = passage:start_span(example, [{tracer, example_tracer}]).
+%%
+%% passage:finish_span(Span). % The span will send to the jaeger agent on the localhost
+%% '''
+%%
+%% === Refereces ===
+%%
+%% <ul>
+%% <li><a href="http://jaeger.readthedocs.io/en/latest/architecture/#agent">Jaeger - Architecture - Agent</a></li>
+%% <li><a href="http://jaeger.readthedocs.io/en/latest/deployment/#agent">Jaeger - Deployment - Agent</a></li>
+%% </ul>
+-module(jaeger_passage_reporter_udp).
+
+-behaviour(gen_server).
+
+-include("constants.hrl").
+
+%%------------------------------------------------------------------------------
+%% Exported API
+%%------------------------------------------------------------------------------
+-export_type([start_option/0, start_options/0]).
+
+%%------------------------------------------------------------------------------
+%% Application Internal API
+%%------------------------------------------------------------------------------
+-export([start_link/2]).
+
+%%------------------------------------------------------------------------------
+%% 'gen_server' Callback API
+%%------------------------------------------------------------------------------
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+%%------------------------------------------------------------------------------
+%% Macros & Records
+%%------------------------------------------------------------------------------
+-define(STATE, ?MODULE).
+
+-record(?STATE,
+        {
+          socket               :: gen_udp:socket(),
+          thrift_format        :: thrift_protocol:format(),
+          agent_host           :: inet:hostname(),
+          agent_port           :: inet:port_number(),
+          default_service_name :: atom(),
+          process_tags         :: passage:tags()
+        }).
+
+%%------------------------------------------------------------------------------
+%% Exported Types
+%%------------------------------------------------------------------------------
+
+-type start_options() :: [start_option()].
+%% Options for {@link start/2}.
+
+-type start_option() :: {thrift_format, thrift_protocol:format()}
+                      | {agent_host, inet:hostname()}
+                      | {agent_port, inet:port_number()}.
+%% <ul>
+%%   <li><b>thrift_format</b>: The format for encoding thrift messages. The default value is `compact'.</li>
+%%   <li><b>agent_host</b>: The hostname of the jaeger agent. The default value is `"127.0.0.1"'.</li>
+%%   <li><b>agent_port</b>: The port of the jaeger agent. The default values for the thrift format `compact' and `binary' are `6831' and `6832' respectively.</li>
+%%   <li><b>default_service_name</b>: The default service name. If a reporting span has `location.application' tag, the value is used as the service name instead of this. The default value is `ReporterId'.</li>
+%%   <li><b>process_tags</b>: The tags of the reporting process. The default value is `#{}'.</li>
+%% </ul>
+
+%%------------------------------------------------------------------------------
+%% Application Internal Functions
+%%------------------------------------------------------------------------------
+%% @private
+-spec start_link(jaeger_passage_reporter:reporter_id(), start_options()) -> {ok, pid()} | {error, Reason} when
+      Reason :: {already_started, pid()} | term().
+start_link(ReporterId, Options) ->
+    Name = jaeger_passage_local_ns:reporter_name(ReporterId),
+    gen_server:start_link(Name, ?MODULE, {ReporterId, Options}, []).
+
+%%------------------------------------------------------------------------------
+%% 'gen_server' Callback Functions
+%%------------------------------------------------------------------------------
+%% @private
+init({ReporterId, Options}) ->
+    Format = proplists:get_value(thrift_format, Options, compact),
+    DefaultPort =
+        case Format of
+            compact -> 6831;
+            binary  -> 6832
+        end,
+    AgentHost = proplists:get_value(agent_host, Options, "127.0.0.1"),
+    AgentPort = proplists:get_value(agent_port, Options, DefaultPort),
+    DefaultServiceName = proplists:get_value(default_service_name, Options, ReporterId),
+    Tags0 = proplists:get_value(process_tags, Options, #{}),
+
+    {ok, Hostname} = inet:gethostname(),
+    {ok, Version} = application:get_key(vsn),
+    Tags1 =
+        maps:merge(
+          Tags0,
+          #{
+            ?JAEGER_CLIENT_VERSION_TAG_KEY => list_to_binary(["jaeger_passage-", Version]),
+            ?TRACER_HOSTNAME_TAG_KEY => list_to_binary(Hostname),
+            'erlang.node' => node()
+           }),
+    {ok, Socket} = gen_udp:open(0),
+    State =
+        #?STATE{
+            socket        = Socket,
+            thrift_format = Format,
+            agent_host    = AgentHost,
+            agent_port    = AgentPort,
+            default_service_name  = DefaultServiceName,
+            process_tags  = Tags1
+           },
+    {ok, State}.
+
+%% @private
+handle_call(_Request, _From, State) ->
+    {noreply, State}.
+
+%% @private
+handle_cast({report, Span}, State) ->
+    handle_report(Span, State);
+handle_cast(_Request, State) ->
+    {noreply, State}.
+
+%% @private
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%% @private
+terminate(_Reason, _State) ->
+    ok.
+
+%% @private
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%------------------------------------------------------------------------------
+%% Internal Functions
+%%------------------------------------------------------------------------------
+-spec handle_report(passage_span:span(), #?STATE{}) -> {noreply, #?STATE{}}.
+handle_report(Span, State = #?STATE{default_service_name = DefaultName, process_tags = Tags}) ->
+    Name = maps:get('location.application', passage_span:get_tags(Span), DefaultName),
+    Message = jaeger_passage_thrift:make_emit_batch_message(Name, Tags, [Span]),
+    Encoded = thrift_protocol:encode_message(Message, State#?STATE.thrift_format),
+    ok = gen_udp:send(State#?STATE.socket, State#?STATE.agent_host, State#?STATE.agent_port, Encoded),
+    {noreply, State}.
diff --git a/src/jaeger_passage_thrift.erl b/src/jaeger_passage_thrift.erl
index 28537fd..d8d81da 100644
--- a/src/jaeger_passage_thrift.erl
+++ b/src/jaeger_passage_thrift.erl
@@ -15,7 +15,7 @@
 %%------------------------------------------------------------------------------
 %% Application Internal API
 %%------------------------------------------------------------------------------
--export([make_emit_batch_message/3]).
+-export([make_emit_batch_message/3, make_batch/3]).
 
 %%------------------------------------------------------------------------------
 %% Macros
@@ -49,6 +49,12 @@
        body         = ?STRUCT(Batch)
       }.
 
+-spec make_batch(atom(), passage:tags(), [passage_span:span()]) ->
+                                     thrift_protocol:struct().
+make_batch(ServiceName, ServiceTags, Spans) ->
+    Process = make_process(ServiceName, ServiceTags),
+    ?STRUCT(Process, make_spans(Spans)).
+
 %%------------------------------------------------------------------------------
 %% Internal Functions
 %%------------------------------------------------------------------------------