From e34eef3e4c1087369f588c808364bd5cb9ad7fd0 Mon Sep 17 00:00:00 2001 From: Jeffery Utter Date: Tue, 23 Jan 2024 13:51:52 -0600 Subject: [PATCH 1/2] Finch: Sets the trace status to error on any 5xx response According to the [Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status) any trace for a 5xx response should mark the trace with `status: :error`. Previously they were not marked as error unless the client throws an error without reaching the server (timeout, connection refused, etc.) Also added a helper to `utilities/opentelemetry_instrumentation_http/src` to convert the http status codes to binaries to add as the details on the error trace. --- .../lib/opentelemetry_finch.ex | 7 + instrumentation/opentelemetry_finch/mix.exs | 2 + .../test/opentelemetry_finch_test.exs | 30 ++- .../opentelemetry_instrumentation_http.erl | 198 ++++++++++++++++++ 4 files changed, 236 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex b/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex index ce9bbf67..01d4433a 100644 --- a/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex +++ b/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex @@ -67,6 +67,13 @@ defmodule OpentelemetryFinch do kind: :client }) + if status >= 500 && status < 600 do + OpenTelemetry.Span.set_status( + s, + OpenTelemetry.status(:error, :opentelemetry_instrumentation_http.code_to_phrase(status)) + ) + end + if meta.result |> elem(0) == :error do OpenTelemetry.Span.set_status( s, diff --git a/instrumentation/opentelemetry_finch/mix.exs b/instrumentation/opentelemetry_finch/mix.exs index 87129781..420b2c03 100644 --- a/instrumentation/opentelemetry_finch/mix.exs +++ b/instrumentation/opentelemetry_finch/mix.exs @@ -56,6 +56,8 @@ defmodule OpentelemetryFinch.MixProject do [ {:telemetry, "~> 0.4 or ~> 1.0"}, {:opentelemetry_api, "~> 1.0"}, + {:opentelemetry_instrumentation_http, + path: "../../utilities/opentelemetry_instrumentation_http"}, {:opentelemetry_semantic_conventions, "~> 0.2"}, {:opentelemetry, "~> 1.0", only: [:dev, :test]}, {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, diff --git a/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs b/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs index 98b44a65..d3adb73c 100644 --- a/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs +++ b/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs @@ -51,7 +51,7 @@ defmodule OpentelemetryFinchTest do } = :otel_attributes.map(attributes) end - test "records span on requests failed", %{bypass: _} do + test "records span on requests failed" do OpentelemetryFinch.setup() _conn = start_supervised!({Finch, name: HttpFinch}) @@ -75,5 +75,33 @@ defmodule OpentelemetryFinchTest do } = :otel_attributes.map(attributes) end + # https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + # > For HTTP status codes in the 5xx range, as well as any other code the client failed to interpret, span status MUST be set to Error. + test "records span on 500 error from server", %{bypass: bypass} do + Bypass.expect(bypass, fn conn -> Plug.Conn.resp(conn, 500, "Internal Server Error") end) + + OpentelemetryFinch.setup() + + _conn = start_supervised!({Finch, name: HttpFinch}) + + {:ok, _} = Finch.build(:get, endpoint_url(bypass.port)) |> Finch.request(HttpFinch) + + assert_receive {:span, + span( + name: "HTTP GET", + kind: :client, + status: {:status, :error, "Internal Server Error"}, + attributes: attributes + )} + + assert %{ + "net.peer.name": "localhost", + "http.method": "GET", + "http.target": "/", + "http.scheme": :http, + "http.status_code": 500 + } = :otel_attributes.map(attributes) + end + defp endpoint_url(port), do: "http://localhost:#{port}/" end diff --git a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl index 3cb59460..37d142c1 100644 --- a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl +++ b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl @@ -5,6 +5,9 @@ -endif. -export([ + code/1, + code_to_atom/1, + code_to_phrase/1, extract_headers_attributes/3, normalize_header_name/1 ]). @@ -62,6 +65,201 @@ attribute_name(request, HeaderName) -> attribute_name(response, HeaderName) -> binary_to_atom(<<"http.response.header.", HeaderName/binary>>). +-spec code(atom()) -> integer(). +code(continue) -> 100; +code(switching_protocols) -> 101; +code(processing) -> 102; +code(early_hints) -> 103; +code(ok) -> 200; +code(created) -> 201; +code(accepted) -> 202; +code(non_authoritative_information) -> 203; +code(no_content) -> 204; +code(reset_content) -> 205; +code(partial_content) -> 206; +code(multi_status) -> 207; +code(already_reported) -> 208; +code(im_used) -> 226; +code(multiple_choices) -> 300; +code(moved_permanently) -> 301; +code(found) -> 302; +code(see_other) -> 303; +code(not_modified) -> 304; +code(use_proxy) -> 305; +code(switch_proxy) -> 306; +code(temporary_redirect) -> 307; +code(permanent_redirect) -> 308; +code(bad_request) -> 400; +code(unauthorized) -> 401; +code(payment_required) -> 402; +code(forbidden) -> 403; +code(not_found) -> 404; +code(method_not_allowed) -> 405; +code(not_acceptable) -> 406; +code(proxy_authentication_required) -> 407; +code(request_timeout) -> 408; +code(conflict) -> 409; +code(gone) -> 410; +code(length_required) -> 411; +code(precondition_failed) -> 412; +code(request_entity_too_large) -> 413; +code(request_uri_too_long) -> 414; +code(unsupported_media_type) -> 415; +code(requested_range_not_satisfiable) -> 416; +code(expectation_failed) -> 417; +code(im_a_teapot) -> 418; +code(misdirected_request) -> 421; +code(unprocessable_entity) -> 422; +code(locked) -> 423; +code(failed_dependency) -> 424; +code(too_early) -> 425; +code(upgrade_required) -> 426; +code(precondition_required) -> 428; +code(too_many_requests) -> 429; +code(request_header_fields_too_large) -> 431; +code(unavailable_for_legal_reasons) -> 451; +code(internal_server_error) -> 500; +code(not_implemented) -> 501; +code(bad_gateway) -> 502; +code(service_unavailable) -> 503; +code(gateway_timeout) -> 504; +code(http_version_not_supported) -> 505; +code(variant_also_negotiates) -> 506; +code(insufficient_storage) -> 507; +code(loop_detected) -> 508; +code(not_extended) -> 510; +code(network_authentication_required) -> 511. + +-spec code_to_atom(integer()) -> atom(). +code_to_atom(100) -> continue; +code_to_atom(101) -> switching_protocols; +code_to_atom(102) -> processing; +code_to_atom(103) -> early_hints; +code_to_atom(200) -> ok; +code_to_atom(201) -> created; +code_to_atom(202) -> accepted; +code_to_atom(203) -> non_authoritative_information; +code_to_atom(204) -> no_content; +code_to_atom(205) -> reset_content; +code_to_atom(206) -> partial_content; +code_to_atom(207) -> multi_status; +code_to_atom(208) -> already_reported; +code_to_atom(226) -> im_used; +code_to_atom(300) -> multiple_choices; +code_to_atom(301) -> moved_permanently; +code_to_atom(302) -> found; +code_to_atom(303) -> see_other; +code_to_atom(304) -> not_modified; +code_to_atom(305) -> use_proxy; +code_to_atom(306) -> switch_proxy; +code_to_atom(307) -> temporary_redirect; +code_to_atom(308) -> permanent_redirect; +code_to_atom(400) -> bad_request; +code_to_atom(401) -> unauthorized; +code_to_atom(402) -> payment_required; +code_to_atom(403) -> forbidden; +code_to_atom(404) -> not_found; +code_to_atom(405) -> method_not_allowed; +code_to_atom(406) -> not_acceptable; +code_to_atom(407) -> proxy_authentication_required; +code_to_atom(408) -> request_timeout; +code_to_atom(409) -> conflict; +code_to_atom(410) -> gone; +code_to_atom(411) -> length_required; +code_to_atom(412) -> precondition_failed; +code_to_atom(413) -> request_entity_too_large; +code_to_atom(414) -> request_uri_too_long; +code_to_atom(415) -> unsupported_media_type; +code_to_atom(416) -> requested_range_not_satisfiable; +code_to_atom(417) -> expectation_failed; +code_to_atom(418) -> im_a_teapot; +code_to_atom(421) -> misdirected_request; +code_to_atom(422) -> unprocessable_entity; +code_to_atom(423) -> locked; +code_to_atom(424) -> failed_dependency; +code_to_atom(425) -> too_early; +code_to_atom(426) -> upgrade_required; +code_to_atom(428) -> precondition_required; +code_to_atom(429) -> too_many_requests; +code_to_atom(431) -> request_header_fields_too_large; +code_to_atom(451) -> unavailable_for_legal_reasons; +code_to_atom(500) -> internal_server_error; +code_to_atom(501) -> not_implemented; +code_to_atom(502) -> bad_gateway; +code_to_atom(503) -> service_unavailable; +code_to_atom(504) -> gateway_timeout; +code_to_atom(505) -> http_version_not_supported; +code_to_atom(506) -> variant_also_negotiates; +code_to_atom(507) -> insufficient_storage; +code_to_atom(508) -> loop_detected; +code_to_atom(510) -> not_extended; +code_to_atom(511) -> network_authentication_required. + +-spec code_to_phrase(integer()) -> binary(). +code_to_phrase(100) -> <<"Continue">>; +code_to_phrase(101) -> <<"Switching Protocols">>; +code_to_phrase(102) -> <<"Processing">>; +code_to_phrase(103) -> <<"Early Hints">>; +code_to_phrase(200) -> <<"OK">>; +code_to_phrase(201) -> <<"Created">>; +code_to_phrase(202) -> <<"Accepted">>; +code_to_phrase(203) -> <<"Non-Authoritative Information">>; +code_to_phrase(204) -> <<"No Content">>; +code_to_phrase(205) -> <<"Reset Content">>; +code_to_phrase(206) -> <<"Partial Content">>; +code_to_phrase(207) -> <<"Multi-Status">>; +code_to_phrase(208) -> <<"Already Reported">>; +code_to_phrase(226) -> <<"IM Used">>; +code_to_phrase(300) -> <<"Multiple Choices">>; +code_to_phrase(301) -> <<"Moved Permanently">>; +code_to_phrase(302) -> <<"Found">>; +code_to_phrase(303) -> <<"See Other">>; +code_to_phrase(304) -> <<"Not Modified">>; +code_to_phrase(305) -> <<"Use Proxy">>; +code_to_phrase(306) -> <<"Switch Proxy">>; +code_to_phrase(307) -> <<"Temporary Redirect">>; +code_to_phrase(308) -> <<"Permanent Redirect">>; +code_to_phrase(400) -> <<"Bad Request">>; +code_to_phrase(401) -> <<"Unauthorized">>; +code_to_phrase(402) -> <<"Payment Required">>; +code_to_phrase(403) -> <<"Forbidden">>; +code_to_phrase(404) -> <<"Not Found">>; +code_to_phrase(405) -> <<"Method Not Allowed">>; +code_to_phrase(406) -> <<"Not Acceptable">>; +code_to_phrase(407) -> <<"Proxy Authentication Required">>; +code_to_phrase(408) -> <<"Request Timeout">>; +code_to_phrase(409) -> <<"Conflict">>; +code_to_phrase(410) -> <<"Gone">>; +code_to_phrase(411) -> <<"Length Required">>; +code_to_phrase(412) -> <<"Precondition Failed">>; +code_to_phrase(413) -> <<"Request Entity Too Large">>; +code_to_phrase(414) -> <<"Request-URI Too Long">>; +code_to_phrase(415) -> <<"Unsupported Media Type">>; +code_to_phrase(416) -> <<"Requested Range Not Satisfiable">>; +code_to_phrase(417) -> <<"Expectation Failed">>; +code_to_phrase(418) -> <<"I'm a teapot">>; +code_to_phrase(421) -> <<"Misdirected Request">>; +code_to_phrase(422) -> <<"Unprocessable Entity">>; +code_to_phrase(423) -> <<"Locked">>; +code_to_phrase(424) -> <<"Failed Dependency">>; +code_to_phrase(425) -> <<"Too Early">>; +code_to_phrase(426) -> <<"Upgrade Required">>; +code_to_phrase(428) -> <<"Precondition Required">>; +code_to_phrase(429) -> <<"Too Many Requests">>; +code_to_phrase(431) -> <<"Request Header Fields Too Large">>; +code_to_phrase(451) -> <<"Unavailable For Legal Reasons">>; +code_to_phrase(500) -> <<"Internal Server Error">>; +code_to_phrase(501) -> <<"Not Implemented">>; +code_to_phrase(502) -> <<"Bad Gateway">>; +code_to_phrase(503) -> <<"Service Unavailable">>; +code_to_phrase(504) -> <<"Gateway Timeout">>; +code_to_phrase(505) -> <<"HTTP Version Not Supported">>; +code_to_phrase(506) -> <<"Variant Also Negotiates">>; +code_to_phrase(507) -> <<"Insufficient Storage">>; +code_to_phrase(508) -> <<"Loop Detected">>; +code_to_phrase(510) -> <<"Not Extended">>; +code_to_phrase(511) -> <<"Network Authentication Required">>. + -ifdef(TEST). normalize_header_name_test_() -> From d0e180b97498a7cf89ef93e95792af79b3fee186 Mon Sep 17 00:00:00 2001 From: Jeffery Utter Date: Tue, 23 Jan 2024 14:59:00 -0600 Subject: [PATCH 2/2] Finch: Remove need to convert http status code to strings for error description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per conventions: > Don’t set the span status description if the reason can be inferred from > http.response.status_code. So we can skip setting the status reason when it's based on HTTP code --- .../lib/opentelemetry_finch.ex | 5 +- instrumentation/opentelemetry_finch/mix.exs | 2 - .../test/opentelemetry_finch_test.exs | 2 +- .../opentelemetry_instrumentation_http.erl | 198 ------------------ 4 files changed, 2 insertions(+), 205 deletions(-) diff --git a/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex b/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex index 01d4433a..29e09a4c 100644 --- a/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex +++ b/instrumentation/opentelemetry_finch/lib/opentelemetry_finch.ex @@ -68,10 +68,7 @@ defmodule OpentelemetryFinch do }) if status >= 500 && status < 600 do - OpenTelemetry.Span.set_status( - s, - OpenTelemetry.status(:error, :opentelemetry_instrumentation_http.code_to_phrase(status)) - ) + OpenTelemetry.Span.set_status(s, OpenTelemetry.status(:error)) end if meta.result |> elem(0) == :error do diff --git a/instrumentation/opentelemetry_finch/mix.exs b/instrumentation/opentelemetry_finch/mix.exs index 420b2c03..87129781 100644 --- a/instrumentation/opentelemetry_finch/mix.exs +++ b/instrumentation/opentelemetry_finch/mix.exs @@ -56,8 +56,6 @@ defmodule OpentelemetryFinch.MixProject do [ {:telemetry, "~> 0.4 or ~> 1.0"}, {:opentelemetry_api, "~> 1.0"}, - {:opentelemetry_instrumentation_http, - path: "../../utilities/opentelemetry_instrumentation_http"}, {:opentelemetry_semantic_conventions, "~> 0.2"}, {:opentelemetry, "~> 1.0", only: [:dev, :test]}, {:opentelemetry_exporter, "~> 1.0", only: [:dev, :test]}, diff --git a/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs b/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs index d3adb73c..9b7dfca5 100644 --- a/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs +++ b/instrumentation/opentelemetry_finch/test/opentelemetry_finch_test.exs @@ -90,7 +90,7 @@ defmodule OpentelemetryFinchTest do span( name: "HTTP GET", kind: :client, - status: {:status, :error, "Internal Server Error"}, + status: {:status, :error, ""}, attributes: attributes )} diff --git a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl index 37d142c1..3cb59460 100644 --- a/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl +++ b/utilities/opentelemetry_instrumentation_http/src/opentelemetry_instrumentation_http.erl @@ -5,9 +5,6 @@ -endif. -export([ - code/1, - code_to_atom/1, - code_to_phrase/1, extract_headers_attributes/3, normalize_header_name/1 ]). @@ -65,201 +62,6 @@ attribute_name(request, HeaderName) -> attribute_name(response, HeaderName) -> binary_to_atom(<<"http.response.header.", HeaderName/binary>>). --spec code(atom()) -> integer(). -code(continue) -> 100; -code(switching_protocols) -> 101; -code(processing) -> 102; -code(early_hints) -> 103; -code(ok) -> 200; -code(created) -> 201; -code(accepted) -> 202; -code(non_authoritative_information) -> 203; -code(no_content) -> 204; -code(reset_content) -> 205; -code(partial_content) -> 206; -code(multi_status) -> 207; -code(already_reported) -> 208; -code(im_used) -> 226; -code(multiple_choices) -> 300; -code(moved_permanently) -> 301; -code(found) -> 302; -code(see_other) -> 303; -code(not_modified) -> 304; -code(use_proxy) -> 305; -code(switch_proxy) -> 306; -code(temporary_redirect) -> 307; -code(permanent_redirect) -> 308; -code(bad_request) -> 400; -code(unauthorized) -> 401; -code(payment_required) -> 402; -code(forbidden) -> 403; -code(not_found) -> 404; -code(method_not_allowed) -> 405; -code(not_acceptable) -> 406; -code(proxy_authentication_required) -> 407; -code(request_timeout) -> 408; -code(conflict) -> 409; -code(gone) -> 410; -code(length_required) -> 411; -code(precondition_failed) -> 412; -code(request_entity_too_large) -> 413; -code(request_uri_too_long) -> 414; -code(unsupported_media_type) -> 415; -code(requested_range_not_satisfiable) -> 416; -code(expectation_failed) -> 417; -code(im_a_teapot) -> 418; -code(misdirected_request) -> 421; -code(unprocessable_entity) -> 422; -code(locked) -> 423; -code(failed_dependency) -> 424; -code(too_early) -> 425; -code(upgrade_required) -> 426; -code(precondition_required) -> 428; -code(too_many_requests) -> 429; -code(request_header_fields_too_large) -> 431; -code(unavailable_for_legal_reasons) -> 451; -code(internal_server_error) -> 500; -code(not_implemented) -> 501; -code(bad_gateway) -> 502; -code(service_unavailable) -> 503; -code(gateway_timeout) -> 504; -code(http_version_not_supported) -> 505; -code(variant_also_negotiates) -> 506; -code(insufficient_storage) -> 507; -code(loop_detected) -> 508; -code(not_extended) -> 510; -code(network_authentication_required) -> 511. - --spec code_to_atom(integer()) -> atom(). -code_to_atom(100) -> continue; -code_to_atom(101) -> switching_protocols; -code_to_atom(102) -> processing; -code_to_atom(103) -> early_hints; -code_to_atom(200) -> ok; -code_to_atom(201) -> created; -code_to_atom(202) -> accepted; -code_to_atom(203) -> non_authoritative_information; -code_to_atom(204) -> no_content; -code_to_atom(205) -> reset_content; -code_to_atom(206) -> partial_content; -code_to_atom(207) -> multi_status; -code_to_atom(208) -> already_reported; -code_to_atom(226) -> im_used; -code_to_atom(300) -> multiple_choices; -code_to_atom(301) -> moved_permanently; -code_to_atom(302) -> found; -code_to_atom(303) -> see_other; -code_to_atom(304) -> not_modified; -code_to_atom(305) -> use_proxy; -code_to_atom(306) -> switch_proxy; -code_to_atom(307) -> temporary_redirect; -code_to_atom(308) -> permanent_redirect; -code_to_atom(400) -> bad_request; -code_to_atom(401) -> unauthorized; -code_to_atom(402) -> payment_required; -code_to_atom(403) -> forbidden; -code_to_atom(404) -> not_found; -code_to_atom(405) -> method_not_allowed; -code_to_atom(406) -> not_acceptable; -code_to_atom(407) -> proxy_authentication_required; -code_to_atom(408) -> request_timeout; -code_to_atom(409) -> conflict; -code_to_atom(410) -> gone; -code_to_atom(411) -> length_required; -code_to_atom(412) -> precondition_failed; -code_to_atom(413) -> request_entity_too_large; -code_to_atom(414) -> request_uri_too_long; -code_to_atom(415) -> unsupported_media_type; -code_to_atom(416) -> requested_range_not_satisfiable; -code_to_atom(417) -> expectation_failed; -code_to_atom(418) -> im_a_teapot; -code_to_atom(421) -> misdirected_request; -code_to_atom(422) -> unprocessable_entity; -code_to_atom(423) -> locked; -code_to_atom(424) -> failed_dependency; -code_to_atom(425) -> too_early; -code_to_atom(426) -> upgrade_required; -code_to_atom(428) -> precondition_required; -code_to_atom(429) -> too_many_requests; -code_to_atom(431) -> request_header_fields_too_large; -code_to_atom(451) -> unavailable_for_legal_reasons; -code_to_atom(500) -> internal_server_error; -code_to_atom(501) -> not_implemented; -code_to_atom(502) -> bad_gateway; -code_to_atom(503) -> service_unavailable; -code_to_atom(504) -> gateway_timeout; -code_to_atom(505) -> http_version_not_supported; -code_to_atom(506) -> variant_also_negotiates; -code_to_atom(507) -> insufficient_storage; -code_to_atom(508) -> loop_detected; -code_to_atom(510) -> not_extended; -code_to_atom(511) -> network_authentication_required. - --spec code_to_phrase(integer()) -> binary(). -code_to_phrase(100) -> <<"Continue">>; -code_to_phrase(101) -> <<"Switching Protocols">>; -code_to_phrase(102) -> <<"Processing">>; -code_to_phrase(103) -> <<"Early Hints">>; -code_to_phrase(200) -> <<"OK">>; -code_to_phrase(201) -> <<"Created">>; -code_to_phrase(202) -> <<"Accepted">>; -code_to_phrase(203) -> <<"Non-Authoritative Information">>; -code_to_phrase(204) -> <<"No Content">>; -code_to_phrase(205) -> <<"Reset Content">>; -code_to_phrase(206) -> <<"Partial Content">>; -code_to_phrase(207) -> <<"Multi-Status">>; -code_to_phrase(208) -> <<"Already Reported">>; -code_to_phrase(226) -> <<"IM Used">>; -code_to_phrase(300) -> <<"Multiple Choices">>; -code_to_phrase(301) -> <<"Moved Permanently">>; -code_to_phrase(302) -> <<"Found">>; -code_to_phrase(303) -> <<"See Other">>; -code_to_phrase(304) -> <<"Not Modified">>; -code_to_phrase(305) -> <<"Use Proxy">>; -code_to_phrase(306) -> <<"Switch Proxy">>; -code_to_phrase(307) -> <<"Temporary Redirect">>; -code_to_phrase(308) -> <<"Permanent Redirect">>; -code_to_phrase(400) -> <<"Bad Request">>; -code_to_phrase(401) -> <<"Unauthorized">>; -code_to_phrase(402) -> <<"Payment Required">>; -code_to_phrase(403) -> <<"Forbidden">>; -code_to_phrase(404) -> <<"Not Found">>; -code_to_phrase(405) -> <<"Method Not Allowed">>; -code_to_phrase(406) -> <<"Not Acceptable">>; -code_to_phrase(407) -> <<"Proxy Authentication Required">>; -code_to_phrase(408) -> <<"Request Timeout">>; -code_to_phrase(409) -> <<"Conflict">>; -code_to_phrase(410) -> <<"Gone">>; -code_to_phrase(411) -> <<"Length Required">>; -code_to_phrase(412) -> <<"Precondition Failed">>; -code_to_phrase(413) -> <<"Request Entity Too Large">>; -code_to_phrase(414) -> <<"Request-URI Too Long">>; -code_to_phrase(415) -> <<"Unsupported Media Type">>; -code_to_phrase(416) -> <<"Requested Range Not Satisfiable">>; -code_to_phrase(417) -> <<"Expectation Failed">>; -code_to_phrase(418) -> <<"I'm a teapot">>; -code_to_phrase(421) -> <<"Misdirected Request">>; -code_to_phrase(422) -> <<"Unprocessable Entity">>; -code_to_phrase(423) -> <<"Locked">>; -code_to_phrase(424) -> <<"Failed Dependency">>; -code_to_phrase(425) -> <<"Too Early">>; -code_to_phrase(426) -> <<"Upgrade Required">>; -code_to_phrase(428) -> <<"Precondition Required">>; -code_to_phrase(429) -> <<"Too Many Requests">>; -code_to_phrase(431) -> <<"Request Header Fields Too Large">>; -code_to_phrase(451) -> <<"Unavailable For Legal Reasons">>; -code_to_phrase(500) -> <<"Internal Server Error">>; -code_to_phrase(501) -> <<"Not Implemented">>; -code_to_phrase(502) -> <<"Bad Gateway">>; -code_to_phrase(503) -> <<"Service Unavailable">>; -code_to_phrase(504) -> <<"Gateway Timeout">>; -code_to_phrase(505) -> <<"HTTP Version Not Supported">>; -code_to_phrase(506) -> <<"Variant Also Negotiates">>; -code_to_phrase(507) -> <<"Insufficient Storage">>; -code_to_phrase(508) -> <<"Loop Detected">>; -code_to_phrase(510) -> <<"Not Extended">>; -code_to_phrase(511) -> <<"Network Authentication Required">>. - -ifdef(TEST). normalize_header_name_test_() ->