1%%%============================================================================= 2%%% Copyright 2011, Travelping GmbH <info@travelping.com> 3%%% Copyright 2013-2017, Tobias Schlager <schlagert@github.com> 4%%% 5%%% Permission to use, copy, modify, and/or distribute this software for any 6%%% purpose with or without fee is hereby granted, provided that the above 7%%% copyright notice and this permission notice appear in all copies. 8%%% 9%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16%%% 17%%% @doc 18%%% A library module providing `syslog' specific utility functions. 19%%% @end 20%%%============================================================================= 21-module(syslog_lib). 22 23%% API 24-export([get_hostname/1, 25 get_name/0, 26 get_name_metdata_key/0, 27 get_property/2, 28 get_property/3, 29 get_pid/1, 30 get_utc_datetime/1, 31 get_utc_offset/2, 32 truncate/2, 33 format_rfc3164_date/1, 34 format_rfc5424_date/1, 35 get_structured_data/3, 36 has_error_logger/0, 37 to_type/2]). 38 39-define(GET_ENV(Property), application:get_env(syslog, Property)). 40 41-include_lib("kernel/include/inet.hrl"). 42 43%% calendar:system_time_to_universal_time/2 44-dialyzer({no_missing_calls, get_utc_datetime/1}). 45 46%%%============================================================================= 47%%% API 48%%%============================================================================= 49 50%%------------------------------------------------------------------------------ 51%% @doc 52%% Returns the hostname of the running node. This may include the fully 53%% qualified domain name. The hostname will usually be the host part of the 54%% node name, except for the cases when the node is not alive or some strange 55%% host part was set, e.g. something related to the loopback interface. In this 56%% case the hostname will be what `inet:gethostname/0` returns, optionally with 57%% the domain removed. 58%% @end 59%%------------------------------------------------------------------------------ 60-spec get_hostname(none | short | long) -> string(). 61get_hostname(Transform) when is_atom(Transform) -> 62 get_hostname(Transform, get_hostpart(node())). 63get_hostname(none, HostPart) when is_list(HostPart) -> 64 clean_hostpart(HostPart); 65get_hostname(short, HostPart) when is_list(HostPart) -> 66 CleanHostPart = clean_hostpart(HostPart), 67 case is_ip4(CleanHostPart) of 68 true -> CleanHostPart; 69 false -> hd(string:tokens(CleanHostPart, ".")) 70 end; 71get_hostname(long, HostPart) when is_list(HostPart) -> 72 CleanHostPart = clean_hostpart(HostPart), 73 case is_ip4(CleanHostPart) of 74 true -> CleanHostPart; 75 false -> 76 case lists:member($., CleanHostPart) of 77 true -> CleanHostPart; 78 false -> 79 case inet:gethostbyname(CleanHostPart) of 80 {ok, #hostent{h_name=Hostname}} -> Hostname; 81 {error, _} -> CleanHostPart 82 end 83 end 84 end. 85 86%%------------------------------------------------------------------------------ 87%% @doc 88%% Returns the name reported in the `APP-NAME' field. If no name is configured 89%% using the application environment, name part of the running node is returned. 90%% If the node is not running in distributed mode (no nodename configured) the 91%% string `"beam"' will be returned. 92%% @end 93%%------------------------------------------------------------------------------ 94-spec get_name() -> binary(). 95get_name() -> 96 case ?GET_ENV(app_name) of 97 {ok, Name} -> 98 to_type(binary, Name); 99 undefined -> 100 case ?GET_ENV(appname) of 101 {ok, Name} -> to_type(binary, Name); 102 undefined -> get_name_from_node(node()) 103 end 104 end. 105 106%%------------------------------------------------------------------------------ 107%% @doc 108%% Returns the key that should be used to lookup a message metadata value to 109%% place in the `APP-NAME' field. If a message metadata field contains such a 110%% mapping this will have higher precendence over names configured/returned by 111%% {@link get_name/0}. `undefined' is used as the invalid/unconfigured value. 112%% @end 113%%------------------------------------------------------------------------------ 114-spec get_name_metdata_key() -> atom() | undefined. 115get_name_metdata_key() -> 116 case ?GET_ENV(appname_from_metadata) of 117 {ok, Value} when is_atom(Value) -> Value; 118 _ -> undefined 119 end. 120 121%%------------------------------------------------------------------------------ 122%% @doc 123%% Returns the value for a specific key from the application environment. If no 124%% value is configured or the application environment can not be read the 125%% provided default value is returned. 126%% @end 127%%------------------------------------------------------------------------------ 128-spec get_property(atom(), term()) -> term(). 129get_property(Property, Default) -> 130 case {?GET_ENV(Property), Default} of 131 {{ok, Value}, _} -> Value; 132 {_, Value} -> Value 133 end. 134 135%%------------------------------------------------------------------------------ 136%% @doc 137%% Similar to {@link get_property/2}. Additionally, this function allows to 138%% specifiy the desired target type. The configured value will be converted to 139%% the desired type. If this is not possible, the function crashes. 140%% @end 141%%------------------------------------------------------------------------------ 142-spec get_property(atom(), term(), binary | integer | ip_addr) -> term(). 143get_property(Property, Default, Type) -> 144 to_type(Type, get_property(Property, Default)). 145 146%%------------------------------------------------------------------------------ 147%% @doc 148%% Returns a string representation for a process. This will either be the 149%% (locally) registered name of the process or its process id. 150%% @end 151%%------------------------------------------------------------------------------ 152-spec get_pid(pid() | atom() | string()) -> binary(). 153get_pid(P) when is_pid(P) -> 154 case catch process_info(P, registered_name) of 155 {registered_name, N} -> to_type(binary, N); 156 _ -> to_type(binary, P) 157 end; 158get_pid(N) -> 159 to_type(binary, N). 160 161%%------------------------------------------------------------------------------ 162%% @doc 163%% Returns a syslog datetime object (UTC) with microsecond resolution from 164%% either an Erlang timestamp or the system time in microseconds. 165%% @end 166%%------------------------------------------------------------------------------ 167-spec get_utc_datetime(erlang:timestamp() | pos_integer()) -> syslog:datetime(). 168get_utc_datetime(SystemTime) when is_integer(SystemTime), SystemTime > 0 -> 169 MilliSecs = SystemTime div 1000, 170 MicroSecs = SystemTime rem 1000000, 171 Datetime = calendar:system_time_to_universal_time(MilliSecs, millisecond), 172 {Datetime, MicroSecs}; 173get_utc_datetime({MegaSecs, Secs, MicroSecs}) -> 174 Datetime = calendar:now_to_universal_time({MegaSecs, Secs, 0}), 175 {Datetime, MicroSecs}. 176 177%%------------------------------------------------------------------------------ 178%% @doc 179%% Returns the offset of a local datetime from the given UTC datetime. 180%% @end 181%%------------------------------------------------------------------------------ 182-spec get_utc_offset(calendar:datetime(), calendar:datetime()) -> 183 {43 | 45, 0..23, 0..59}. 184get_utc_offset(Utc, Local) when Utc < Local -> 185 {0, {H, Mi, 0}} = time_difference(Utc, Local), 186 {$+, H, Mi}; 187get_utc_offset(Utc, Local) -> 188 {0, {H, Mi, 0}} = time_difference(Local, Utc), 189 {$-, H, Mi}. 190 191%%------------------------------------------------------------------------------ 192%% @doc 193%% Returns a truncated string with at most `Len' characters/bytes. 194%% @end 195%%------------------------------------------------------------------------------ 196-spec truncate(pos_integer(), string() | binary()) -> string() | binary(). 197truncate(Len, Str) when length(Str) =< Len -> Str; 198truncate(Len, Str) when size(Str) =< Len -> Str; 199truncate(Len, Str) when is_list(Str) -> string:substr(Str, 1, Len); 200truncate(Len, Str) when is_binary(Str) -> binary:part(Str, 0, Len). 201 202%%------------------------------------------------------------------------------ 203%% @doc 204%% Formats a (UTC) timestamp according to RFC5424. The returned timestamp will 205%% be in local time with UTC offset (if available). 206%% @end 207%%------------------------------------------------------------------------------ 208-spec format_rfc5424_date(syslog:datetime()) -> iodata(). 209format_rfc5424_date({UtcDatetime, MicroSecs}) -> 210 LocaDatetime = erlang:universaltime_to_localtime(UtcDatetime), 211 format_rfc5424_date(LocaDatetime, UtcDatetime, MicroSecs). 212format_rfc5424_date(Utc = {{Y, Mo, D}, {H, Mi, S}}, Utc, Micro) -> 213 [integer_to_list(Y), $-, digit(Mo), $-, digit(D), $T, 214 digit(H), $:, digit(Mi), $:, digit(S), $., micro(Micro), $Z]; 215format_rfc5424_date(Local = {{Y, Mo, D}, {H, Mi, S}}, Utc, Micro) -> 216 {Sign, OH, OMi} = get_utc_offset(Utc, Local), 217 [integer_to_list(Y), $-, digit(Mo), $-, digit(D), $T, 218 digit(H), $:, digit(Mi), $:, digit(S), $., micro(Micro), 219 Sign, digit(OH), $:, digit(OMi)]. 220 221%%------------------------------------------------------------------------------ 222%% @doc 223%% Formats a (UTC) timestamp according to RFC3164. The returned timestamp will 224%% be in local time. 225%% @end 226%%------------------------------------------------------------------------------ 227-spec format_rfc3164_date(syslog:datetime()) -> iodata(). 228format_rfc3164_date({UtcDatetime, _MicroSecs}) -> 229 format_rfc3164_date_(erlang:universaltime_to_localtime(UtcDatetime)). 230format_rfc3164_date_({{_, Mo, D}, {H, Mi, S}}) -> 231 [month(Mo), " ", day(D), " ", digit(H), $:, digit(Mi), $:, digit(S)]. 232 233%%------------------------------------------------------------------------------ 234%% @doc 235%% Returns structured data from a list or map of metadata (e.g. metadata 236%% provided in `lager' messages or `logger' events). 237%% @end 238%%------------------------------------------------------------------------------ 239-spec get_structured_data(map() | list(), syslog:sd_id(), [atom()]) -> 240 [syslog:sd_element()]. 241get_structured_data(Metadata, SDId, MDKeys) when is_map(Metadata) -> 242 get_structured_data(maps:to_list(Metadata), SDId, MDKeys); 243get_structured_data(Metadata, SDId, MDKeys) when is_list(Metadata) -> 244 case [D || D = {K, _} <- Metadata, lists:member(K, MDKeys)] of 245 [] -> []; 246 SDParams -> [{SDId, SDParams}] 247 end. 248 249%%------------------------------------------------------------------------------ 250%% @doc 251%% Determine whether `error_logger' is available (e.g. we are running a pre 252%% OTP-21 release) or not. 253%% @end 254%%------------------------------------------------------------------------------ 255-spec has_error_logger() -> boolean(). 256has_error_logger() -> 257 case whereis(error_logger) of 258 P when is_pid(P) -> true; 259 undefined -> false 260 end. 261 262%%------------------------------------------------------------------------------ 263%% @doc 264%% Convert variables from one type to another 265%% @end 266%%------------------------------------------------------------------------------ 267to_type(binary, V) when is_binary(V) -> V; 268to_type(binary, V) when is_list(V) -> list_to_binary(V); 269to_type(binary, V) when is_atom(V) -> atom_to_binary(V, utf8); 270to_type(integer, V) when is_integer(V) -> V; 271to_type(integer, V) when is_list(V) -> list_to_integer(V); 272to_type(ip_addr, V) when is_tuple(V) -> V; 273to_type(ip_addr, V) when is_list(V) -> to_ip_addr_type(V); 274to_type(Type, V) when is_pid(V) -> to_type(Type, pid_to_list(V)); 275to_type(Type, V) when is_integer(V) -> to_type(Type, integer_to_list(V)); 276to_type(Type, V) when is_binary(V) -> to_type(Type, binary_to_list(V)). 277 278%%%============================================================================= 279%%% internal functions 280%%%============================================================================= 281 282%%------------------------------------------------------------------------------ 283%% @private 284%%------------------------------------------------------------------------------ 285get_name_from_node(Node) when is_atom(Node) -> 286 case atom_to_binary(Node, utf8) of 287 <<"nonode@nohost">> -> <<"beam">>; 288 N -> hd(binary:split(N, <<"@">>)) 289 end. 290 291%%------------------------------------------------------------------------------ 292%% @private 293%% Return the host part of the node name 294%%------------------------------------------------------------------------------ 295get_hostpart(Node) when is_atom(Node) -> 296 lists:last(string:tokens(atom_to_list(Node), "@")). 297 298%%------------------------------------------------------------------------------ 299%% @private 300%% Check for cases when the node is not alive or some strange host part was set, 301%% e.g. something related to the loopback interface and replace host part 302%% with the result of `inet:gethostname/0` 303%%------------------------------------------------------------------------------ 304clean_hostpart("nohost") -> 305 element(2, inet:gethostname()); 306clean_hostpart(HostPart) when is_list(HostPart) -> 307 case lists:member(HostPart, get_loopback_names()) of 308 true -> element(2, inet:gethostname()); 309 false -> HostPart 310 end. 311 312%%------------------------------------------------------------------------------ 313%% @private 314%% Return all possible names of the available loopback interfaces, e.g. 315%% `["127.0.0.1", "localhost", "localhost.localdomain", ...]' 316%%------------------------------------------------------------------------------ 317get_loopback_names() -> 318 Addresses = get_loopback_addresses(), 319 lists:append( 320 [Name || Addr <- Addresses, 321 Name <- [ntoa(Addr)], 322 is_list(Name)], 323 [Name || Addr <- Addresses, 324 {ok, Hostent} <- [inet:gethostbyaddr(Addr)], 325 Name <- [Hostent#hostent.h_name | Hostent#hostent.h_aliases], 326 is_list(Name)]). 327 328%%------------------------------------------------------------------------------ 329%% @private 330%% Returns the IPv4 addresses associated with the available loopback interfaces. 331%%------------------------------------------------------------------------------ 332get_loopback_addresses() -> 333 [Addr || {ok, IfList} <- [inet:getifaddrs()], 334 {_, IfProps} <- IfList, 335 {addr, Addr = {_, _, _, _}} <- IfProps, 336 {flags, IfFlags} <- IfProps, 337 lists:member(loopback, IfFlags)]. 338 339%%------------------------------------------------------------------------------ 340%% @private 341%%------------------------------------------------------------------------------ 342ntoa(IPv4) -> lists:flatten(io_lib:format("~w.~w.~w.~w", tuple_to_list(IPv4))). 343 344%%------------------------------------------------------------------------------ 345%% @private 346%%------------------------------------------------------------------------------ 347is_ip4(Str) -> 348 re:run(Str, "\\A\\d+\\.\\d+\\.\\d+\\.\\d+\\Z", [{capture, none}]) =:= match. 349 350%%------------------------------------------------------------------------------ 351%% @private 352%%------------------------------------------------------------------------------ 353time_difference(T1, T2) -> 354 calendar:seconds_to_daystime( 355 calendar:datetime_to_gregorian_seconds(T2) - 356 calendar:datetime_to_gregorian_seconds(T1)). 357 358%%------------------------------------------------------------------------------ 359%% @private 360%%------------------------------------------------------------------------------ 361month(1) -> "Jan"; 362month(2) -> "Feb"; 363month(3) -> "Mar"; 364month(4) -> "Apr"; 365month(5) -> "May"; 366month(6) -> "Jun"; 367month(7) -> "Jul"; 368month(8) -> "Aug"; 369month(9) -> "Sep"; 370month(10) -> "Oct"; 371month(11) -> "Nov"; 372month(12) -> "Dec". 373 374%%------------------------------------------------------------------------------ 375%% @private 376%%------------------------------------------------------------------------------ 377day(1) -> " 1"; 378day(2) -> " 2"; 379day(3) -> " 3"; 380day(4) -> " 4"; 381day(5) -> " 5"; 382day(6) -> " 6"; 383day(7) -> " 7"; 384day(8) -> " 8"; 385day(9) -> " 9"; 386day(N) -> integer_to_list(N). 387 388%%------------------------------------------------------------------------------ 389%% @private 390%%------------------------------------------------------------------------------ 391digit(0) -> "00"; 392digit(1) -> "01"; 393digit(2) -> "02"; 394digit(3) -> "03"; 395digit(4) -> "04"; 396digit(5) -> "05"; 397digit(6) -> "06"; 398digit(7) -> "07"; 399digit(8) -> "08"; 400digit(9) -> "09"; 401digit(N) -> integer_to_list(N). 402 403%%------------------------------------------------------------------------------ 404%% @private 405%%------------------------------------------------------------------------------ 406micro(M) when M < 10 -> ["00000", integer_to_list(M)]; 407micro(M) when M < 100 -> ["0000", integer_to_list(M)]; 408micro(M) when M < 1000 -> ["000", integer_to_list(M)]; 409micro(M) when M < 10000 -> ["00", integer_to_list(M)]; 410micro(M) when M < 100000 -> ["0", integer_to_list(M)]; 411micro(M) -> integer_to_list(M). 412 413%%------------------------------------------------------------------------------ 414%% @private 415%%------------------------------------------------------------------------------ 416to_ip_addr_type(V) -> 417 case inet:parse_address(V) of 418 {error, einval} -> try_inet_getaddr(V, [inet, inet6]); 419 {ok, IpAddr} -> IpAddr 420 end. 421 422%%------------------------------------------------------------------------------ 423%% @private 424%%------------------------------------------------------------------------------ 425try_inet_getaddr(V, [AddrFamily | Rest]) -> 426 handle_inet_getaddr(inet:getaddr(V, AddrFamily), V, Rest). 427 428%%------------------------------------------------------------------------------ 429%% @private 430%%------------------------------------------------------------------------------ 431handle_inet_getaddr(_, _, []) -> 432 error(invalid_dest_host); 433handle_inet_getaddr({error, _}, V, AddrFamilies) -> 434 try_inet_getaddr(V, AddrFamilies); 435handle_inet_getaddr({ok, IpAddr}, _, _) -> 436 IpAddr. 437 438%%%============================================================================= 439%%% TESTS 440%%%============================================================================= 441 442-ifdef(TEST). 443 444-include_lib("eunit/include/eunit.hrl"). 445 446get_hostname_ip_test() -> 447 ?assertEqual("192.168.1.1", get_hostname(none, "192.168.1.1")), 448 ?assertEqual("192.168.1.1", get_hostname(short, "192.168.1.1")), 449 ?assertEqual("192.168.1.1", get_hostname(long, "192.168.1.1")). 450 451get_hostname_none_test() -> 452 {ok, Hostname} = inet:gethostname(), 453 {ok, #hostent{h_name=Localhost}} = inet:gethostbyaddr("127.0.0.1"), 454 ?assertEqual(Hostname, get_hostname(none, "nohost")), 455 ?assertEqual(Hostname, get_hostname(none, "127.0.0.1")), 456 ?assertEqual(Hostname, get_hostname(none, Localhost)), 457 ?assertEqual("hostname", get_hostname(none, "hostname")), 458 ?assertEqual("hostname.domain", get_hostname(none, "hostname.domain")). 459 460get_hostname_short_test() -> 461 {ok, Hostname} = inet:gethostname(), 462 HostnameShort = hd(string:tokens(Hostname, ".")), 463 {ok, #hostent{h_name=Localhost}} = inet:gethostbyaddr("127.0.0.1"), 464 ?assertEqual(HostnameShort, get_hostname(short, "nohost")), 465 ?assertEqual(HostnameShort, get_hostname(short, "127.0.0.1")), 466 ?assertEqual(HostnameShort, get_hostname(short, Localhost)), 467 ?assertEqual("hostname", get_hostname(short, "hostname")), 468 ?assertEqual("hostname", get_hostname(short, "hostname.domain")). 469 470get_hostname_long_test() -> 471 {ok, Hostname} = inet:gethostname(), 472 HostnameLong1 = case inet:gethostbyname(Hostname) of 473 {ok, #hostent{h_name=Hname1}} -> Hname1; 474 {error, _} -> Hostname 475 end, 476 HostnameLong2 = case inet:gethostbyname("hostname") of 477 {ok, #hostent{h_name=Hname2}} -> Hname2; 478 {error, _} -> "hostname" 479 end, 480 {ok, #hostent{h_name=Localhost}} = inet:gethostbyaddr("127.0.0.1"), 481 ?assertEqual(HostnameLong1, get_hostname(long, "nohost")), 482 ?assertEqual(HostnameLong1, get_hostname(long, "127.0.0.1")), 483 ?assertEqual(HostnameLong1, get_hostname(long, Localhost)), 484 ?assertEqual(HostnameLong2, get_hostname(long, "hostname")), 485 ?assertEqual("hostname.domain", get_hostname(long, "hostname.domain")). 486 487get_name_from_node_test() -> 488 ?assertEqual(<<"beam">>, get_name_from_node('nonode@nohost')), 489 ?assertEqual(<<"nodename">>, get_name_from_node('nodename@hostname')), 490 ?assertEqual(<<"nodename">>, get_name_from_node('nodename@hostname.dom.ain')). 491 492get_pid_test() -> 493 ?assertEqual(<<"init">>, get_pid(init)), 494 ?assertEqual(<<"init">>, get_pid(whereis(init))), 495 ?assertEqual(list_to_binary(pid_to_list(self())), get_pid(self())). 496 497truncate_test() -> 498 ?assertEqual("", truncate(0, "")), 499 ?assertEqual("", truncate(1, "")), 500 ?assertEqual("", truncate(0, "123")), 501 ?assertEqual("1", truncate(1, "123")), 502 ?assertEqual("12", truncate(2, "123")), 503 ?assertEqual("123", truncate(3, "123")), 504 ?assertEqual("123", truncate(4, "123")), 505 ?assertEqual(<<>>, truncate(0, <<>>)), 506 ?assertEqual(<<>>, truncate(1, <<>>)), 507 ?assertEqual(<<>>, truncate(0, <<"123">>)), 508 ?assertEqual(<<"1">>, truncate(1, <<"123">>)), 509 ?assertEqual(<<"12">>, truncate(2, <<"123">>)), 510 ?assertEqual(<<"123">>, truncate(3, <<"123">>)), 511 ?assertEqual(<<"123">>, truncate(4, <<"123">>)). 512 513format_rfc3164_date_test() -> 514 Datetime = {{{2013,4,6},{21,20,56}},908235}, 515 Date = format_rfc3164_date(Datetime), 516 Rx = "Apr [67] \\d\\d:20:56", 517 ?assertMatch({match, _}, re:run(lists:flatten(Date), Rx)). 518 519format_rfc5424_date_test() -> 520 Datetime = {{{2013,4,6},{21,20,56}},908235}, 521 Date = format_rfc5424_date(Datetime), 522 Rx = "2013-04-0[67]T\\d\\d:\\d\\d:56\\.908235(Z|(\\+|-)\\d\\d:\\d\\d)", 523 ?assertMatch({match, _}, re:run(lists:flatten(Date), Rx)). 524 525to_type_test() -> 526 {ok, #hostent{h_name=Localhost}} = inet:gethostbyaddr("127.0.0.1"), 527 ?assertEqual(<<"1">>, to_type(binary, <<"1">>)), 528 ?assertEqual(<<"1">>, to_type(binary, "1")), 529 ?assertEqual(<<"1">>, to_type(binary, 1)), 530 ?assert(is_binary(to_type(binary, self()))), 531 ?assertEqual(<<"1">>, to_type(binary, '1')), 532 ?assertEqual(1, to_type(integer, <<"1">>)), 533 ?assertEqual(1, to_type(integer, "1")), 534 ?assertEqual(1, to_type(integer, 1)), 535 ?assertEqual({127,0,0,1}, to_type(ip_addr, Localhost)), 536 ?assertEqual({127,0,0,1}, to_type(ip_addr, list_to_binary(Localhost))), 537 ?assertEqual({127,0,0,1}, to_type(ip_addr, <<"127.0.0.1">>)), 538 ?assertEqual({127,0,0,1}, to_type(ip_addr, "127.0.0.1")), 539 ?assertEqual({0,0,0,0,0,0,0,1}, to_type(ip_addr, "::1")), 540 ?assertError(invalid_dest_host, to_type(ip_addr, "5dzFvraZ7lZUAlQu")). 541 542-endif. %% TEST 543