%%%------------------------------------------------------------------- %%% Copyright (c) 2017 Heinz N. Gies %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to %%% deal in the Software without restriction, including without limitation the %%% rights to use, copy, modify, merge, publish, distribute, sublicense, and/or %%% sell copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software.
%%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING, %%% FROM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS %%% IN THE SOFTWARE. %%% %%% @author Heinz N. Gies %%% @copyright (C) 2017, Heinz N. Gies %%% @doc High perofrmance encoder for the zapkin thrift protocol. %%% @end %%%------------------------------------------------------------------- -module(otters_zipkin_encoder). -export([encode/1]). -include("otters.hrl"). -ifdef(TEST). -compile(export_all). -endif. -define(T_I16, 6). -define(T_I32, 8). -define(T_I64, 10). -define(T_STRING, 11). -define(T_STRUCT, 12). -define(T_LIST, 15). -record(conf, { service :: binary(), ip :: non_neg_integer(), port :: non_neg_integer(), add_tag_to_log :: boolean(), add_tag_to_tag :: boolean(), add_tag :: undefined | {otters:info(), otters:info()}, server_bin_log_dflt = <<>> :: binary(), server_bin_tag_dflt = <<>> :: binary(), server_bin_log_undef = <<>> :: binary(), server_bin_tag_undef = <<>> :: binary() }). encode(#span{} = S) -> encode([S]); encode(Spans) when is_list(Spans) -> Size = length(Spans), Cfg = defaults(), <> || S <- Spans>>/binary>>. defaults() -> DefaultService = cfg(zipkin_tag_host_service, atom_to_binary(node(), utf8)), DfltIP = otters_lib:ip_to_i32(cfg(zipkin_tag_host_ip, {127, 0, 0, 1})), DfltPort = cfg(zipkin_tag_host_port, 0), AddDfltToLog = cfg(zipkin_add_default_service_to_logs, false), AddDfltToTag = cfg(zipkin_add_default_service_to_tags, false), C0 = #conf{ service = DefaultService, ip = DfltIP, port = DfltPort, add_tag_to_log = AddDfltToLog, add_tag_to_tag = AddDfltToTag, add_tag = cfg(zipkin_add_host_tag_to_span, undefined) }, HostBin = encode_host(default, C0), LogHostBin = <>, TagHostBin = <>, C0#conf{ server_bin_log_dflt = LogHostBin, server_bin_tag_dflt = TagHostBin, server_bin_log_undef = case AddDfltToLog of true -> LogHostBin; _ -> <<>> end, server_bin_tag_undef = case AddDfltToTag of true -> TagHostBin; _ -> <<>> end}. encode_span(#span{ id = Id, trace_id = TraceId, name = Name, parent_id = ParentId, logs = Logs, tags = Tags, timestamp = Timestamp, duration = Duration }, Cfg) -> {Tags0Bin, TagSize} = case Cfg#conf.add_tag of {Key, Value} -> {encode_tag({Key, {Value, default}}, Cfg), maps:size(Tags) + 1}; _ -> {<<>>, maps:size(Tags)} end, LogSize = length(Logs), NameBin = otters_lib:to_bin(Name), ParentBin = case ParentId of undefined -> <<>>; ParentId -> <> end, << %% Header ?T_I64, 1:16, TraceId:64/signed-integer, ?T_STRING, 3:16, (byte_size(NameBin)):32, NameBin/binary, ?T_I64, 4:16, Id:64/signed-integer, ParentBin/binary, %% Logs ?T_LIST, 6:16, ?T_STRUCT, LogSize:32, (<< <<(encode_log(Log, Cfg))/binary>> || Log <- Logs >>)/bytes, %% Tags ?T_LIST, 8:16, ?T_STRUCT, TagSize:32, Tags0Bin/binary, (<< <<(encode_tag(Tag, Cfg))/binary>> || Tag <- maps:to_list(Tags)>>)/bytes, %% Tail ?T_I64, 10:16, Timestamp:64/signed-integer, ?T_I64, 11:16, Duration:64/signed-integer, 0>>. encode_host(default, Cfg) -> encode_host(Cfg#conf.service, Cfg); encode_host(Service, Cfg) when is_binary(Service) -> encode_host({Service, Cfg#conf.ip, Cfg#conf.port}, Cfg); encode_host(Service, Cfg) when is_list(Service); is_atom(Service) -> encode_host({ otters_lib:to_bin(Service), Cfg#conf.ip, Cfg#conf.port }, Cfg); encode_host({Service, Ip, Port}, _Cfg) when is_integer(Ip) -> <>; encode_host({Service, Ip, Port}, _Cfg) -> IPInt = otters_lib:ip_to_i32(Ip), <>. %% Log w/o a service %% binary text encode_log({Timestamp, Text}, Cfg) when is_binary(Text)-> <>; %% other content encode_log({Timestamp, Text}, Cfg) -> TextBin = otters_lib:to_bin(Text), <>; %% logs w/ undefined service %% binary text encode_log({Timestamp, Text, undefined}, Cfg) when is_binary(Text) -> <>; encode_log({Timestamp, Text, undefined}, Cfg) -> TextBin = otters_lib:to_bin(Text), <>; %% logs w/ undefined service %% binary text encode_log({Timestamp, Text, default}, Cfg) when is_binary(Text) -> <>; encode_log({Timestamp, Text, default}, Cfg) -> TextBin = otters_lib:to_bin(Text), <>; %% Other services encode_log({Timestamp, Text, Service}, Cfg) when is_binary(Text) -> SrvBin = encode_service(Service, log, Cfg), <>; encode_log({Timestamp, Text, Service}, Cfg) -> TextBin = otters_lib:to_bin(Text), SrvBin = encode_service(Service, log, Cfg), <>. %% Tags undefined encode_tag({Key, {Value, undefined}}, Cfg) when is_binary(Key), is_binary(Value) -> <>; encode_tag({Key, {Value, undefined}}, Cfg) when is_binary(Key) -> ValueBin = otters_lib:to_bin(Value), <>; %% TAgs default encode_tag({Key, {Value, default}}, Cfg) when is_binary(Key), is_binary(Value) -> <>; encode_tag({Key, {Value, default}}, Cfg) when is_binary(Key) -> ValueBin = otters_lib:to_bin(Value), <>; %% Other services encode_tag({Key, {Value, Service}}, Cfg) when is_binary(Key), is_binary(Value) -> SrvBin = encode_service(Service, tag, Cfg), <>; encode_tag({Key, {Value, Service}}, Cfg) when is_binary(Key) -> ValueBin = otters_lib:to_bin(Value), SrvBin = encode_service(Service, tag, Cfg), <>; encode_tag({Key, {Value, Service}}, Cfg) -> encode_tag({otters_lib:to_bin(Key), {Value, Service}}, Cfg). encode_service(Service, log, Cfg) -> HostBin = encode_host(Service, Cfg), <>; encode_service(Service, tag, Cfg) -> HostBin = encode_host(Service, Cfg), <>. cfg(K, D) -> otters_config:read(K, D).