diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index b6b70f62..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,78 +0,0 @@ -version: 2.1 - -orbs: - rebar3: tsloughter/rebar3@0.8.1 - codecov: codecov/codecov@1.0.5 - -jobs: - codecov: - parameters: - executor: - description: The executor to use for this job. - type: executor - default: erlang - executor: <> - steps: - - attach_workspace: - at: ~/project/ - - codecov/upload: - file: _build/test/covertool/opentelemetry.covertool.xml - - run_benchmarks: - docker: - - image: circleci/elixir:1.9 - steps: - - checkout - - - run: | - mix local.hex --force - mix local.rebar --force - - rebar3 as bench compile - - ERL_LIBS=_build/bench/lib/ mix run --no-mix-exs samples/run.exs - -erlang22: &erlang22 - executor: - name: rebar3/erlang - tag: "22" - -erlang21: &erlang21 - executor: - name: rebar3/erlang - tag: "21" - -workflows: - otp21: - jobs: - - rebar3/compile: - <<: *erlang21 - - otp22: - jobs: - - rebar3/compile: - <<: *erlang22 - - rebar3/xref: - <<: *erlang22 - requires: - - rebar3/compile - - rebar3/dialyzer: - <<: *erlang22 - requires: - - rebar3/compile - - rebar3/ct: - <<: *erlang22 - requires: - - rebar3/compile - - rebar3/cover: - <<: *erlang22 - requires: - - rebar3/ct - - codecov: - <<: *erlang22 - requires: - - rebar3/cover - - benchmarks: - jobs: - - run_benchmarks diff --git a/include/ot_resource.hrl b/apps/opentelemetry/include/ot_resource.hrl similarity index 100% rename from include/ot_resource.hrl rename to apps/opentelemetry/include/ot_resource.hrl diff --git a/include/ot_sampler.hrl b/apps/opentelemetry/include/ot_sampler.hrl similarity index 100% rename from include/ot_sampler.hrl rename to apps/opentelemetry/include/ot_sampler.hrl diff --git a/include/ot_span.hrl b/apps/opentelemetry/include/ot_span.hrl similarity index 100% rename from include/ot_span.hrl rename to apps/opentelemetry/include/ot_span.hrl diff --git a/apps/opentelemetry/rebar.config b/apps/opentelemetry/rebar.config new file mode 100644 index 00000000..2656fd55 --- /dev/null +++ b/apps/opentelemetry/rebar.config @@ -0,0 +1,2 @@ +{erl_opts, [debug_info]}. +{deps, []}. diff --git a/src/opentelemetry.app.src b/apps/opentelemetry/src/opentelemetry.app.src similarity index 91% rename from src/opentelemetry.app.src rename to apps/opentelemetry/src/opentelemetry.app.src index 98bbc860..03992ab6 100644 --- a/src/opentelemetry.app.src +++ b/apps/opentelemetry/src/opentelemetry.app.src @@ -9,7 +9,7 @@ opentelemetry_api ]}, {env, [{sampler, {always_on, #{}}}, - {http_propagators, [fun ot_correlations:get_http_propagators/0, + {http_propagators, [fun ot_baggage:get_http_propagators/0, fun ot_tracer_default:w3c_propagators/0]}, %% list of disabled tracers diff --git a/src/opentelemetry_app.erl b/apps/opentelemetry/src/opentelemetry_app.erl similarity index 100% rename from src/opentelemetry_app.erl rename to apps/opentelemetry/src/opentelemetry_app.erl diff --git a/src/opentelemetry_sup.erl b/apps/opentelemetry/src/opentelemetry_sup.erl similarity index 100% rename from src/opentelemetry_sup.erl rename to apps/opentelemetry/src/opentelemetry_sup.erl diff --git a/src/ot_batch_processor.erl b/apps/opentelemetry/src/ot_batch_processor.erl similarity index 100% rename from src/ot_batch_processor.erl rename to apps/opentelemetry/src/ot_batch_processor.erl diff --git a/src/ot_exporter.erl b/apps/opentelemetry/src/ot_exporter.erl similarity index 100% rename from src/ot_exporter.erl rename to apps/opentelemetry/src/ot_exporter.erl diff --git a/src/ot_exporter_pid.erl b/apps/opentelemetry/src/ot_exporter_pid.erl similarity index 100% rename from src/ot_exporter_pid.erl rename to apps/opentelemetry/src/ot_exporter_pid.erl diff --git a/src/ot_exporter_stdout.erl b/apps/opentelemetry/src/ot_exporter_stdout.erl similarity index 100% rename from src/ot_exporter_stdout.erl rename to apps/opentelemetry/src/ot_exporter_stdout.erl diff --git a/src/ot_exporter_tab.erl b/apps/opentelemetry/src/ot_exporter_tab.erl similarity index 100% rename from src/ot_exporter_tab.erl rename to apps/opentelemetry/src/ot_exporter_tab.erl diff --git a/src/ot_meter.hrl b/apps/opentelemetry/src/ot_meter.hrl similarity index 100% rename from src/ot_meter.hrl rename to apps/opentelemetry/src/ot_meter.hrl diff --git a/src/ot_meter_default.erl b/apps/opentelemetry/src/ot_meter_default.erl similarity index 100% rename from src/ot_meter_default.erl rename to apps/opentelemetry/src/ot_meter_default.erl diff --git a/src/ot_meter_server.erl b/apps/opentelemetry/src/ot_meter_server.erl similarity index 100% rename from src/ot_meter_server.erl rename to apps/opentelemetry/src/ot_meter_server.erl diff --git a/src/ot_metric_accumulator.erl b/apps/opentelemetry/src/ot_metric_accumulator.erl similarity index 100% rename from src/ot_metric_accumulator.erl rename to apps/opentelemetry/src/ot_metric_accumulator.erl diff --git a/src/ot_metric_aggregator.erl b/apps/opentelemetry/src/ot_metric_aggregator.erl similarity index 100% rename from src/ot_metric_aggregator.erl rename to apps/opentelemetry/src/ot_metric_aggregator.erl diff --git a/src/ot_metric_aggregator_array.erl b/apps/opentelemetry/src/ot_metric_aggregator_array.erl similarity index 100% rename from src/ot_metric_aggregator_array.erl rename to apps/opentelemetry/src/ot_metric_aggregator_array.erl diff --git a/src/ot_metric_aggregator_last_value.erl b/apps/opentelemetry/src/ot_metric_aggregator_last_value.erl similarity index 100% rename from src/ot_metric_aggregator_last_value.erl rename to apps/opentelemetry/src/ot_metric_aggregator_last_value.erl diff --git a/src/ot_metric_aggregator_mmsc.erl b/apps/opentelemetry/src/ot_metric_aggregator_mmsc.erl similarity index 100% rename from src/ot_metric_aggregator_mmsc.erl rename to apps/opentelemetry/src/ot_metric_aggregator_mmsc.erl diff --git a/src/ot_metric_aggregator_sum.erl b/apps/opentelemetry/src/ot_metric_aggregator_sum.erl similarity index 100% rename from src/ot_metric_aggregator_sum.erl rename to apps/opentelemetry/src/ot_metric_aggregator_sum.erl diff --git a/src/ot_metric_controller_push.erl b/apps/opentelemetry/src/ot_metric_controller_push.erl similarity index 100% rename from src/ot_metric_controller_push.erl rename to apps/opentelemetry/src/ot_metric_controller_push.erl diff --git a/src/ot_metric_exporter.erl b/apps/opentelemetry/src/ot_metric_exporter.erl similarity index 100% rename from src/ot_metric_exporter.erl rename to apps/opentelemetry/src/ot_metric_exporter.erl diff --git a/src/ot_metric_exporter_stdout.erl b/apps/opentelemetry/src/ot_metric_exporter_stdout.erl similarity index 100% rename from src/ot_metric_exporter_stdout.erl rename to apps/opentelemetry/src/ot_metric_exporter_stdout.erl diff --git a/src/ot_metric_integrator.erl b/apps/opentelemetry/src/ot_metric_integrator.erl similarity index 100% rename from src/ot_metric_integrator.erl rename to apps/opentelemetry/src/ot_metric_integrator.erl diff --git a/src/ot_metric_sup.erl b/apps/opentelemetry/src/ot_metric_sup.erl similarity index 100% rename from src/ot_metric_sup.erl rename to apps/opentelemetry/src/ot_metric_sup.erl diff --git a/src/ot_propagation_http_b3.erl b/apps/opentelemetry/src/ot_propagation_http_b3.erl similarity index 100% rename from src/ot_propagation_http_b3.erl rename to apps/opentelemetry/src/ot_propagation_http_b3.erl diff --git a/src/ot_propagation_http_w3c.erl b/apps/opentelemetry/src/ot_propagation_http_w3c.erl similarity index 98% rename from src/ot_propagation_http_w3c.erl rename to apps/opentelemetry/src/ot_propagation_http_w3c.erl index a7f6d9e4..07217d10 100644 --- a/src/ot_propagation_http_w3c.erl +++ b/apps/opentelemetry/src/ot_propagation_http_w3c.erl @@ -65,7 +65,7 @@ encode_tracestate(#span_ctx{tracestate=undefined}) -> []; encode_tracestate(#span_ctx{tracestate=Entries}) -> StateHeaderValue = lists:join($,, [[Key, $=, Value] || {Key, Value} <- Entries]), - [{?STATE_HEADER_KEY, StateHeaderValue}]. + [{?STATE_HEADER_KEY, unicode:characters_to_binary(StateHeaderValue)}]. -spec extract(ot_propagation:http_headers(), term()) -> opentelemetry:span_ctx()| undefined. extract(Headers, _) when is_list(Headers) -> diff --git a/src/ot_resource.erl b/apps/opentelemetry/src/ot_resource.erl similarity index 100% rename from src/ot_resource.erl rename to apps/opentelemetry/src/ot_resource.erl diff --git a/src/ot_resource_app_env.erl b/apps/opentelemetry/src/ot_resource_app_env.erl similarity index 100% rename from src/ot_resource_app_env.erl rename to apps/opentelemetry/src/ot_resource_app_env.erl diff --git a/src/ot_resource_env_var.erl b/apps/opentelemetry/src/ot_resource_env_var.erl similarity index 100% rename from src/ot_resource_env_var.erl rename to apps/opentelemetry/src/ot_resource_env_var.erl diff --git a/src/ot_sampler.erl b/apps/opentelemetry/src/ot_sampler.erl similarity index 100% rename from src/ot_sampler.erl rename to apps/opentelemetry/src/ot_sampler.erl diff --git a/src/ot_span_ets.erl b/apps/opentelemetry/src/ot_span_ets.erl similarity index 92% rename from src/ot_span_ets.erl rename to apps/opentelemetry/src/ot_span_ets.erl index 52b97ce6..258b9091 100644 --- a/src/ot_span_ets.erl +++ b/apps/opentelemetry/src/ot_span_ets.erl @@ -27,7 +27,7 @@ handle_cast/2]). -export([start_span/3, - start_span/4, + start_span/5, end_span/1, end_span/2, get_ctx/1, @@ -49,13 +49,14 @@ start_link(Opts) -> gen_server:start_link(?MODULE, Opts, []). start_span(Name, Opts, InstrumentationLibrary) -> - start_span(Name, Opts, fun(Span) -> Span end, InstrumentationLibrary). + start_span(Name, undefined, Opts, fun(Span) -> Span end, InstrumentationLibrary). %% @doc Start a span and insert into the active span ets table. --spec start_span(opentelemetry:span_name(), ot_span:start_opts(), fun(), - ot_tracer_server:instrumentation_library()) -> opentelemetry:span_ctx(). -start_span(Name, Opts, Processors, InstrumentationLibrary) -> - {SpanCtx, Span} = ot_span_utils:start_span(Name, Opts), +-spec start_span(opentelemetry:span_name(), opentelemetry:span_ctx() | undefined, + ot_span:start_opts(), fun(), ot_tracer_server:instrumentation_library()) + -> opentelemetry:span_ctx(). +start_span(Name, ParentSpanCtx, Opts, Processors, InstrumentationLibrary) -> + {SpanCtx, Span} = ot_span_utils:start_span(Name, ParentSpanCtx, Opts), Span1 = Span#span{instrumentation_library=InstrumentationLibrary}, Span2 = Processors(Span1), _ = storage_insert(Span2), diff --git a/src/ot_span_ets.hrl b/apps/opentelemetry/src/ot_span_ets.hrl similarity index 100% rename from src/ot_span_ets.hrl rename to apps/opentelemetry/src/ot_span_ets.hrl diff --git a/src/ot_span_processor.erl b/apps/opentelemetry/src/ot_span_processor.erl similarity index 100% rename from src/ot_span_processor.erl rename to apps/opentelemetry/src/ot_span_processor.erl diff --git a/src/ot_span_sup.erl b/apps/opentelemetry/src/ot_span_sup.erl similarity index 100% rename from src/ot_span_sup.erl rename to apps/opentelemetry/src/ot_span_sup.erl diff --git a/src/ot_span_sweeper.erl b/apps/opentelemetry/src/ot_span_sweeper.erl similarity index 100% rename from src/ot_span_sweeper.erl rename to apps/opentelemetry/src/ot_span_sweeper.erl diff --git a/src/ot_span_utils.erl b/apps/opentelemetry/src/ot_span_utils.erl similarity index 96% rename from src/ot_span_utils.erl rename to apps/opentelemetry/src/ot_span_utils.erl index c5792959..84d91ac9 100644 --- a/src/ot_span_utils.erl +++ b/apps/opentelemetry/src/ot_span_utils.erl @@ -18,7 +18,7 @@ %%%------------------------------------------------------------------------- -module(ot_span_utils). --export([start_span/2, +-export([start_span/3, end_span/1]). -include_lib("opentelemetry_api/include/opentelemetry.hrl"). @@ -28,10 +28,9 @@ %% sampling bit is the first bit in 8-bit trace options -define(IS_ENABLED(X), (X band 1) =/= 0). --spec start_span(opentelemetry:span_name(), ot_span:start_opts()) +-spec start_span(opentelemetry:span_name(), opentelemetry:span_ctx() | undefined, ot_span:start_opts()) -> {opentelemetry:span_ctx(), opentelemetry:span() | undefined}. -start_span(Name, Opts) -> - Parent = maps:get(parent, Opts, undefined), +start_span(Name, Parent, Opts) -> Attributes = maps:get(attributes, Opts, []), Links = maps:get(links, Opts, []), Kind = maps:get(kind, Opts, ?SPAN_KIND_INTERNAL), diff --git a/src/ot_tracer.hrl b/apps/opentelemetry/src/ot_tracer.hrl similarity index 100% rename from src/ot_tracer.hrl rename to apps/opentelemetry/src/ot_tracer.hrl diff --git a/src/ot_tracer_default.erl b/apps/opentelemetry/src/ot_tracer_default.erl similarity index 62% rename from src/ot_tracer_default.erl rename to apps/opentelemetry/src/ot_tracer_default.erl index 6f05f773..de12c83e 100644 --- a/src/ot_tracer_default.erl +++ b/apps/opentelemetry/src/ot_tracer_default.erl @@ -20,7 +20,9 @@ -behaviour(ot_tracer). -export([start_span/3, + start_span/4, start_inactive_span/3, + start_inactive_span/4, set_span/2, with_span/3, with_span/4, @@ -38,7 +40,7 @@ -include("ot_tracer.hrl"). %% the context namespace key --define(TRACER_KEY, '$__ot_tracer_ctx_key'). +%% -define(TRACER_KEY, '$__ot_tracer_ctx_key'). %% key under the namespace for the active tracer context -define(TRACER_CTX, {ot_tracer_default, tracer_ctx}). %% the span context extracted with a propagator @@ -48,19 +50,39 @@ -spec start_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) -> opentelemetry:span_ctx(). start_span(Tracer, Name, Opts) -> - PreviousTracerCtx = ot_ctx:get_value(?TRACER_KEY, ?TRACER_CTX), + PreviousTracerCtx = ot_ctx:get_value(?TRACER_CTX), SpanCtx = start_inactive_span(Tracer, Name, Opts), - ot_ctx:set_value(?TRACER_KEY, ?TRACER_CTX, #tracer_ctx{active=SpanCtx, - previous=PreviousTracerCtx}), + ot_ctx:set_value(?TRACER_CTX, #tracer_ctx{active=SpanCtx, + previous=PreviousTracerCtx}), SpanCtx. +%% @doc Creates a Span and sets it to the current active Span in the process's Tracer Context. +-spec start_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_span(Ctx, Tracer, Name, Opts) -> + PreviousTracerCtx = ot_ctx:get_value(?TRACER_CTX, undefined), + {SpanCtx, _} = start_inactive_span(Ctx, Tracer, Name, Opts), + {SpanCtx, ot_ctx:set_value(Ctx, ?TRACER_CTX, #tracer_ctx{active=SpanCtx, + previous=PreviousTracerCtx})}. + %% @doc Starts an inactive Span and returns its SpanCtx. -spec start_inactive_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) - -> opentelemetry:span_ctx(). + -> opentelemetry:span_ctx(). start_inactive_span(Tracer={_, #tracer{on_start_processors=Processors, instrumentation_library=InstrumentationLibrary}}, Name, Opts) -> - Opts1 = maybe_set_sampler(Tracer, maybe_set_parent(Opts)), - ot_span_ets:start_span(Name, Opts1, Processors, InstrumentationLibrary). + Ctx = ot_ctx:get_current(), + ParentSpanCtx = maybe_parent_span_ctx(Ctx), + Opts1 = maybe_set_sampler(Tracer, Opts), + ot_span_ets:start_span(Name, ParentSpanCtx, Opts1, Processors, InstrumentationLibrary). + +%% @doc Starts an inactive Span and returns its SpanCtx. +-spec start_inactive_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), + ot_span:start_opts()) -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_inactive_span(Ctx, Tracer={_, #tracer{on_start_processors=Processors, + instrumentation_library=InstrumentationLibrary}}, Name, _Opts) -> + ParentSpanCtx = maybe_parent_span_ctx(Ctx), + Opts1 = maybe_set_sampler(Tracer, Ctx), + {ot_span_ets:start_span(Name, ParentSpanCtx, Opts1, Processors, InstrumentationLibrary), Ctx}. maybe_set_sampler(_Tracer, Opts) when is_map_key(sampler, Opts) -> Opts; @@ -68,18 +90,16 @@ maybe_set_sampler({_, #tracer{sampler=Sampler}}, Opts) -> Opts#{sampler => Sampler}. %% returns the span `start_opts' map with the parent span ctx set -%% based on the current tracer ctx, the opts passed to `start_span' +%% based on the current tracer ctx, the ctx passed to `start_span' %% or `start_inactive_span' and in the case none of those are defined it %% checks the `EXTERNAL_SPAN_CTX' which can be set by a propagator extractor. --spec maybe_set_parent(ot_span:start_opts()) -> ot_span:start_opts(). -maybe_set_parent(Opts=#{parent := #span_ctx{}}) -> - Opts; -maybe_set_parent(Opts) -> - case ot_ctx:get_value(?TRACER_KEY, ?TRACER_CTX) of +-spec maybe_parent_span_ctx(ot_ctx:ctx()) -> opentelemetry:span_ctx() | undefined. +maybe_parent_span_ctx(Ctx) -> + case ot_ctx:get_value(Ctx, ?TRACER_CTX, undefined) of #tracer_ctx{active=ActiveSpanCtx} when ActiveSpanCtx =/= undefined -> - Opts#{parent => ActiveSpanCtx}; + ActiveSpanCtx; _ -> - Opts#{parent => ot_ctx:get_value(?TRACER_KEY, ?EXTERNAL_SPAN_CTX)} + ot_ctx:get_value(?EXTERNAL_SPAN_CTX) end. %% @doc Takes a SpanCtx and sets it to the current active Span in the process's Tracer Context. @@ -88,9 +108,9 @@ maybe_set_parent(Opts) -> %% @end -spec set_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> ok. set_span(_Tracer, SpanCtx) -> - ActiveTracerCtx = ot_ctx:get_value(?TRACER_KEY, ?TRACER_CTX), - ot_ctx:set_value(?TRACER_KEY, ?TRACER_CTX, #tracer_ctx{active=SpanCtx, - previous=ActiveTracerCtx}). + ActiveTracerCtx = ot_ctx:get_value(?TRACER_CTX), + ot_ctx:set_value(?TRACER_CTX, #tracer_ctx{active=SpanCtx, + previous=ActiveTracerCtx}). -spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_tracer:traced_fun()) -> ok. with_span(_Tracer, SpanName, Fun) -> @@ -111,12 +131,12 @@ with_span(Tracer, SpanName, Opts, Fun) -> %% If spans in `Fun()' were started and not finished properly they will be %% abandoned and it be up to the `ot_span_sweeper' to eventually remove them. _ = end_span(Tracer, SpanCtx), - ot_ctx:set_value(?TRACER_KEY, ?TRACER_CTX, PreviousTracerCtx) + ot_ctx:set_value(?TRACER_CTX, PreviousTracerCtx) end. -spec current_span_ctx(opentelemetry:tracer()) -> opentelemetry:span_ctx(). current_span_ctx(_Tracer) -> - case ot_ctx:get_value(?TRACER_KEY, ?TRACER_CTX) of + case ot_ctx:get_value(?TRACER_CTX) of #tracer_ctx{active=SpanCtx, previous=_PreviousTracerCtx} -> SpanCtx; @@ -129,7 +149,11 @@ current_span_ctx(_Tracer) -> %% previous trace context, which contains its previous and so on. -spec current_ctx(opentelemetry:tracer()) -> ot_tracer:tracer_ctx(). current_ctx(_Tracer) -> - ot_ctx:get_value(?TRACER_KEY, ?TRACER_CTX). + ot_ctx:get_value(?TRACER_CTX). + +-spec current_ctx(ot_ctx:ctx(), opentelemetry:tracer()) -> ot_tracer:tracer_ctx(). +current_ctx(Ctx, _Tracer) -> + ot_ctx:get_value(Ctx, ?TRACER_CTX, undefined). span_module({_, #tracer{span_module=SpanModule}}) -> SpanModule. @@ -138,16 +162,16 @@ span_module({_, #tracer{span_module=SpanModule}}) -> b3_propagators() -> ToText = fun ot_propagation_http_b3:inject/2, FromText = fun ot_propagation_http_b3:extract/2, - Injector = ot_ctx:http_injector(?TRACER_KEY, ?TRACER_CTX, ToText), - Extractor = ot_ctx:http_extractor(?TRACER_KEY, ?EXTERNAL_SPAN_CTX, FromText), + Injector = ot_ctx:http_injector(?TRACER_CTX, ToText), + Extractor = ot_ctx:http_extractor(?EXTERNAL_SPAN_CTX, FromText), {Extractor, Injector}. -spec w3c_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}. w3c_propagators() -> ToText = fun ot_propagation_http_w3c:inject/2, FromText = fun ot_propagation_http_w3c:extract/2, - Injector = ot_ctx:http_injector(?TRACER_KEY, ?TRACER_CTX, ToText), - Extractor = ot_ctx:http_extractor(?TRACER_KEY, ?EXTERNAL_SPAN_CTX, FromText), + Injector = ot_ctx:http_injector(?TRACER_CTX, ToText), + Extractor = ot_ctx:http_extractor(?EXTERNAL_SPAN_CTX, FromText), {Extractor, Injector}. %% @doc Ends the span in the current pdict context. And sets the previous @@ -158,13 +182,24 @@ end_span(Tracer) -> #tracer_ctx{active=SpanCtx, previous=PreviousTracerCtx} -> Result = end_span(Tracer, SpanCtx), - ot_ctx:set_value(?TRACER_KEY, ?TRACER_CTX, PreviousTracerCtx), + ot_ctx:set_value(?TRACER_CTX, PreviousTracerCtx), Result; _ -> false end. +-spec end_span(ot_ctx:ctx() | opentelemetry:tracer(), opentelemetry:tracer() | opentelemetry:span_ctx()) -> boolean() | {error, term()}. %% @doc Ends the Span by setting the the `end_time' and calling the `OnEnd' Span Processors. --spec end_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> boolean() | {error, term()}. end_span({_, #tracer{on_end_processors=Processors}}, SpanCtx) -> - ot_span_ets:end_span(SpanCtx, Processors). + ot_span_ets:end_span(SpanCtx, Processors); +%% @doc Ends the Span in the context argument. +end_span(Ctx, Tracer) -> + case current_ctx(Ctx, Tracer) of + #tracer_ctx{active=SpanCtx, + previous=PreviousTracerCtx} -> + Result = end_span(Tracer, SpanCtx), + ot_ctx:set_value(?TRACER_CTX, PreviousTracerCtx), + Result; + _ -> + false + end. diff --git a/src/ot_tracer_server.erl b/apps/opentelemetry/src/ot_tracer_server.erl similarity index 100% rename from src/ot_tracer_server.erl rename to apps/opentelemetry/src/ot_tracer_server.erl diff --git a/src/ot_utils.erl b/apps/opentelemetry/src/ot_utils.erl similarity index 100% rename from src/ot_utils.erl rename to apps/opentelemetry/src/ot_utils.erl diff --git a/test/opentelemetry_SUITE.erl b/apps/opentelemetry/test/opentelemetry_SUITE.erl similarity index 76% rename from test/opentelemetry_SUITE.erl rename to apps/opentelemetry/test/opentelemetry_SUITE.erl index e630c8dc..9f53ac9a 100644 --- a/test/opentelemetry_SUITE.erl +++ b/apps/opentelemetry/test/opentelemetry_SUITE.erl @@ -17,7 +17,7 @@ all() -> all_testcases() -> [with_span, macros, child_spans, update_span_data, tracer_instrumentation_library, - tracer_previous_ctx, stop_temporary_app]. + tracer_previous_ctx, stop_temporary_app, reset_after, attach_ctx]. groups() -> [{w3c, [], [propagation]}, @@ -36,14 +36,14 @@ init_per_group(Propagator, Config) when Propagator =:= w3c ; application:set_env(opentelemetry, processors, [{ot_batch_processor, #{scheduled_delay_ms => 1}}]), {ok, _} = application:ensure_all_started(opentelemetry), - {CorrelationsHttpExtractor, CorrelationsHttpInjector} = ot_correlations:get_http_propagators(), + {BaggageHttpExtractor, BaggageHttpInjector} = ot_baggage:get_http_propagators(), {TraceHttpExtractor, TraceHttpInjector} = case Propagator of w3c -> ot_tracer_default:w3c_propagators(); b3 -> ot_tracer_default:b3_propagators() end, - opentelemetry:set_http_extractor([CorrelationsHttpExtractor, + opentelemetry:set_http_extractor([BaggageHttpExtractor, TraceHttpExtractor]), - opentelemetry:set_http_injector([CorrelationsHttpInjector, + opentelemetry:set_http_injector([BaggageHttpInjector, TraceHttpInjector]), [{propagator, Propagator} | Config]. @@ -182,20 +182,39 @@ update_span_data(Config) -> events=Events, _='_'})). -propagation(_Config) -> - #span_ctx{trace_id=TraceId} = ?start_span(<<"span-1">>), +propagation(Config) -> + Propagator = ?config(propagator, Config), + #span_ctx{trace_id=TraceId, + span_id=SpanId} = ?start_span(<<"span-1">>), + + ot_baggage:set("key-1", "value-1"), + ot_baggage:set("key-2", "value-2"), Headers = ot_propagation:http_inject([{<<"existing-header">>, <<"I exist">>}]), - [?assert(is_binary(Value)) || {_Key, Value} <- Headers], + EncodedTraceId = io_lib:format("~32.16.0b", [TraceId]), + EncodedSpanId = io_lib:format("~16.16.0b", [SpanId]), + + ?assertListsMatch([{<<"otcorrelations">>, "key-2=value-2,key-1=value-1"}, + {<<"existing-header">>, <<"I exist">>} | + trace_context(Propagator, EncodedTraceId, EncodedSpanId)], Headers), ?end_span(), + ?assertEqual(#{"key-1" => "value-1", + "key-2" => "value-2"}, ot_baggage:get_all()), ?assertEqual(undefined, ?current_span_ctx()), + %% clear our baggage from the context to test extraction + ot_ctx:remove(ot_baggage:ctx_key()), + ?assertEqual(#{}, ot_baggage:get_all()), + %% make header keys uppercase to validate the extractor is case insensitive BinaryHeaders = [{string:uppercase(Key), iolist_to_binary(Value)} || {Key, Value} <- Headers], ot_propagation:http_extract(BinaryHeaders), + ?assertEqual(#{"key-1" => "value-1", + "key-2" => "value-2"}, ot_baggage:get_all()), + %% extracted remote spans are not set to the active span %% instead they are stored under a special "external span" %% key and then used as the parent if current active span @@ -240,17 +259,15 @@ tracer_previous_ctx(Config) -> SpanCtx1 = ot_tracer:start_span(Tracer, <<"span-1">>, #{}), ?assertMatch(SpanCtx1, ?current_span_ctx()), - %% create a span that is not set to active and with no parent - SpanCtx2 = ot_tracer:start_inactive_span(Tracer, <<"span-2">>, #{parent => undefined}), - + %% create a span that is not on the current context and with no parent + {SpanCtx2, Ctx} = ot_tracer:start_span(ot_ctx:new(), Tracer, <<"span-2">>, #{}), %% start a new active span with SpanCtx2 as the parent - SpanCtx3 = ot_tracer:start_span(Tracer, <<"span-3">>, #{parent => SpanCtx2}), + {SpanCtx3, Ctx1} = ot_tracer:start_span(Ctx, Tracer, <<"span-3">>, #{}), %% end SpanCtx3, even though it isn't the parent SpanCtx1 - %% should be the active context afterward - ot_tracer:end_span(Tracer), + ot_tracer:end_span(Ctx1, Tracer), - ?assertMatch(SpanCtx1, ?current_span_ctx()), + ?assertEqual(SpanCtx1, ?current_span_ctx()), ot_tracer:end_span(Tracer), @@ -261,6 +278,53 @@ tracer_previous_ctx(Config) -> ok. +attach_ctx(Config) -> + Tid = ?config(tid, Config), + + Tracer = opentelemetry:get_tracer(), + + SpanCtx1 = ot_tracer:start_span(Tracer, <<"span-1">>, #{}), + ?assertMatch(SpanCtx1, ?current_span_ctx()), + + %% create a span that is not set to active and with no parent + {SpanCtx2, _Ctx} = ot_tracer:start_inactive_span(ot_ctx:new(), Tracer, <<"span-2">>, #{}), + Ctx = ot_ctx:get_current(), + + erlang:spawn(fun() -> + ot_ctx:attach(Ctx), + ot_tracer:set_span(Tracer, SpanCtx2), + ot_tracer:end_span(Tracer) + end), + + ot_tracer:end_span(Tracer), + + assert_all_exported(Tid, [SpanCtx1, SpanCtx2]), + + ok. + +reset_after(Config) -> + Tid = ?config(tid, Config), + + Tracer = opentelemetry:get_tracer(), + + SpanCtx1 = ot_tracer:start_span(Tracer, <<"span-1">>, #{}), + ?assertMatch(SpanCtx1, ?current_span_ctx()), + + Ctx = ot_ctx:get_current(), + + try + %% start but don't end a span + _SpanCtx2 = ot_tracer:start_span(Tracer, <<"span-2">>, #{}) + after + ot_ctx:attach(Ctx) + end, + + ot_tracer:end_span(Tracer), + + assert_all_exported(Tid, [SpanCtx1]), + + ok. + stop_temporary_app(_Config) -> SpanCtx1 = ?start_span(<<"span-1">>), ?assertNotMatch(#span_ctx{trace_id=0, @@ -296,8 +360,8 @@ assert_not_exported(Tid, #span_ctx{trace_id=TraceId, trace_context(w3c, EncodedTraceId, EncodedSpanId) -> [{<<"traceparent">>, - [<<"00">>, "-", EncodedTraceId,"-", EncodedSpanId, "-", <<"01">>]}]; + iolist_to_binary([<<"00">>, "-", EncodedTraceId,"-", EncodedSpanId, "-", <<"01">>])}]; trace_context(b3, EncodedTraceId, EncodedSpanId) -> - [{<<"X-B3-Sampled">>, "1"}, - {<<"X-B3-SpanId">>, EncodedSpanId}, - {<<"X-B3-TraceId">>,EncodedTraceId}]. + [{<<"X-B3-Sampled">>, <<"1">>}, + {<<"X-B3-SpanId">>, list_to_binary(EncodedSpanId)}, + {<<"X-B3-TraceId">>, list_to_binary(EncodedTraceId)}]. diff --git a/test/ot_batch_processor_SUITE.erl b/apps/opentelemetry/test/ot_batch_processor_SUITE.erl similarity index 96% rename from test/ot_batch_processor_SUITE.erl rename to apps/opentelemetry/test/ot_batch_processor_SUITE.erl index 53b762b3..2beb93e3 100644 --- a/test/ot_batch_processor_SUITE.erl +++ b/apps/opentelemetry/test/ot_batch_processor_SUITE.erl @@ -6,7 +6,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("opentelemetry_api/include/opentelemetry.hrl"). --include("ot_sampler.hrl"). +-include_lib("ot_sampler.hrl"). all() -> [exporting_timeout_test]. diff --git a/test/ot_metric_SUITE.erl b/apps/opentelemetry/test/ot_metric_SUITE.erl similarity index 100% rename from test/ot_metric_SUITE.erl rename to apps/opentelemetry/test/ot_metric_SUITE.erl diff --git a/test/ot_resource_SUITE.erl b/apps/opentelemetry/test/ot_resource_SUITE.erl similarity index 100% rename from test/ot_resource_SUITE.erl rename to apps/opentelemetry/test/ot_resource_SUITE.erl diff --git a/test/ot_samplers_SUITE.erl b/apps/opentelemetry/test/ot_samplers_SUITE.erl similarity index 100% rename from test/ot_samplers_SUITE.erl rename to apps/opentelemetry/test/ot_samplers_SUITE.erl diff --git a/test/ot_sweeper_SUITE.erl b/apps/opentelemetry/test/ot_sweeper_SUITE.erl similarity index 100% rename from test/ot_sweeper_SUITE.erl rename to apps/opentelemetry/test/ot_sweeper_SUITE.erl diff --git a/test/ot_test_utils.hrl b/apps/opentelemetry/test/ot_test_utils.hrl similarity index 100% rename from test/ot_test_utils.hrl rename to apps/opentelemetry/test/ot_test_utils.hrl diff --git a/apps/opentelemetry_api/LICENSE b/apps/opentelemetry_api/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/apps/opentelemetry_api/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/apps/opentelemetry_api/README.md b/apps/opentelemetry_api/README.md new file mode 100644 index 00000000..6d7a0aa8 --- /dev/null +++ b/apps/opentelemetry_api/README.md @@ -0,0 +1,140 @@ +# Erlang/Elixir OpenTelemetry API + +[![EEF Observability WG project](https://img.shields.io/badge/EEF-Observability-black)](https://github.com/erlef/eef-observability-wg) +[![Hex.pm](https://img.shields.io/hexpm/v/opentelemetry)](https://hex.pm/packages/opentelemetry_api) +![Build Status](https://github.com/open-telemetry/opentelemetry-erlang-api/workflows/Common%20Test/badge.svg) + +This is the API portion of [OpenTelemetry](https://opentelemetry.io/) for Erlang and Elixir Applications. + +This is a library, it does not start any processes, and should be the only OpenTelemetry dependency of Erlang/Elixir Applications. + +The end user of your Application can then choose to include the [OpenTelemetry implementation](https://github.com/open-telemetry/opentelemetry-erlang) Application. If the implementation Application is not in the final release the OpenTelemetry instrumentation will all be no-ops. This means no processes started, no ETS tables created and nothing added to the process dictionary. + +This separation is done so you should feel comfortable instrumenting your Erlang/Elixir Application with OpenTelemetry and not worry that a complicated dependency is being forced on your users. + +## Use + +When instrumenting an Application to be used as a dependency of other projects it is best practice to register a `Tracer` with a name and a version using the Application's name and version. This should be the name and version of the Application that has the `opentelemetry` calls being written in it, not the name of the Application it might be being used to instrument. For example, an [Elli](https://github.com/elli-lib/elli) middleware to add tracing to the Elli HTTP server would *not* be named `elli`, it would be the name of the middleware Application, like `opentelemetry_elli`. + +Registration is done through a single process and uses a [persistent_term](https://erlang.org/doc/man/persistent_term.html), so should be done only once per-Application. Updating a registration is allowed, so updating the version on a release upgrade can, and should, be done, but will involve the performance penalty of updating a [persistent_term](https://erlang.org/doc/man/persistent_term.html). + +Naming the `Tracers` provides additional metadata on spans and allows the user of your Application to disable the traces from the dependency if it is needed. + +### Dependency in Elixir + +``` elixir +def deps do + [ + {:opentelemetry_api, "~> 0.3.0"} + ] +end +``` + +### Registering and Using Tracers Directly + +If it is a runnable application then this registration should happen in `start/2`, example below is adding `Tracer` registration to the Postgres library [pgo](https://github.com/erleans/pgo): + +``` erlang +start(_StartType, _StartArgs) -> + _ = opentelemetry:register_application_tracer(pgo), +... +``` + +Or for an Elixir Application named `MyApp`: + +``` elixir +defmodule MyApp do + use Application + + def start(_type, _args) do + _ = OpenTelemetry.register_application_tracer(:my_app), + ... + end +end +``` + +Then when the spans are started and finished in the application's code the `Tracer` is fetched with `get_tracer/1` and passed to `with_span/3` or `start_span/3`: + +``` erlang +Tracer = opentelemetry:get_tracer(pgo), +ot_tracer:with_span(Tracer, <<"pgo:query/3">>, fun() -> ... end). +``` + +A `Tracer` variable can be passed through your Application's calls so `get_tracer` only has to be called once, it is safe to store it in the state of a `gen_server` and to pass across process boundaries. + +If the application does not have a `start/2` there may be another function that is always called before the library would create any spans. For example, the [Elli](https://github.com/elli-lib/elli) middleware for OpenTelemetry instrumentation registers the `Tracer` during Elli startup: + +``` erlang +handle_event(elli_startup, _Args, _Config) -> + _ = opentelemetry:register_application_tracer(opentelemetry_elli), + ok; +``` + +When there is no startup of any kind to hook into in the library itself it should export a function `register_application_tracer/0` to be used by any application that depends on it to do the registration: + +``` erlang +-module(mylib). + +-export([register_tracer/0]). + +register_tracer() -> + _ = opentelemetry:register_application_tracer(mylib), + ok. +``` + +Not registering does not cause any issues or crashes, OpenTelemetry simply will fallback to the default `Tracer` if `get_tracer/1` is called with a name that is not registered. + + +### Helper Macros for Application Tracers + +When `register_application_tracer/1` is used to register a Tracer there are both Erlang and Elixir macros that make use of the current module's name to lookup the Tracer for you and can be used for Trace and Span operations: + +``` erlang +-include_lib("opentelemetry_api/include/tracer.hrl"). + +some_fun() -> + ?with_span(<<"some_fun/0">>, #{}, + fun(_SpanCtx) -> + ... + ?set_attribute(<<"key">>, <<"value">>), + ... + end), +``` + +``` elixir +require OpenTelemetry.Tracer +require OpenTelemetry.Span + +def some_fun() do + OpenTelemetry.Tracer.with_span "some-span" do + ... + OpenTelemetry.Span.set_attribute("key", "value") + ... + end +end +``` + +### Including the OpenTelemetry SDK + +For traces to actually be tracked, propagated and exported, the [opentelemetry](https://github.com/open-telemetry/opentelemetry-erlang) Application must be included as a dependency of your project, likely as part of a [Release](https://erlang.org/doc/design_principles/release_structure.html) and not as a dependency of an individual Application within the Release. + +See the [Using section](https://github.com/open-telemetry/opentelemetry-erlang#using) of the [OpenTelemetry-Erlang](https://github.com/open-telemetry/opentelemetry-erlang) repository for details. + +### Exporters + +Exporters can be found as separate Applications on Github under the [OpenTelemetry Beam Organization](https://github.com/opentelemetry-beam). + +- [Zipkin](https://hex.pm/packages/opentelemetry_zipkin) +- [OpenTelemetry Collector](https://hex.pm/packages/opentelemetry_exporter) + +### HTTP Integrations + +- [Elli](https://hex.pm/packages/opentelemetry_elli) + +### Database Client Integration + +- [Ecto](https://hex.pm/packages/opentelemetry_ecto) + +## Contributing + +See the [contributing file](CONTRIBUTING.md). diff --git a/apps/opentelemetry_api/VERSION b/apps/opentelemetry_api/VERSION new file mode 100644 index 00000000..d15723fb --- /dev/null +++ b/apps/opentelemetry_api/VERSION @@ -0,0 +1 @@ +0.3.2 diff --git a/apps/opentelemetry_api/include/meter.hrl b/apps/opentelemetry_api/include/meter.hrl new file mode 100644 index 00000000..dc722010 --- /dev/null +++ b/apps/opentelemetry_api/include/meter.hrl @@ -0,0 +1,52 @@ +%% macros for meters +%% register a meter for an application with opentelemetry:register_application_meter(AppName) + +-define(ot_current_meter, opentelemetry:get_meter(?MODULE)). + +-define(ot_new_counter(Meter, Name, Opts), + ot_counter:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_updown_counter(Meter, Name, Opts), + ot_updown_counter:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_value_recorder(Meter, Name, Opts), + ot_value_recorder:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_sum_observer(Meter, Name, Opts), + ot_sum_observer:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_updown_observer(Meter, Name, Opts), + ot_updown_observer:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_value_observer(Meter, Name, Opts), + ot_value_observer:new(?ot_current_meter, Name, Opts)). + +-define(ot_new_instruments(List), + ot_meter:new_instruments(?ot_current_meter, List)). + +-define(ot_counter_add(BoundCounter, Number), + ot_counter:add(BoundCounter, Number)). + +-define(ot_counter_add(Name, Number, LabelSet), + ot_counter:add(?ot_current_meter, Name, Number, LabelSet)). + +-define(ot_measure_record(BoundMeasure, Number), + ot_measure:record(BoundMeasure, Number)). + +-define(ot_measure_record(Name, Number, LabelSet), + ot_measure:record(?ot_current_meter, Name, Number, LabelSet)). + +-define(ot_bind(Name, LabelSet), + ot_meter:bind(?ot_current_meter, Name, LabelSet)). + +-define(ot_release(BoundInstrument), + ot_meter:release(?ot_current_meter, BoundInstrument)). + +-define(ot_record(Name, Number, LabelSet), + ot_meter:record(?ot_current_meter, Name, Number, LabelSet)). + +-define(ot_record(BoundInstrument, Number), + ot_meter:record(BoundInstrument, Number)). + +-define(ot_record_batch(LabelSet, Measurements), + ot_meter:record_batch(?ot_current_meter, LabelSet, Measurements)). diff --git a/apps/opentelemetry_api/include/opentelemetry.hrl b/apps/opentelemetry_api/include/opentelemetry.hrl new file mode 100644 index 00000000..84dff4e7 --- /dev/null +++ b/apps/opentelemetry_api/include/opentelemetry.hrl @@ -0,0 +1,90 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%%------------------------------------------------------------------------ + +%% These records are based on protos found in the opentelemetry-proto repo: +%% src/opentelemetry/proto/trace/v1/trace.proto +%% They are not exact translations because further processing is done after +%% the span has finished and can be vendor specific. For example, there is +%% no count of the number of dropped attributes in the span record. And +%% an attribute's value can be a function to only evaluate the value if it +%% is actually used (at the time of exporting). + +%% for use in guards: sampling bit is the first bit in 8-bit trace options +-define(IS_SPAN_ENABLED(TraceOptions), (TraceOptions band 1) =/= 0). + +-define(SPAN_KIND_INTERNAL, 'INTERNAL'). +-define(SPAN_KIND_SERVER, 'SERVER'). +-define(SPAN_KIND_CLIENT, 'CLIENT'). +-define(SPAN_KIND_PRODUCER, 'PRODUCER'). +-define(SPAN_KIND_CONSUMER, 'CONSUMER'). + +-record(span_ctx, { + %% 128 bit int trace id + trace_id :: opentelemetry:trace_id() | undefined, + %% 64 bit int span id + span_id :: opentelemetry:span_id() | undefined, + %% 8-bit integer, lowest bit is if it is sampled + trace_flags = 1 :: integer() | undefined, + %% Tracestate represents tracing-system specific context in a list of key-value pairs. + %% Tracestate allows different vendors propagate additional information and + %% inter-operate with their legacy Id formats. + tracestate :: opentelemetry:tracestate() | undefined, + %% IsValid is a boolean flag which returns true if the SpanContext has a non-zero + %% TraceID and a non-zero SpanID. + is_valid :: boolean() | undefined, + %% true if the span context came from a remote process + is_remote :: boolean() | undefined, + %% this field is not propagated and is only here as an implementation optimization + %% If true updates like adding events are done on the span. The same as if the + %% trace flags lowest bit is 1 but simply not propagated. + is_recording :: boolean() | undefined + }). + +-record(link, { + trace_id :: opentelemetry:trace_id(), + span_id :: opentelemetry:span_id(), + attributes :: opentelemetry:attributes(), + tracestate :: opentelemetry:tracestate() + }). + +-record(event, { + system_time_nano :: non_neg_integer(), + name :: unicode:unicode_binary() | atom(), + attributes = [] :: opentelemetry:attributes() + }). + +-record(status, { + code :: atom() | integer(), + %% developer-facing error message + message :: unicode:unicode_binary() + }). + +-define(OTEL_STATUS_OK, 'Ok'). +-define(OTEL_STATUS_CANCELLED, 'Cancelled'). +-define(OTEL_STATUS_UNKNOWN, 'UnknownError'). +-define(OTEL_STATUS_INVALID_ARGUMENT, 'InvalidArgument'). +-define(OTEL_STATUS_DEADLINE_EXCEEDED, 'DeadlineExceeded'). +-define(OTEL_STATUS_NOT_FOUND, 'NotFound'). +-define(OTEL_STATUS_ALREADY_EXISTS , 'AlreadyExists'). +-define(OTEL_STATUS_PERMISSION_DENIED, 'PermissionDenied'). +-define(OTEL_STATUS_RESOURCE_EXHAUSTED, 'ResourceExhausted'). +-define(OTEL_STATUS_FAILED_PRECONDITION, 'FailedPrecondition'). +-define(OTEL_STATUS_ABORTED, 'Aborted'). +-define(OTEL_STATUS_OUT_OF_RANGE, 'OutOfRange'). +-define(OTEL_STATUS_UNIMPLEMENTED, 'Unimplemented'). +-define(OTEL_STATUS_INTERNAL, 'InternalError'). +-define(OTEL_STATUS_UNAVAILABLE, 'Unavailable'). +-define(OTEL_STATUS_DATA_LOSS, 'DataLoss'). +-define(OTEL_STATUS_UNAUTHENTICATED, 'Unauthenticated'). diff --git a/apps/opentelemetry_api/include/ot_sampler.hrl b/apps/opentelemetry_api/include/ot_sampler.hrl new file mode 100644 index 00000000..de32bcc2 --- /dev/null +++ b/apps/opentelemetry_api/include/ot_sampler.hrl @@ -0,0 +1,3 @@ +-define(NOT_RECORD, not_record). +-define(RECORD, record). +-define(RECORD_AND_PROPAGATE, record_and_propagate). diff --git a/apps/opentelemetry_api/include/tracer.hrl b/apps/opentelemetry_api/include/tracer.hrl new file mode 100644 index 00000000..e6f3b27f --- /dev/null +++ b/apps/opentelemetry_api/include/tracer.hrl @@ -0,0 +1,53 @@ +%% macros for tracing +%% register a tracer for an application with opentelemetry:register_application_tracer(AppName) + +-define(current_tracer, opentelemetry:get_tracer(?MODULE)). +-define(current_span_ctx, ot_tracer:current_span_ctx(?current_tracer)). + +-define(start_span(SpanName), + ot_tracer:start_span(?current_tracer, SpanName, #{})). + +-define(start_span(SpanName, StartOpts), + ot_tracer:start_span(?current_tracer, SpanName, StartOpts)). + +-define(start_inactive_span(SpanName), + ot_tracer:start_inactive_span(?current_tracer, SpanName, #{})). + +-define(start_inactive_span(SpanName, StartOpts), + ot_tracer:start_inactive_span(?current_tracer, SpanName, StartOpts)). + +-define(set_span(SpanCtx), + ot_tracer:set_span(?current_tracer, SpanCtx)). + +-define(with_span(SpanName, StartOpts, Fun), + ot_tracer:with_span(?current_tracer, SpanName, StartOpts, Fun)). + +-define(end_span(), + ot_tracer:end_span(?current_tracer)). + +-define(current_span_ctx(), + ot_tracer:current_span_ctx(?current_tracer)). + +-define(is_recording(), + ot_span:is_recording(?current_tracer, ?current_span_ctx)). + +-define(set_attribute(Key, Value), + ot_span:set_attribute(?current_tracer, ?current_span_ctx, Key, Value)). + +-define(set_attributes(Attributes), + ot_span:set_attributes(?current_tracer, ?current_span_ctx, Attributes)). + +-define(add_event(Event), + ot_span:add_event(?current_tracer, ?current_span_ctx, Event)). + +-define(add_events(Events), + ot_span:add_events(?current_tracer, ?current_span_ctx, Events)). + +-define(add_links(Links), + ot_span:add_links(?current_tracer, ?current_span_ctx, Links)). + +-define(set_status(Status), + ot_span:set_status(?current_tracer, ?current_span_ctx, Status)). + +-define(update_name(Name), + ot_span:update_name(?current_tracer, ?current_span_ctx, Name)). diff --git a/apps/opentelemetry_api/lib/open_telemetry.ex b/apps/opentelemetry_api/lib/open_telemetry.ex new file mode 100644 index 00000000..2a63c20e --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry.ex @@ -0,0 +1,212 @@ +defmodule OpenTelemetry do + @moduledoc """ + An [OpenTelemetry](https://opentelemetry.io) Trace consists of 1 or more Spans that either have a + parent/child relationship or are linked together through a Link. Each Span has a TraceId (`t:trace_id/0`), + SpanId (`t:span_id/0`), and a start and end time in nanoseconds. + + This module provides declaration of the types used throughout the library, as well as functions for + building the additional pieces of a span that are optional. Each item can be attached to individual + Span using the functions in `OpenTelemetry.Span` module. + + ## Example + + require OpenTelemetry.Tracer + require OpenTelemetry.Span + + OpenTelemetry.register_application_tracer(:this_otp_app) + + Tracer.start_span("some-span") + ... + event = "ecto.query" + ecto_attributes = OpenTelemetry.event([{"query", query}, {"total_time", total_time}]) + OpenTelemetry.Span.add_event(event, ecto_event) + ... + Tracer.end_span() + """ + + @typedoc """ + A SpanContext represents the portion of a Span needed to do operations on a + Span. Within a process it acts as a key for looking up and modifying the + actual Span. It is also what is serialized and propagated across process + boundaries. + """ + @type span_ctx() :: :opentelemetry.span_ctx() + + @typedoc """ + TracerContext refers to the data kept in process by the tracer to track + the current SpanContext and the parent. + """ + @type tracer_ctx() :: :opentelemetry.tracer_ctx() + + @typedoc """ + Span represents a single operation within a trace. Spans can be + nested to form a trace tree. Spans may also be linked to other spans + from the same or different trace and form graphs. Often, a trace + contains a root span that describes the end-to-end latency, and one + or more subspans for its sub-operations. A trace can also contain + multiple root spans, or none at all. Spans do not need to be + contiguous - there may be gaps or overlaps between spans in a trace. + """ + @type span() :: :opentelemetry.span() + + @typedoc """ + TraceId is a unique identifier for a trace. All spans from the same trace share + the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes + is considered invalid. + """ + @type trace_id() :: non_neg_integer() + + @typedoc """ + SpanId is a unique identifier for a span within a trace, assigned when the span + is created. The ID is an 8-byte array. An ID with all zeroes is considered + invalid. + """ + @type span_id() :: non_neg_integer() + + @type attribute_key() :: String.t() + @type attribute_value() :: String.t() | integer() | float() | boolean() + + @typedoc """ + Attributes are a collection of key/value pairs. The value can be a string, + an integer, a double or the boolean values `true` or `false`. Note, global attributes + like server name can be set using the resource API. + + Examples of attributes: + + [{"/http/user_agent" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"} + {"/http/server_latency", 300} + {"abc.com/myattribute", True} + {"abc.com/score", 10.239}] + """ + @type attributes() :: [{attribute_key(), attribute_value()}] + + @typedoc """ + Tracestate represents tracing-system specific context in a list of key-value pairs. + Tracestate allows different vendors propagate additional information and + inter-operate with their legacy Id formats. + + It is a tracestate in the [w3c-trace-context format](https://www.w3.org/TR/trace-context/#tracestate-header). + See also [https://github.com/w3c/distributed-tracing](https://github.com/w3c/distributed-tracing) + for more details about this field. + """ + @type tracestate() :: [{String.t(), String.t()}] + + @typedoc """ + A Link is a pointer from the current span to another span in the same trace or in a + different trace. For example, this can be used in batching operations, + where a single batch handler processes multiple requests from different + traces or when the handler receives a request from a different project. + """ + @type link() :: :opentelemetry.link() + + @typedoc """ + An Event is a time-stamped annotation of the span, consisting of user-supplied + text description and key-value pairs. + """ + @type event() :: :opentelemetry.event() + + @typedoc """ + An optional final status for this span. Semantically when Status + wasn't set it means span ended without errors and assume `Ok`. + """ + @type status() :: :opentelemetry.status() + + @doc """ + Registering a [Named Tracer](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/api-tracing.md#obtaining-a-tracer) with the name of an OTP Application enables each module in + the Application to be mapped to the Named Tracer, named for the Application and using the + version of the currently loaded Application by that name. + + Macros in `OpenTelemetry.Tracer` use the name of the module they are being used in in order + to lookup the Named Tracer registered for that module and using it for trace operations. + """ + @spec register_application_tracer(atom()) :: boolean() + defdelegate register_application_tracer(otp_app), to: :opentelemetry + + @spec register_application_meter(atom()) :: boolean() + defdelegate register_application_meter(name), to: :opentelemetry + + # Helpers to build OpenTelemetry structured types + + @doc """ + A monotonically increasing time provided by the Erlang runtime system in the native time unit. + This value is the most accurate and precise timestamp available from the Erlang runtime and + should be used for finding durations or any timestamp that can be converted to a system + time before being sent to another system. + + Use `convert_timestamp/2` or `timestamp_to_nano/1` to convert a native monotonic time to a + system time of either nanoseconds or another unit. + + Using these functions allows timestamps to be accurate, used for duration and be exportable + as POSIX time when needed. + """ + @spec timestamp() :: integer() + defdelegate timestamp(), to: :opentelemetry + + @doc """ + Convert a native monotonic timestamp to nanosecond POSIX time. Meaning the time since Epoch. + Epoch is defined to be 00:00:00 UTC, 1970-01-01. + """ + @spec timestamp_to_nano(integer()) :: integer() + defdelegate timestamp_to_nano(timestamp), to: :opentelemetry + + @doc """ + Convert a native monotonic timestamp to POSIX time of any `:erlang.time_unit/0`. + Meaning the time since Epoch. Epoch is defined to be 00:00:00 UTC, 1970-01-01. + """ + @spec convert_timestamp(integer(), :erlang.time_unit()) :: integer() + defdelegate convert_timestamp(timestamp, unit), to: :opentelemetry + + # span item functions + + @doc """ + Creates a `t:link/0`. + """ + @spec link(trace_id(), span_id(), attributes(), tracestate()) :: link() + defdelegate link(trace_id, span_id, attributes, tracestate), to: :opentelemetry + + @doc """ + Creates a `t:link/0` from a `t:span_ctx/0`. + """ + @spec link(span_ctx() | :undefined) :: link() + defdelegate link(span_ctx), to: :opentelemetry + + @doc """ + Creates a `t:link/0` from a `t:span_ctx/0` and list of `t:attributes/0`. + """ + @spec link(span_ctx() | :undefined, attributes()) :: link() + defdelegate link(span_ctx, attributes), to: :opentelemetry + + @doc """ + Creates a list of `t:link/0` from a list of 4-tuples. + """ + @spec links([ + {integer(), integer(), attributes(), tracestate()} + | span_ctx() + | {span_ctx(), attributes()} + ]) :: [link()] + defdelegate links(link_list), to: :opentelemetry + + @doc """ + Creates a `t:event/0`. + """ + @spec event(String.t(), attributes()) :: event() + defdelegate event(name, attributes), to: :opentelemetry + + @doc """ + Creates a `t:event/0`. + """ + @spec event(integer(), String.t(), attributes()) :: event() + defdelegate event(timestamp, name, attributes), to: :opentelemetry + + @doc """ + Creates a list of `t:event/0` items. + """ + @spec events(list()) :: [event()] + defdelegate events(event_list), to: :opentelemetry + + @doc """ + Creates a Status. + """ + @spec status(atom(), String.t()) :: status() + defdelegate status(code, message), to: :opentelemetry +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/counter.ex b/apps/opentelemetry_api/lib/open_telemetry/counter.ex new file mode 100644 index 00000000..aa84b99a --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/counter.ex @@ -0,0 +1,31 @@ +defmodule OpenTelemetry.Counter do + @moduledoc """ + + require OpenTelemetry.Counter + + OpenTelemetry.Counter.new("some.counter") + + OpenTelemetry.Counter.add("some.counter", 3) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_counter.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro add(name, number, label_set) do + quote do + :ot_meter.record(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(number), unquote(label_set)) + end + end + + defmacro add(bound_instrument, number) do + quote do + :ot_meter.record(:opentelemetry.get_meter(__MODULE__), unquote(bound_instrument), unquote(number)) + end + end + + defdelegate definition(name, opts), to: :ot_counter + defdelegate measurement(name_or_instrument, number), to: :ot_counter +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/meter.ex b/apps/opentelemetry_api/lib/open_telemetry/meter.ex new file mode 100644 index 00000000..de969e0f --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/meter.ex @@ -0,0 +1,70 @@ +defmodule OpenTelemetry.Meter do + @moduledoc """ + + require OpenTelemetry.ValueRecorder + require OpenTelemetry.Counter + require OpenTelemetry.Meter + + OpenTelemetry.register_application_meter(Your.Application) + + OpenTelemetry.Meter.new_instruments([OpenTelemetry.ValueRecorder.instrument("some.latency"), + OpenTelemetry.Counter.instrument("some.counter")]) + + # use the new instrument by name + OpenTelemetry.Counter.add("some.counter", 1) + + # or use a bound instrument + bound = OpenTelemetry.Meter.bind("some.latency", []) + # measure time spent on some function and then record it + OpenTelemetry.ValueRecorder.record(bound, time) + """ + + defmacro new_instruments(list) do + quote do + :ot_meter.new_instruments(:opentelemetry.get_meter(__MODULE__), unquote(list)) + end + end + + defmacro bind(name, label_set) do + quote do + :ot_meter.bind(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(label_set)) + end + end + + defmacro release(bound_instrument) do + quote do + :ot_meter.release(:opentelemetry.get_meter(__MODULE__), unquote(bound_instrument)) + end + end + + defmacro record(name, number, label_set) do + quote do + :ot_meter.record( + :opentelemetry.get_meter(__MODULE__), + unquote(name), + unquote(number), + unquote(label_set) + ) + end + end + + defmacro record(bound_instrument, number) do + quote do + :ot_meter.record( + :opentelemetry.get_meter(__MODULE__), + unquote(bound_instrument), + unquote(number) + ) + end + end + + defmacro record_batch(label_set, measurements) do + quote do + :ot_meter.record_batch( + :opentelemetry.get_meter(__MODULE__), + unquote(label_set), + unquote(measurements) + ) + end + end +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/span.ex b/apps/opentelemetry_api/lib/open_telemetry/span.ex new file mode 100644 index 00000000..e2aaf74d --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/span.ex @@ -0,0 +1,137 @@ +defmodule OpenTelemetry.Span do + @moduledoc """ + This module contains macros for Span operations that update the active current Span in the current process. + An example of creating an Event and adding it to the current Span: + + require OpenTelemetry.Span + ... + event = "ecto.query" + ecto_attributes = OpenTelemetry.event([{"query", query}, {"total_time", total_time}]) + OpenTelemetry.Span.add_event(event, ecto_attributes) + ... + + A Span represents a single operation within a trace. Spans can be nested to form a trace tree. + Each trace contains a root span, which typically describes the end-to-end latency and, optionally, + one or more sub-spans for its sub-operations. + + Spans encapsulate: + + - The span name + - An immutable SpanContext (`t:OpenTelemetry.span_ctx/0`) that uniquely identifies the Span + - A parent Span in the form of a Span (`t:OpenTelemetry.span/0`), SpanContext (`t:OpenTelemetry.span_ctx/0`), or `undefined` + - A start timestamp + - An end timestamp + - An ordered mapping of Attributes (`t:OpenTelemetry.attributes/0`) + - A list of Links to other Spans (`t:OpenTelemetry.link/0`) + - A list of timestamped Events (`t:OpenTelemetry.event/0`) + - A Status (`t:OpenTelemetry.status/0`) + """ + + @doc """ + Get the SpanId of a Span. + """ + @spec span_id(OpenTelemetry.span_ctx()) :: OpenTelemetry.span_id() + defdelegate span_id(span), to: :ot_span + + @doc """ + Get the TraceId of a Span. + """ + @spec trace_id(OpenTelemetry.span_ctx()) :: OpenTelemetry.trace_id() + defdelegate trace_id(span), to: :ot_span + + @doc """ + Get the Tracestate of a Span. + """ + @spec tracestate(OpenTelemetry.span_ctx()) :: OpenTelemetry.tracestate() + defdelegate tracestate(span), to: :ot_span + + @doc """ + Set an attribute with key and value on the currently active Span. + """ + @spec set_attribute(OpenTelemetry.attribute_key(), OpenTelemetry.attribute_value()) :: boolean() + defmacro set_attribute(key, value) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + + :ot_span.set_attribute( + tracer, + :ot_tracer.current_span_ctx(tracer), + unquote(key), + unquote(value) + ) + end + end + + @doc """ + Add a list of attributes to the currently active Span. + """ + @spec set_attributes([OpenTelemetry.attribute()]) :: boolean() + defmacro set_attributes(attributes) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + :ot_span.set_attributes(tracer, :ot_tracer.current_span_ctx(tracer), unquote(attributes)) + end + end + + @doc """ + Add an event to the currently active Span. + """ + @spec add_event(String.t(), [OpenTelemetry.attribute()]) :: boolean() + defmacro add_event(event, attributes) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + + :ot_span.add_event( + tracer, + :ot_tracer.current_span_ctx(tracer), + unquote(event), + unquote(attributes) + ) + end + end + + @doc """ + Add a list of events to the currently active Span. + """ + @spec add_events([OpenTelemetry.event()]) :: boolean() + defmacro add_events(events) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + :ot_span.add_events(tracer, :ot_tracer.current_span_ctx(tracer), unquote(events)) + end + end + + @doc """ + Sets the Status of the currently active Span. + + If used, this will override the default Span Status, which is `Ok`. + """ + @spec set_status(OpenTelemetry.status()) :: boolean() + defmacro set_status(status) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + :ot_span.set_status(tracer, :ot_tracer.current_span_ctx(tracer), unquote(status)) + end + end + + @doc """ + Updates the Span name. + + It is highly discouraged to update the name of a Span after its creation. Span name is + often used to group, filter and identify the logical groups of spans. And often, filtering + logic will be implemented before the Span creation for performance reasons. Thus the name + update may interfere with this logic. + + The function name is called UpdateName to differentiate this function from the regular + property setter. It emphasizes that this operation signifies a major change for a Span + and may lead to re-calculation of sampling or filtering decisions made previously + depending on the implementation. + """ + @spec update_name(String.t()) :: boolean() + defmacro update_name(name) do + quote do + tracer = :opentelemetry.get_tracer(__MODULE__) + :ot_span.update_name(tracer, :ot_tracer.current_span_ctx(tracer), unquote(name)) + end + end +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/sum_observer.ex b/apps/opentelemetry_api/lib/open_telemetry/sum_observer.ex new file mode 100644 index 00000000..78f3741b --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/sum_observer.ex @@ -0,0 +1,27 @@ +defmodule OpenTelemetry.SumObserver do + @moduledoc """ + + require OpenTelemetry.SumObserver + + OpenTelemetry.SumObserver.set_callback("some.counter", &OpenTelemetry.SumObserver.observe(&1, 33, [])) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_sum_observer.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro set_callback(observer, callback) do + quote do + :ot_meter.set_observer_callback( + :opentelemetry.get_meter(__MODULE__), + unquote(observer), + unquote(callback) + ) + end + end + + defdelegate definition(name, opts), to: :ot_sum_observer + defdelegate observe(observer_result, number, label_set), to: :ot_sum_observer +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/tracer.ex b/apps/opentelemetry_api/lib/open_telemetry/tracer.ex new file mode 100644 index 00000000..b8edb4dc --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/tracer.ex @@ -0,0 +1,118 @@ +defmodule OpenTelemetry.Tracer do + @moduledoc """ + This module contains macros for Tracer operations around the lifecycle of the Spans within a Trace. + + The Tracer is able to start a new Span as a child of the active Span of the current process, set + a different Span to be the current Span by passing the Span's context, end a Span or run a code + block within the context of a newly started span that is ended when the code block completes. + + The macros use the Tracer registered to the Application the module using the macro is included in, + assuming `OpenTelemetry.register_application_tracer/1` has been called for the Application. If + not then the default Tracer is used. + + require OpenTelemetry.Tracer + + OpenTelemetry.Tracer.with_span "span-1" do + ... do something ... + end + """ + + @type start_opts() :: %{ + optional(:parent) => OpenTelemetry.span() | OpenTelemetry.span_ctx(), + optional(:attributes) => OpenTelemetry.attributes(), + optional(:sampler) => :ot_sampler.sampler(), + optional(:links) => OpenTelemetry.links(), + optional(:is_recording) => boolean(), + optional(:start_time) => :opentelemetry.timestamp(), + optional(:kind) => OpenTelemetry.span_kind() + } + + @doc """ + Starts a new span and makes it the current active span of the current process. + + The current active Span is used as the parent of the created Span unless a `parent` is given in the + `t:start_opts/0` argument or there is no active Span. If there is neither a current Span or a + `parent` option given then the Tracer checks for an extracted SpanContext to use as the parent. If + there is also no extracted context then the created Span is a root Span. + """ + defmacro start_span(name, opts \\ quote(do: %{})) do + quote bind_quoted: [name: name, start_opts: opts] do + :ot_tracer.start_span(:opentelemetry.get_tracer(__MODULE__), name, start_opts) + end + end + + @doc """ + Starts a new span but does not make it the current active span of the current process. + + This is particularly useful when creating a child Span that is for a new process. Before spawning + the new process start an inactive Span, which uses the current context as the parent, then + pass this new SpanContext as an argument to the spawned function and in that function use + `set_span/1`. + + The current active Span is used as the parent of the created Span unless a `parent` is given in the + `t:start_opts/0` argument or there is no active Span. If there is neither a current Span or a + `parent` option given then the Tracer checks for an extracted SpanContext to use as the parent. If + there is also no extracted context then the created Span is a root Span. + """ + defmacro start_inactive_span(name, opts \\ quote(do: %{})) do + quote bind_quoted: [name: name, start_opts: opts] do + :ot_tracer.start_inactive_span(:opentelemetry.get_tracer(__MODULE__), name, start_opts) + end + end + + @doc """ + Takes a `t:OpenTelemetry.span_ctx/0` and the Tracer sets it to the currently active Span. + """ + defmacro set_span(span_ctx) do + quote bind_quoted: [span_ctx: span_ctx] do + :ot_tracer.set_span(:opentelemetry.get_tracer(__MODULE__), span_ctx) + end + end + + @doc """ + End the Span. Sets the end timestamp for the currently active Span. This has no effect on any + child Spans that may exist of this Span. + + The default Tracer in the OpenTelemetry Erlang/Elixir SDK will then set the parent, if there + is a local parent of the current Span, to the current active Span. + """ + defmacro end_span() do + quote do + :ot_tracer.end_span(:opentelemetry.get_tracer(__MODULE__)) + end + end + + @doc """ + Creates a new span which is ended automatically when the `block` completes. + + See `start_span/2` and `end_span/0`. + """ + defmacro with_span(name, start_opts \\ quote(do: %{}), do: block) do + quote do + :ot_tracer.with_span( + :opentelemetry.get_tracer(__MODULE__), + unquote(name), + unquote(start_opts), + fn _ -> unquote(block) end + ) + end + end + + @doc """ + Returns the currently active `t:OpenTelemetry.tracer_ctx/0`. + """ + defmacro current_ctx() do + quote do + :ot_tracer.current_ctx(:opentelemetry.get_tracer(__MODULE__)) + end + end + + @doc """ + Returns the currently active `t:OpenTelemetry.span_ctx/0`. + """ + defmacro current_span_ctx() do + quote do + :ot_tracer.current_span_ctx(:opentelemetry.get_tracer(__MODULE__)) + end + end +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/updown_counter.ex b/apps/opentelemetry_api/lib/open_telemetry/updown_counter.ex new file mode 100644 index 00000000..96674839 --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/updown_counter.ex @@ -0,0 +1,31 @@ +defmodule OpenTelemetry.UpdownCounter do + @moduledoc """ + + require OpenTelemetry.UpdownCounter + + OpenTelemetry.UpdownCounter.new("some.counter") + + OpenTelemetry.UpdownCounter.add("some.counter", -3) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_updown_counter.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro add(name, number, label_set) do + quote do + :ot_meter.record(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(number), unquote(label_set)) + end + end + + defmacro add(bound_instrument, number) do + quote do + :ot_meter.record(:opentelemetry.get_meter(__MODULE__), unquote(bound_instrument), unquote(number)) + end + end + + defdelegate definition(name, opts), to: :ot_updown_counter + defdelegate measurement(name_or_instrument, number), to: :ot_updown_counter +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/updown_sum_observer.ex b/apps/opentelemetry_api/lib/open_telemetry/updown_sum_observer.ex new file mode 100644 index 00000000..8bd658df --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/updown_sum_observer.ex @@ -0,0 +1,27 @@ +defmodule OpenTelemetry.UpdownSumObserver do + @moduledoc """ + + require OpenTelemetry.UpdownSumObserver + + OpenTelemetry.UpdownSumObserver.set_callback("some.counter", OpenTelemetry.UpdownSumObserver.observe(&1, -33, [])) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_updown_sum_observer.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro set_callback(observer, callback) do + quote do + :ot_meter.set_observer_callback( + :opentelemetry.get_meter(__MODULE__), + unquote(observer), + unquote(callback) + ) + end + end + + defdelegate definition(name, opts), to: :ot_updown_sum_observer + defdelegate observe(observer_result, number, label_set), to: :ot_updown_sum_observer +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/value_observer.ex b/apps/opentelemetry_api/lib/open_telemetry/value_observer.ex new file mode 100644 index 00000000..25e86c73 --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/value_observer.ex @@ -0,0 +1,27 @@ +defmodule OpenTelemetry.ValueObserver do + @moduledoc """ + + require OpenTelemetry.ValueObserver + + OpenTelemetry.ValueObserver.set_callback("some.counter", fn o -> OpenTelemetry.ValueObserver.observe(o, 33, [])) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_value_observer.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro set_callback(observer, callback) do + quote do + :ot_meter.set_observer_callback( + :opentelemetry.get_meter(__MODULE__), + unquote(observer), + unquote(callback) + ) + end + end + + defdelegate definition(name, opts), to: :ot_value_observer + defdelegate observe(observer_result, number, label_set), to: :ot_value_observer +end diff --git a/apps/opentelemetry_api/lib/open_telemetry/value_recorder.ex b/apps/opentelemetry_api/lib/open_telemetry/value_recorder.ex new file mode 100644 index 00000000..0c1a22df --- /dev/null +++ b/apps/opentelemetry_api/lib/open_telemetry/value_recorder.ex @@ -0,0 +1,38 @@ +defmodule OpenTelemetry.ValueRecorder do + @moduledoc """ + + require OpenTelemetry.ValueRecorder + + OpenTelemetry.ValueRecorder.record("some.recorder", 3) + """ + + defmacro new(name, opts \\ %{}) do + quote do + :ot_value_recorder.new(:opentelemetry.get_meter(__MODULE__), unquote(name), unquote(opts)) + end + end + + defmacro record(name, number, label_set) do + quote do + :ot_meter.record( + :opentelemetry.get_meter(__MODULE__), + unquote(name), + unquote(number), + unquote(label_set) + ) + end + end + + defmacro record(bound_instrument, number) do + quote do + :ot_meter.record( + :opentelemetry.get_meter(__MODULE__), + unquote(bound_instrument), + unquote(number) + ) + end + end + + defdelegate definition(name, opts), to: :ot_value_recorder + defdelegate measurement(name_or_instrument, number), to: :ot_value_recorder +end diff --git a/apps/opentelemetry_api/mix.exs b/apps/opentelemetry_api/mix.exs new file mode 100644 index 00000000..eb6c34c5 --- /dev/null +++ b/apps/opentelemetry_api/mix.exs @@ -0,0 +1,90 @@ +defmodule OpenTelemetry.MixProject do + use Mix.Project + + def project do + {app, desc} = load_app() + config = load_config() + + [ + app: app, + version: version(Keyword.fetch!(desc, :vsn)), + description: to_string(Keyword.fetch!(desc, :description)), + elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + # We should never have dependencies + deps: deps(Keyword.fetch!(config, :deps)), + # Docs + name: "OpenTelemetry API", + # source_url: "https://github.com/USER/PROJECT", + # homepage_url: "http://YOUR_PROJECT_HOMEPAGE", + docs: [ + markdown_processor: ExDoc.Markdown.Cmark, + main: "OpenTelemetry", + # logo: "path/to/logo.png", + extras: erlang_docs() + ], + aliases: [ + # when build docs first build edocs with rebar3 + docs: ["cmd rebar3 edoc", "docs"] + ], + package: package() + ] + end + + defp version(version) when is_list(version) do + List.to_string(version) + end + + defp version({:file, path}) do + path + |> File.read!() + |> String.trim() + end + + # Run "mix help compile.app" to learn about applications. + def application, do: [] + + defp deps(rebar) do + rebar + |> Enum.map(fn + {dep, version} -> {dep, to_string(version)} + dep when is_atom(dep) -> {dep, ">= 0.0.0"} + end) + |> Enum.concat([ + {:cmark, "~> 0.7", only: :dev, runtime: false}, + {:ex_doc, "~> 0.21", only: :dev, runtime: false} + ]) + end + + defp package() do + [ + description: "OpenTelemetry API", + build_tools: ["rebar3", "mix"], + files: ~w(lib mix.exs README.md LICENSE CODEOWNERS rebar.config rebar.lock VERSION include src), + licenses: ["Apache-2.0"], + links: %{"GitHub" => "https://github.com/open-telemetry/opentelemetry-erlang-api", + "OpenTelemetry.io" => "https://opentelemetry.io"} + ] + end + + def erlang_docs() do + files = + for file <- Path.wildcard("edoc/*.md"), + file != "edoc/README.md", + do: {String.to_atom(file), [title: Path.basename(file, ".md")]} + + [{:"README.md", [title: "Overview"]} | files] + end + + defp load_config do + {:ok, config} = :file.consult('rebar.config') + + config + end + + defp load_app do + {:ok, [{:application, name, desc}]} = :file.consult('src/opentelemetry_api.app.src') + + {name, desc} + end +end diff --git a/apps/opentelemetry_api/mix.lock b/apps/opentelemetry_api/mix.lock new file mode 100644 index 00000000..6535f3d0 --- /dev/null +++ b/apps/opentelemetry_api/mix.lock @@ -0,0 +1,9 @@ +%{ + "cmark": {:hex, :cmark, "0.7.0", "cf20106714b35801df3a2250a8ca478366f93ad6067df9d6815c80b2606ae01e", [:make, :mix], [], "hexpm", "deffa0b66cba126433efb54c068354bcf7c1fd8ba0f579741ab1d3cb26e0f942"}, + "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, + "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, + "wts": {:hex, :wts, "0.4.0", "62d9dc400ad29f0d233f0665b9c75c8f8eb0a8af75eec921056ba4a73b0605a2", [:rebar3], [], "hexpm", "711bb675de2ce2b3ebab80a613ac93b994f74df44af4cff7970dc9eebe724869"}, +} diff --git a/apps/opentelemetry_api/rebar.config b/apps/opentelemetry_api/rebar.config new file mode 100644 index 00000000..90e27fdb --- /dev/null +++ b/apps/opentelemetry_api/rebar.config @@ -0,0 +1,10 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{profiles, + [{docs, [{deps, [edown]}, + {edoc_opts, + [{doclet, edown_doclet}, + {preprocess, true}, + {dir, "edoc"}, + {subpackages, true}]}]}]}. diff --git a/apps/opentelemetry_api/src/opentelemetry.erl b/apps/opentelemetry_api/src/opentelemetry.erl new file mode 100644 index 00000000..a7250d2c --- /dev/null +++ b/apps/opentelemetry_api/src/opentelemetry.erl @@ -0,0 +1,396 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc The types defined here, and referencing records in opentelemetry.hrl +%% are used to store trace information while being collected on the +%% Erlang node. +%% +%% Thus, while the types are based on protos found in the opentelemetry-proto +%% repo: src/opentelemetry/proto/trace/v1/trace.proto, +%% they are not exact translations because further processing is done after +%% the span has finished and can be vendor specific. For example, there is +%% no count of the number of dropped attributes in the span record. And +%% an attribute's value can be a function to only evaluate the value if it +%% is actually used (at the time of exporting). And the stacktrace is a +%% regular Erlang stack trace. +%% @end +%%%------------------------------------------------------------------------- +-module(opentelemetry). + +-export([set_default_tracer/1, + set_tracer/2, + set_meter/2, + set_default_meter/1, + register_tracer/2, + register_application_tracer/1, + register_meter/2, + register_application_meter/1, + get_tracer/0, + get_tracer/1, + get_meter/0, + get_meter/1, + set_http_extractor/1, + get_http_extractor/0, + set_http_injector/1, + get_http_injector/0, + timestamp/0, + timestamp_to_nano/1, + convert_timestamp/2, + links/1, + link/1, + link/2, + link/4, + event/2, + event/3, + events/1, + status/2, + generate_trace_id/0, + generate_span_id/0]). + +-include("opentelemetry.hrl"). +-include_lib("kernel/include/logger.hrl"). + +-export_type([tracer/0, + meter/0, + trace_id/0, + span_id/0, + timestamp/0, + span_name/0, + span_ctx/0, + span/0, + span_kind/0, + link/0, + links/0, + attribute_key/0, + attribute_value/0, + attributes/0, + event/0, + events/0, + stack_trace/0, + tracestate/0, + status/0, + resource/0, + http_headers/0]). + +-type tracer() :: {module(), term()}. +-type meter() :: {module(), term()}. + +-type trace_id() :: non_neg_integer(). +-type span_id() :: non_neg_integer(). + +-type timestamp() :: integer(). + +-type span_ctx() :: #span_ctx{}. +-type span() :: term(). +-type span_name() :: unicode:unicode_binary() | atom(). + +-type attribute_key() :: unicode:unicode_binary() | atom(). +-type attribute_value() :: any(). +-type attribute() :: {attribute_key(), attribute_value()}. +-type attributes() :: [attribute()]. + +-type span_kind() :: ?SPAN_KIND_INTERNAL | + ?SPAN_KIND_SERVER | + ?SPAN_KIND_CLIENT | + ?SPAN_KIND_PRODUCER | + ?SPAN_KIND_CONSUMER. +-type event() :: #event{}. +-type events() :: [#event{}]. +-type link() :: #link{}. +-type links() :: [#link{}]. +-type status() :: #status{}. + +%% The key must begin with a lowercase letter, and can only contain +%% lowercase letters 'a'-'z', digits '0'-'9', underscores '_', dashes +%% '-', asterisks '*', and forward slashes '/'. +%% The value is opaque string up to 256 characters printable ASCII +%% RFC0020 characters (i.e., the range 0x20 to 0x7E) except ',' and '='. +%% Note that this also excludes tabs, newlines, carriage returns, etc. +-type tracestate() :: [{unicode:latin1_chardata(), unicode:latin1_chardata()}]. + +-type stack_trace() :: [erlang:stack_item()]. + +-type resource() :: #{unicode:unicode_binary() => unicode:unicode_binary()}. + +-type http_headers() :: [{unicode:unicode_binary(), unicode:unicode_binary()}]. + +-spec set_default_tracer(tracer()) -> boolean(). +set_default_tracer(Tracer) -> + verify_and_set_term(Tracer, default_tracer, ot_tracer). + +-spec set_tracer(atom(), tracer()) -> boolean(). +set_tracer(Name, Tracer) -> + verify_and_set_term(Tracer, Name, ot_tracer). + +-spec set_default_meter(meter()) -> boolean(). +set_default_meter(Meter) -> + verify_and_set_term(Meter, default_meter, ot_meter). + +-spec set_meter(atom(), meter()) -> boolean(). +set_meter(Name, Meter) -> + verify_and_set_term(Meter, Name, ot_meter). + +-spec register_tracer(atom(), string()) -> boolean(). +register_tracer(Name, Vsn) -> + ot_tracer_provider:register_tracer(Name, Vsn). + +-spec register_application_tracer(atom()) -> boolean(). +register_application_tracer(Name) -> + ot_tracer_provider:register_application_tracer(Name). + +-spec register_meter(atom(), string()) -> boolean(). +register_meter(Name, Vsn) -> + ot_meter_provider:register_meter(Name, Vsn). + +-spec register_application_meter(atom()) -> boolean(). +register_application_meter(Name) -> + ot_meter_provider:register_application_meter(Name). + +-spec get_tracer() -> tracer(). +get_tracer() -> + persistent_term:get({?MODULE, default_tracer}, {ot_tracer_noop, []}). + +-spec get_tracer(atom()) -> tracer(). +get_tracer(Name) -> + persistent_term:get({?MODULE, Name}, get_tracer()). + +-spec get_meter() -> meter(). +get_meter() -> + persistent_term:get({?MODULE, default_meter}, {ot_meter_noop, []}). + +-spec get_meter(atom()) -> meter(). +get_meter(Name) -> + persistent_term:get({?MODULE, Name}, get_meter()). + +set_http_extractor(List) when is_list(List) -> + persistent_term:put({?MODULE, http_extractor}, List); +set_http_extractor(_) -> + ok. + +set_http_injector(List) when is_list(List) -> + persistent_term:put({?MODULE, http_injector}, List); +set_http_injector(_) -> + ok. + +get_http_extractor() -> + persistent_term:get({?MODULE, http_extractor}, []). + +get_http_injector() -> + persistent_term:get({?MODULE, http_injector}, []). + +%% @doc A monotonically increasing time provided by the Erlang runtime system in the native time unit. +%% This value is the most accurate and precise timestamp available from the Erlang runtime and +%% should be used for finding durations or any timestamp that can be converted to a system +%% time before being sent to another system. + +%% Use {@link convert_timestamp/2} or {@link timestamp_to_nano/1} to convert a native monotonic time to a +%% system time of either nanoseconds or another {@link erlang:time_unit()}. + +%% Using these functions allows timestamps to be accurate, used for duration and be exportable +%% as POSIX time when needed. +%% @end +-spec timestamp() -> integer(). +timestamp() -> + erlang:monotonic_time(). + +%% @doc Convert a native monotonic timestamp to nanosecond POSIX time. Meaning the time since Epoch. +%% Epoch is defined to be 00:00:00 UTC, 1970-01-01. +%% @end +-spec timestamp_to_nano(timestamp()) -> integer(). +timestamp_to_nano(Timestamp) -> + convert_timestamp(Timestamp, nanosecond). + +%% @doc Convert a native monotonic timestamp to POSIX time of any {@link erlang:time_unit()}. +%% Meaning the time since Epoch. Epoch is defined to be 00:00:00 UTC, 1970-01-01. +%% @end +-spec convert_timestamp(timestamp(), erlang:time_unit()) -> integer(). +convert_timestamp(Timestamp, Unit) -> + Offset = erlang:time_offset(), + erlang:convert_time_unit(Timestamp + Offset, native, Unit). + +-spec links([{TraceId, SpanId, Attributes, TraceState} | span_ctx() | {span_ctx(), Attributes}]) -> links() when + TraceId :: trace_id(), + SpanId :: span_id(), + Attributes :: attributes(), + TraceState :: tracestate(). +links(List) when is_list(List) -> + lists:filtermap(fun({TraceId, SpanId, Attributes, TraceState}) when is_integer(TraceId) , + is_integer(SpanId) , + is_list(Attributes) , + is_list(TraceState) -> + link_or_false(TraceId, SpanId, Attributes, TraceState); + ({#span_ctx{trace_id=TraceId, + span_id=SpanId, + tracestate=TraceState}, Attributes}) when is_integer(TraceId) , + is_integer(SpanId) , + is_list(Attributes) , + is_list(TraceState) -> + link_or_false(TraceId, SpanId, Attributes, TraceState); + (#span_ctx{trace_id=TraceId, + span_id=SpanId, + tracestate=TraceState}) when is_integer(TraceId) , + is_integer(SpanId) , + is_list(TraceState) -> + link_or_false(TraceId, SpanId, [], TraceState); + (_) -> + false + end, List); +links(_) -> + []. + +-spec link(span_ctx() | undefined) -> link(). +link(SpanCtx) -> + link(SpanCtx, []). + +-spec link(span_ctx() | undefined, attributes()) -> link(). +link(#span_ctx{trace_id=TraceId, + span_id=SpanId, + tracestate=TraceState}, Attributes) -> + ?MODULE:link(TraceId, SpanId, Attributes, TraceState); +link(_, _) -> + undefined. + +-spec link(TraceId, SpanId, Attributes, TraceState) -> link() | undefined when + TraceId :: trace_id(), + SpanId :: span_id(), + Attributes :: attributes(), + TraceState :: tracestate(). +link(TraceId, SpanId, Attributes, TraceState) when is_integer(TraceId), + is_integer(SpanId), + is_list(Attributes), + is_list(TraceState) -> + #link{trace_id=TraceId, + span_id=SpanId, + attributes=Attributes, + tracestate=TraceState}; +link(_, _, _, _) -> + undefined. + +-spec event(Name, Attributes) -> event() | undefined when + Name :: unicode:unicode_binary(), + Attributes :: attributes(). +event(Name, Attributes) when is_binary(Name), + is_list(Attributes) -> + event(erlang:system_time(nanosecond), Name, Attributes); +event(_, _) -> + undefined. + +-spec event(Timestamp, Name, Attributes) -> event() | undefined when + Timestamp :: non_neg_integer(), + Name :: unicode:unicode_binary(), + Attributes :: attributes(). +event(Timestamp, Name, Attributes) when is_integer(Timestamp), + is_binary(Name), + is_list(Attributes) -> + #event{system_time_nano=Timestamp, + name=Name, + attributes=Attributes}; +event(_, _, _) -> + undefined. + +events(List) -> + Timestamp = timestamp(), + lists:filtermap(fun({Time, Name, Attributes}) when is_binary(Name), + is_list(Attributes) -> + case event(Time, Name, Attributes) of + undefined -> + false; + Event -> + {true, Event} + end; + ({Name, Attributes}) when is_binary(Name), + is_list(Attributes) -> + case event(Timestamp, Name, Attributes) of + undefined -> + false; + Event -> + {true, Event} + end; + (_) -> + false + end, List). + +-spec status(Code, Message) -> status() | undefined when + Code :: atom(), + Message :: unicode:unicode_binary(). +status(Code, Message) when is_atom(Code), + is_binary(Message) -> + #status{code=Code, + message=Message}; +status(_, _) -> + undefined. + +%%-------------------------------------------------------------------- +%% @doc +%% Generates a 128 bit random integer to use as a trace id. +%% @end +%%-------------------------------------------------------------------- +-spec generate_trace_id() -> trace_id(). +generate_trace_id() -> + uniform(2 bsl 127 - 1). %% 2 shifted left by 127 == 2 ^ 128 + +%%-------------------------------------------------------------------- +%% @doc +%% Generates a 64 bit random integer to use as a span id. +%% @end +%%-------------------------------------------------------------------- +-spec generate_span_id() -> span_id(). +generate_span_id() -> + uniform(2 bsl 63 - 1). %% 2 shifted left by 63 == 2 ^ 64 + +uniform(X) -> + rand:uniform(X). + +%% internal functions + +-spec verify_and_set_term(module() | {module(), term()}, term(), atom()) -> boolean(). +verify_and_set_term(Module, TermKey, Behaviour) -> + case verify_behaviour(Module, Behaviour) of + true -> + persistent_term:put({?MODULE, TermKey}, Module), + true; + false -> + ?LOG_WARNING("Module ~p does not implement behaviour ~p. " + "A noop ~p will be used until a module implementing " + "the behaviour is configured.", + [Module, Behaviour, Behaviour]), + false + end. + +-spec verify_behaviour(module() | {module(), term()}, atom()) -> boolean(). +verify_behaviour({Module, _}, Behaviour) -> + verify_behaviour(Module, Behaviour); +verify_behaviour(Module, Behaviour) -> + try Module:module_info(attributes) of + Attributes -> + case lists:keyfind(behaviour, 1, Attributes) of + {behaviour, Behaviours} -> + lists:member(Behaviour, Behaviours); + _ -> + false + end + catch + error:undef -> + false + end. + +%% for use in a filtermap +%% return {true, Link} if a link is returned or return false +link_or_false(TraceId, SpanId, Attributes, TraceState) -> + case link(TraceId, SpanId, Attributes, TraceState) of + Link=#link{} -> + {true, Link}; + _ -> + false + end. diff --git a/apps/opentelemetry_api/src/opentelemetry_api.app.src b/apps/opentelemetry_api/src/opentelemetry_api.app.src new file mode 100644 index 00000000..45c56f79 --- /dev/null +++ b/apps/opentelemetry_api/src/opentelemetry_api.app.src @@ -0,0 +1,14 @@ +{application, opentelemetry_api, + [{description, "OpenTelemetry API"}, + {vsn, {file, "VERSION"}}, + {registered, []}, + {applications, + [kernel, + stdlib + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache-2.0"]}, + {links, [{"GitHub", "https://github.com/open-telemetry/opentelemetry-erlang-api"}]} + ]}. diff --git a/apps/opentelemetry_api/src/ot_baggage.erl b/apps/opentelemetry_api/src/ot_baggage.erl new file mode 100644 index 00000000..a3b8ff61 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_baggage.erl @@ -0,0 +1,87 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_baggage). + +-export([ctx_key/0, + set/2, + get_all/0, + get_http_propagators/0]). + +-type key() :: string(). +-type value() :: string(). + +-type t() :: #{key() => value()}. + +-export_type([t/0, + key/0, + value/0]). + +-define(BAGGAGE_KEY, '$__ot_baggage_ctx_key'). +-define(BAGGAGE_HEADER, <<"otcorrelations">>). + +ctx_key() -> + ?BAGGAGE_KEY. + +-spec set(key(), value()) -> ok. +set(Key, Value) -> + Baggage = ot_ctx:get_value(?BAGGAGE_KEY, #{}), + ot_ctx:set_value(?BAGGAGE_KEY, Baggage#{Key => Value}). + +-spec get_all() -> t(). +get_all() -> + ot_ctx:get_value(?BAGGAGE_KEY, #{}). + +-spec get_http_propagators() -> {ot_propagation:http_extractor(), ot_propagation:http_injector()}. +get_http_propagators() -> + ToText = fun(_Headers, Baggage) -> + case maps:fold(fun(Key, Value, Acc) -> + [$,, [Key, "=", Value] | Acc] + end, [], Baggage) of + [$, | List] -> + [{?BAGGAGE_HEADER, unicode:characters_to_list(List)}]; + _ -> + [] + end + end, + FromText = fun(Headers, CurrentBaggage) -> + case lookup(?BAGGAGE_HEADER, Headers) of + undefined -> + CurrentBaggage; + String -> + Pairs = string:lexemes(String, [$,]), + lists:foldl(fun(Pair, Acc) -> + [Key, Value] = string:split(Pair, "="), + Acc#{unicode:characters_to_list(Key) => + unicode:characters_to_list(Value)} + end, CurrentBaggage, Pairs) + end + end, + Inject = ot_ctx:http_injector(?BAGGAGE_KEY, ToText), + Extract = ot_ctx:http_extractor(?BAGGAGE_KEY, FromText), + {Extract, Inject}. + +%% find a header in a list, ignoring case +lookup(_, []) -> + undefined; +lookup(Header, [{H, Value} | Rest]) -> + case string:equal(Header, H, true, none) of + true -> + Value; + false -> + lookup(Header, Rest) + end. diff --git a/apps/opentelemetry_api/src/ot_counter.erl b/apps/opentelemetry_api/src/ot_counter.erl new file mode 100644 index 00000000..9e91464c --- /dev/null +++ b/apps/opentelemetry_api/src/ot_counter.erl @@ -0,0 +1,62 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_counter). + +-behaviour(ot_instrument). + +-export([new/2, + new/3, + definition/1, + definition/2, + add/2, + add/4, + measurement/2]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => true, + synchronous => true}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec add(ot_meter:bound_instrument(), number()) -> ok. +add(BoundInstrument, Number) -> + ot_meter:record(BoundInstrument, Number). + +-spec add(opentelemetry:meter(), ot_meter:name(), number(), ot_meter:labels()) -> ok. +add(Meter, Name, Number, Labels) -> + ot_meter:record(Meter, Name, Number, Labels). + +-spec measurement(ot_meter:bound_instrument() | ot_meter:name(), number()) + -> {ot_meter:bound_instrument() | ot_meter:name(), number()}. +measurement(NameOrInstrument, Number) -> + {NameOrInstrument, Number}. diff --git a/apps/opentelemetry_api/src/ot_ctx.erl b/apps/opentelemetry_api/src/ot_ctx.erl new file mode 100644 index 00000000..eae6a198 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_ctx.erl @@ -0,0 +1,141 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_ctx). + +-export([new/0, + set_value/2, + set_value/3, + get_value/1, + get_value/2, + get_value/3, + remove/1, + clear/0, + + attach/1, + detach/1, + get_current/0, + + http_extractor/1, + http_extractor/2, + http_injector/1, + http_injector/2, + http_extractor_fun/2, + http_extractor_fun/3, + http_injector_fun/2, + http_injector_fun/3]). + +-type ctx() :: map(). +-type key() :: term(). +-type value() :: term(). +-type token() :: reference(). + +-export_type([ctx/0, + key/0, + value/0]). + +-define(CURRENT_CTX, '$__current_otel_ctx'). + +-spec new() -> ctx(). +new() -> + #{}. + +-spec set_value(term(), term()) -> ok. +set_value(Key, Value) -> + erlang:put(?CURRENT_CTX, set_value(erlang:get(?CURRENT_CTX), Key, Value)). + +-spec set_value(ctx(), term(), term()) -> map(). +set_value(Ctx, Key, Value) when is_map(Ctx) -> + Ctx#{Key => Value}; +set_value(_, Key, Value) -> + #{Key => Value}. + +-spec get_value(term()) -> term(). +get_value(Key) -> + get_value(erlang:get(?CURRENT_CTX), Key, undefined). + +-spec get_value(term(), term()) -> term(). +get_value(Key, Default) -> + get_value(erlang:get(?CURRENT_CTX), Key, Default). + +-spec get_value(ctx(), term(), term()) -> term(). +get_value(undefined, _Key, Default) -> + Default; +get_value(Ctx, Key, Default) when is_map(Ctx) -> + maps:get(Key, Ctx, Default); +get_value(_, _, Default) -> + Default. + +-spec clear() -> ok. +clear() -> + erlang:erase(?CURRENT_CTX). + +-spec remove(term()) -> ok. +remove(Key) -> + case erlang:get(?CURRENT_CTX) of + Map when is_map(Map) -> + erlang:put(?CURRENT_CTX, maps:remove(Key, Map)), + ok; + _ -> + ok + end. + +-spec get_current() -> map(). +get_current() -> + case erlang:get(?CURRENT_CTX) of + Map when is_map(Map) -> + Map; + _ -> + #{} + end. + +-spec attach(map()) -> token(). +attach(Ctx) -> + erlang:put(?CURRENT_CTX, Ctx). + +-spec detach(token()) -> ok. +detach(Token) -> + erlang:put(?CURRENT_CTX, Token). + + +%% Extractor and Injector setup functions + +http_extractor(FromText) -> + {fun ?MODULE:http_extractor_fun/2, FromText}. + +http_extractor_fun(Headers, FromText) -> + New = FromText(Headers, ?MODULE:get_current()), + ?MODULE:attach(New). + +http_extractor(Key, FromText) -> + {fun ?MODULE:http_extractor_fun/3, {Key, FromText}}. + +http_extractor_fun(Headers, Key, FromText) -> + New = FromText(Headers, ?MODULE:get_value(Key, #{})), + ?MODULE:set_value(Key, New). + +http_injector(ToText) -> + {fun ?MODULE:http_injector_fun/2, ToText}. + +http_injector_fun(Headers, ToText) -> + Headers ++ ToText(Headers, ?MODULE:get_current()). + +http_injector(Key, ToText) -> + {fun ?MODULE:http_injector_fun/3, {Key, ToText}}. + +http_injector_fun(Headers, Key, ToText) -> + Headers ++ ToText(Headers, ?MODULE:get_value(Key, #{})). diff --git a/apps/opentelemetry_api/src/ot_http_status.erl b/apps/opentelemetry_api/src/ot_http_status.erl new file mode 100644 index 00000000..8bccaffd --- /dev/null +++ b/apps/opentelemetry_api/src/ot_http_status.erl @@ -0,0 +1,106 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% HTTP Status helper module. Provides functions to working with HTTP status +%% codes to status records. +%% @end +%%%------------------------------------------------------------------------- +-module(ot_http_status). + +-export([to_status/1, + to_status/2]). + +-include("opentelemetry.hrl"). + +-type http_status_code() :: 1..599. + +-callback to_status(http_status_code()) -> opentelemetry:status(). +-callback to_status(http_status_code(), unicode:unicode_binary()) -> opentelemetry:status(). + +-spec to_status(HttpStatusCode) -> opentelemetry:status() when + HttpStatusCode :: http_status_code(). +to_status(HttpStatusCode) -> + to_status(HttpStatusCode, <<"">>). + +-spec to_status(HttpStatusCode, Message) -> opentelemetry:status() when + HttpStatusCode :: http_status_code(), + Message :: unicode:unicode_binary(). +to_status(HttpStatusCode, Message) -> + Status = + case HttpStatusCode of + Code when Code >= 100 andalso Code < 300 -> + opentelemetry:status(?OTEL_STATUS_OK, Message); + Code when Code >= 300 andalso Code < 400 -> + opentelemetry:status(?OTEL_STATUS_OK, Message); + 401 -> + opentelemetry:status(?OTEL_STATUS_UNAUTHENTICATED, Message); + 403 -> + opentelemetry:status(?OTEL_STATUS_PERMISSION_DENIED, Message); + 404 -> + opentelemetry:status(?OTEL_STATUS_NOT_FOUND, Message); + 412 -> + opentelemetry:status(?OTEL_STATUS_FAILED_PRECONDITION, Message); + 416 -> + opentelemetry:status(?OTEL_STATUS_OUT_OF_RANGE, Message); + 429 -> + opentelemetry:status(?OTEL_STATUS_RESOURCE_EXHAUSTED, Message); + Code when Code >= 400 andalso Code < 500 -> + opentelemetry:status(?OTEL_STATUS_INVALID_ARGUMENT, Message); + 501 -> + opentelemetry:status(?OTEL_STATUS_UNIMPLEMENTED, Message); + 503 -> + opentelemetry:status(?OTEL_STATUS_UNAVAILABLE, Message); + 504 -> + opentelemetry:status(?OTEL_STATUS_DEADLINE_EXCEEDED, Message); + Code when Code >= 500 -> + opentelemetry:status(?OTEL_STATUS_INTERNAL, Message); + _ -> + opentelemetry:status(?OTEL_STATUS_UNKNOWN, Message) + end, + status_message(Status). + +status_message(#status{code = Code, message = <<"">>} = Status) -> + Message = + case Code of + ?OTEL_STATUS_OK -> + <<"Ok">>; + ?OTEL_STATUS_UNKNOWN -> + <<"Unknown Status">>; + ?OTEL_STATUS_INVALID_ARGUMENT -> + <<"Bad Argument">>; + ?OTEL_STATUS_DEADLINE_EXCEEDED -> + <<"Gateway Timeout">>; + ?OTEL_STATUS_NOT_FOUND -> + <<"Not Found">>; + ?OTEL_STATUS_PERMISSION_DENIED -> + <<"Forbidden">>; + ?OTEL_STATUS_RESOURCE_EXHAUSTED -> + <<"Too Many Requests">>; + ?OTEL_STATUS_FAILED_PRECONDITION -> + <<"Failed Precondition">>; + ?OTEL_STATUS_OUT_OF_RANGE -> + <<"Range Not Satisfiable">>; + ?OTEL_STATUS_UNIMPLEMENTED -> + <<"Not Implemented">>; + ?OTEL_STATUS_INTERNAL -> + <<"Internal Error">>; + ?OTEL_STATUS_UNAVAILABLE -> + <<"Service Unavailable">>; + ?OTEL_STATUS_UNAUTHENTICATED -> + <<"Unauthorized">> + end, + Status#status{message = Message}; +status_message(Status) -> + Status. diff --git a/apps/opentelemetry_api/src/ot_instrument.erl b/apps/opentelemetry_api/src/ot_instrument.erl new file mode 100644 index 00000000..6958f9b6 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_instrument.erl @@ -0,0 +1,50 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc All measurements are associated with an instrument. To record +%% measurements for an instrument it must first be created with `new' and +%% then can be referenced by name. +%% @end +%%%------------------------------------------------------------------------- +-module(ot_instrument). + +%% @doc Calls the SDK to create a new instrument which can then be referenced +%% by name. +%% @end +-callback new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +-callback new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). + +%% @doc Returns an instrument definition which can be used to create a new instrument +%% by passing to `ot_meter:new_instruments/1' +%% @end +-callback definition(ot_meter:name()) -> ot_meter:instrument_definition(). +-callback definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). + +%% @doc Used by additive instruments to record measurements. +-callback add(ot_meter:bound_instrument(), number()) -> ok. +-callback add(opentelemetry:meter(), ot_meter:name(), number(), ot_meter:labels()) -> ok. + +%% @doc Used by non-additive instruments to record measurements. +-callback record(ot_meter:bound_instrument(), number()) -> ok. +-callback record(opentelemetry:meter(), ot_meter:name(), number(), ot_meter:labels()) -> ok. + +%% @doc Returns a measurement tuple that can be based to a batch recording through `ot_meter:batch_record/3' +-callback measurement(ot_meter:bound_instrument() | ot_meter:name(), number()) -> + {ot_meter:bound_instrument() | ot_meter:name(), number()}. + +-optional_callbacks([add/2, + add/4, + record/2, + record/4, + measurement/2]). diff --git a/apps/opentelemetry_api/src/ot_meter.erl b/apps/opentelemetry_api/src/ot_meter.erl new file mode 100644 index 00000000..becccad0 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_meter.erl @@ -0,0 +1,136 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_meter). + +-include("meter.hrl"). + +-callback new_instrument(opentelemetry:meter(), name(), instrument_kind(), instrument_opts()) -> boolean(). +-callback new_instruments(opentelemetry:meter(), [instrument_opts()]) -> boolean(). + +-callback record(opentelemetry:meter(), term (), number()) -> ok. +-callback record(opentelemetry:meter(), name(), labels(), number()) -> ok. + +-callback record_batch(opentelemetry:meter(), [{instrument(), number()}], labels()) -> ok. + +-callback bind(opentelemetry:meter(), instrument(), labels()) -> term(). +-callback release(opentelemetry:meter(), term()) -> ok. + +-callback register_observer(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) -> ok | unknown_instrument. +-callback set_observer_callback(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) -> ok | unknown_instrument. + +-callback observe(ot_observer:observer_result(), number(), labels()) -> ok. + +-export([new_instrument/4, + new_instruments/2, + instrument_definition/4, + bind/3, + release/1, + record/2, + record/4, + record_batch/3, + register_observer/3, + set_observer_callback/3, + observe/3]). + +-type label_key() :: unicode:unicode_binary(). +-type label_value() :: unicode:unicode_binary(). +-type label() :: {label_key(), label_value()}. +-type labels() :: [label()]. + +-type name() :: unicode:unicode_binary(). +-type description() :: unicode:unicode_binary(). +-type instrument_kind() :: module(). +-type unit() :: atom(). +-type number_kind() :: integer | float. + +-type instrument_config() :: #{description => description(), + number_kind => number_kind(), + unit => unit(), + monotonic := boolean(), + synchronous := boolean()}. + +-type instrument_properties() :: #{monotonic := boolean(), + synchronous := boolean()}. + +-type instrument_opts() :: #{description => description(), + number_kind => number_kind(), + unit => unit()}. + +-type instrument_definition() :: {name(), instrument_kind(), instrument_opts()}. +-type instrument() :: term(). +-type bound_instrument() :: {opentelemetry:meter(), term()}. + +-type measurement() :: {bound_instrument() | name(), number()}. + +-export_type([name/0, + description/0, + instrument_kind/0, + instrument_config/0, + instrument_opts/0, + number_kind/0, + unit/0, + measurement/0, + labels/0]). + +-spec new_instrument(opentelemetry:meter(), name(), instrument_kind(), instrument_opts()) -> boolean(). +new_instrument(Meter={Module, _}, Name, InstrumentKind, InstrumentOpts) -> + Module:new_instrument(Meter, Name, InstrumentKind, InstrumentOpts). + +-spec new_instruments(opentelemetry:meter(), [instrument_definition()]) -> boolean(). +new_instruments(Meter={Module, _}, List) -> + Module:new_instruments(Meter, List). + +-spec instrument_definition(module(), name(), instrument_properties(), instrument_opts()) -> instrument_definition(). +instrument_definition(InstrumentModule, Name, Properties, Opts) -> + %% instrument config values are not allowed to be overridden so in case the user + %% attempts to pass as an optiion this merge will use the config value + {Name, InstrumentModule, maps:merge(Opts, Properties)}. + +-spec bind(opentelemetry:meter(), name(), labels()) -> bound_instrument(). +bind(Meter={Module, _}, Name, Labels) -> + {Meter, Module:bind(Meter, Name, Labels)}. + +-spec release(bound_instrument()) -> ok. +release({Meter={Module, _}, BoundInstrument}) -> + Module:release(Meter, BoundInstrument). + +-spec record(opentelemetry:meter(), name(), number(), labels()) -> ok. +record(Meter={Module, _}, Name, Number, Labels) -> + Module:record(Meter, Name, Labels, Number). + +-spec record(bound_instrument(), number()) -> ok. +record({Meter={Module, _}, BoundInstrument}, Number) -> + Module:record(Meter, BoundInstrument, Number). + +-spec record_batch(opentelemetry:meter(), labels(), [measurement()]) -> ok. +record_batch(Meter={Module, _}, Labels, Measurements) -> + Module:record_batch(Meter, Labels, Measurements). + +-spec register_observer(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) + -> ok | unknown_instrument. +register_observer(Meter={Module, _}, Name, Callback) -> + Module:register_observer(Meter, Name, Callback). + +-spec set_observer_callback(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) + -> ok | unknown_instrument. +set_observer_callback(Meter={Module, _}, Name, Callback) -> + Module:set_observer_callback(Meter, Name, Callback). + +-spec observe(ot_observer:observer_result(), number(), labels()) -> ok. +observe({Module, Instrument}, Number, Labels) -> + Module:observe(Instrument, Number, Labels). diff --git a/apps/opentelemetry_api/src/ot_meter_noop.erl b/apps/opentelemetry_api/src/ot_meter_noop.erl new file mode 100644 index 00000000..3846f70a --- /dev/null +++ b/apps/opentelemetry_api/src/ot_meter_noop.erl @@ -0,0 +1,65 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_meter_noop). + +-behaviour(ot_meter). + +-export([new_instrument/4, + new_instruments/2, + labels/2, + record/3, + record/4, + record_batch/3, + bind/3, + release/2, + set_observer_callback/3, + register_observer/3, + observe/3]). + +new_instrument(_, _, _, _) -> + true. + +new_instruments(_, _) -> + true. + +labels(_, _) -> + #{}. + +record(_, _, _) -> + ok. + +record(_, _, _, _) -> + ok. + +record_batch(_, _, _) -> + ok. + +bind(_, _, _) -> + []. + +release(_, _) -> + ok. + +set_observer_callback(_, _, _) -> + ok. + +register_observer(_, _, _) -> + ok. + +observe(_, _, _) -> + ok. diff --git a/apps/opentelemetry_api/src/ot_meter_provider.erl b/apps/opentelemetry_api/src/ot_meter_provider.erl new file mode 100644 index 00000000..29f3135f --- /dev/null +++ b/apps/opentelemetry_api/src/ot_meter_provider.erl @@ -0,0 +1,84 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_meter_provider). + +-behaviour(gen_server). + +-export([start_link/2, + register_application_meter/1, + register_meter/2]). + +-export([init/1, + handle_call/3, + handle_cast/2]). + +-type cb_state() :: term(). + +-callback init(term()) -> {ok, cb_state()}. +-callback register_meter(atom(), string(), cb_state()) -> boolean(). + +-record(state, {callback :: module(), + cb_state :: term()}). + +start_link(ProviderModule, Opts) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [ProviderModule, Opts], []). + +-spec register_meter(atom(), string()) -> boolean(). +register_meter(Name, Vsn) -> + try + gen_server:call(?MODULE, {register_meter, Name, Vsn}) + catch exit:{noproc, _} -> + %% ignore register_meter because no SDK has been included and started + false + end. + +-spec register_application_meter(atom()) -> boolean(). +register_application_meter(Name) -> + try + {ok, Vsn} = application:get_key(Name, vsn), + {ok, Modules} = application:get_key(Name, modules), + gen_server:call(?MODULE, {register_meter, Name, Vsn, Modules}) + catch exit:{noproc, _} -> + %% ignore register_meter because no SDK has been included and started + false + end. + +init([ProviderModule, Opts]) -> + case ProviderModule:init(Opts) of + {ok, CbState} -> + {ok, #state{callback=ProviderModule, + cb_state=CbState}}; + Other -> + Other + end. + +handle_call({register_meter, Name, Vsn, Modules}, _From, State=#state{callback=Cb, + cb_state=CbState}) -> + _ = Cb:register_meter(Name, Vsn, Modules, CbState), + {reply, true, State}; +handle_call({register_meter, Name, Vsn}, _From, State=#state{callback=Cb, + cb_state=CbState}) -> + _ = Cb:register_meter(Name, Vsn, CbState), + {reply, true, State}; +handle_call(_Msg, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% diff --git a/apps/opentelemetry_api/src/ot_observer.erl b/apps/opentelemetry_api/src/ot_observer.erl new file mode 100644 index 00000000..1f3def6d --- /dev/null +++ b/apps/opentelemetry_api/src/ot_observer.erl @@ -0,0 +1,32 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc An `Observer' is a callback based instrument with a `LastValue' aggregator. +%% On each collection cycle each Observer callback is run and passed an +%% `ObserverInstrument' variable to use when `observe'ing the new value, passing +%% the `ObserverInstrument', the new value and a list of attributes, key/value pairs. +%% @end +%%%------------------------------------------------------------------------- +-module(ot_observer). + +%% value containing all information needed by the SDK to record an update +-type instrument() :: term(). + +%% function called with an `observer_instrument' argument to update observer +-type callback() :: fun((instrument()) -> ok). + +-export_type([callback/0]). + +-callback set_callback(opentelemetry:meter(), ot_meter:name(), callback()) -> ok. +-callback observe(instrument(), number(), ot_meter:labels()) -> ok. diff --git a/apps/opentelemetry_api/src/ot_propagation.erl b/apps/opentelemetry_api/src/ot_propagation.erl new file mode 100644 index 00000000..56eadf78 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_propagation.erl @@ -0,0 +1,65 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_propagation). + +-export([http_inject/1, + http_extract/1]). + +-type extractor(T) :: {fun((T, {fun(), term()}) -> ok), term()} | + {fun((T, ot_ctx:key(), {fun(), term()}) -> ok), term()}. +-type injector(T) :: {fun((T, {fun(), term()}) -> T), term()} | + {fun((T, ot_ctx:key(), {fun(), term()}) -> T), term()}. + +-type http_headers() :: [{binary(), binary()}]. + +-type http_injector() :: injector(http_headers()). +-type http_extractor() :: extractor(http_headers()). + +-export_type([extractor/1, + injector/1, + http_injector/0, + http_extractor/0, + http_headers/0]). + +http_inject(Headers) -> + Injectors = opentelemetry:get_http_injector(), + run_injectors(Headers, Injectors). + +http_extract(Headers) -> + Extractors = opentelemetry:get_http_extractor(), + run_extractors(Headers, Extractors). + +run_extractors(Headers, Extractors) -> + lists:foldl(fun({Extract, {Key, FromText}}, ok) -> + Extract(Headers, Key, FromText), + ok; + ({Extract, FromText}, ok) -> + Extract(Headers, FromText), + ok; + (_, ok) -> + ok + end, ok, Extractors). + +run_injectors(Headers, Injectors) -> + lists:foldl(fun({Inject, {Key, ToText}}, HeadersAcc) -> + Inject(HeadersAcc, Key, ToText); + ({Inject, ToText}, HeadersAcc) -> + Inject(HeadersAcc, ToText); + (_, HeadersAcc) -> + HeadersAcc + end, Headers, Injectors). diff --git a/apps/opentelemetry_api/src/ot_span.erl b/apps/opentelemetry_api/src/ot_span.erl new file mode 100644 index 00000000..4f2e2c0f --- /dev/null +++ b/apps/opentelemetry_api/src/ot_span.erl @@ -0,0 +1,150 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% Span behaviour. +%% @end +%%%------------------------------------------------------------------------- +-module(ot_span). + +-export([get_ctx/2, + trace_id/1, + span_id/1, + tracestate/1, + is_recording/2, + set_attribute/4, + set_attributes/3, + add_event/4, + add_events/3, + set_status/3, + update_name/3]). + +-include("opentelemetry.hrl"). + +-define(is_recording(SpanCtx), SpanCtx =/= undefined andalso SpanCtx#span_ctx.is_recording =:= true). + +-type start_opts() :: #{attributes => opentelemetry:attributes(), + sampler => term(), + links => opentelemetry:links(), + is_recording => boolean(), + start_time => opentelemetry:timestamp(), + kind => opentelemetry:span_kind()}. + +-export_type([start_opts/0]). + +-callback get_ctx(opentelemetry:span()) -> opentelemetry:span_ctx(). +-callback set_attribute(opentelemetry:span_ctx(), + opentelemetry:attribute_key(), + opentelemetry:attribute_value()) -> boolean(). +-callback set_attributes(opentelemetry:span_ctx(), opentelemetry:attributes()) -> boolean(). +-callback add_event(opentelemetry:span_ctx(), unicode:unicode_binary(), opentelemetry:attributes()) -> boolean(). +-callback add_events(opentelemetry:span_ctx(), opentelemetry:events()) -> boolean(). +-callback set_status(opentelemetry:span_ctx(), opentelemetry:status()) -> boolean(). +-callback update_name(opentelemetry:span_ctx(), opentelemetry:span_name()) -> boolean(). + +%% handy macros so we don't have function name typos +-define(DO(Tracer, SpanCtx, Args), do_span_function(?FUNCTION_NAME, Tracer, SpanCtx, Args)). + +-spec get_ctx(Tracer, Span) -> SpanCtx when + Tracer :: opentelemetry:tracer(), + Span :: opentelemetry:span(), + SpanCtx :: opentelemetry:span_ctx(). +get_ctx(Tracer, Span) -> + SpanModule = ot_tracer:span_module(Tracer), + SpanModule:get_ctx(Span). + +-spec is_recording(Tracer, SpanCtx) -> boolean() when + Tracer :: opentelemetry:tracer(), + SpanCtx :: opentelemetry:span_ctx(). +is_recording(ot_span_noop, _) -> + false; +is_recording(_Tracer, SpanCtx) -> + ?is_recording(SpanCtx). + +-spec set_attribute(Tracer, SpanCtx, Key, Value) -> boolean() when + Tracer :: opentelemetry:tracer(), + Key :: opentelemetry:attribute_key(), + Value :: opentelemetry:attribute_value(), + SpanCtx :: opentelemetry:span_ctx(). +set_attribute(Tracer, SpanCtx, Key, Value) when ?is_recording(SpanCtx) -> + ?DO(Tracer, SpanCtx, [Key, Value]); +set_attribute(_, _, _, _) -> + false. + +-spec set_attributes(Tracer, SpanCtx, Attributes) -> boolean() when + Tracer :: opentelemetry:tracer(), + Attributes :: opentelemetry:attributes(), + SpanCtx :: opentelemetry:span_ctx(). +set_attributes(Tracer, SpanCtx, Attributes) when ?is_recording(SpanCtx) , is_list(Attributes) -> + ?DO(Tracer, SpanCtx, [Attributes]); +set_attributes(_, _, _) -> + false. + +-spec add_event(Tracer, SpanCtx, Name, Attributes) -> boolean() when + Tracer :: opentelemetry:tracer(), + Name :: unicode:unicode_binary(), + Attributes :: opentelemetry:attributes(), + SpanCtx :: opentelemetry:span_ctx(). +add_event(Tracer, SpanCtx, Name, Attributes) when ?is_recording(SpanCtx) -> + ?DO(Tracer, SpanCtx, [Name, Attributes]); +add_event(_, _, _, _) -> + false. + +-spec add_events(Tracer, SpanCtx, Events) -> boolean() when + Tracer :: opentelemetry:tracer(), + Events :: opentelemetry:events(), + SpanCtx :: opentelemetry:span_ctx(). +add_events(Tracer, SpanCtx, Events) when ?is_recording(SpanCtx) , is_list(Events) -> + ?DO(Tracer, SpanCtx, [Events]); +add_events(_, _, _) -> + false. + +-spec set_status(Tracer, SpanCtx, Status) -> boolean() when + Tracer :: opentelemetry:tracer(), + Status :: opentelemetry:status(), + SpanCtx :: opentelemetry:span_ctx(). +set_status(Tracer, SpanCtx, Status) when ?is_recording(SpanCtx) -> + ?DO(Tracer, SpanCtx, [Status]); +set_status(_, _, _) -> + false. + +-spec update_name(Tracer, SpanCtx, Name) -> boolean() when + Tracer :: opentelemetry:tracer(), + Name :: opentelemetry:span_name(), + SpanCtx :: opentelemetry:span_ctx(). +update_name(Tracer, SpanCtx, SpanName) when ?is_recording(SpanCtx) -> + ?DO(Tracer, SpanCtx, [SpanName]); +update_name(_, _, _) -> + false. + +%% accessors +-spec trace_id(opentelemetry:span_ctx()) -> opentelemetry:trace_id(). +trace_id(#span_ctx{ trace_id = TraceId }) -> TraceId. + +-spec span_id(opentelemetry:span_ctx()) -> opentelemetry:span_id(). +span_id(#span_ctx{ span_id = SpanId }) -> SpanId. + +-spec tracestate(opentelemetry:span_ctx()) -> opentelemetry:tracestate(). +tracestate(#span_ctx{ tracestate = Tracestate }) -> Tracestate. + +%% internal functions + +do_span_function(Function, Tracer, SpanCtx, Args) -> + SpanModule = ot_tracer:span_module(Tracer), + apply_span_function(SpanModule, Function, [SpanCtx | Args]). + +apply_span_function(ot_span_noop, _Function, _Args) -> + ok; +apply_span_function(SpanModule, Function, Args) -> + erlang:apply(SpanModule, Function, Args). diff --git a/apps/opentelemetry_api/src/ot_sum_observer.erl b/apps/opentelemetry_api/src/ot_sum_observer.erl new file mode 100644 index 00000000..7d6b0bca --- /dev/null +++ b/apps/opentelemetry_api/src/ot_sum_observer.erl @@ -0,0 +1,57 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_sum_observer). + +-behaviour(ot_instrument). +-behaviour(ot_observer). + +-export([new/2, + new/3, + definition/1, + definition/2, + set_callback/3, + observe/3]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => true, + synchronous => false}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec set_callback(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) -> ok. +set_callback(Meter, Observer, Callback) -> + ot_meter:set_observer_callback(Meter, Observer, Callback). + +-spec observe(ot_observer:instrument(), number(), ot_meter:labels()) -> ok. +observe(ObserverInstrument, Number, LabelSet) -> + ot_meter:observe(ObserverInstrument, Number, LabelSet). diff --git a/apps/opentelemetry_api/src/ot_tracer.erl b/apps/opentelemetry_api/src/ot_tracer.erl new file mode 100644 index 00000000..3fa22dc2 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_tracer.erl @@ -0,0 +1,116 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_tracer). + +-export([start_span/3, + start_span/4, + start_inactive_span/3, + start_inactive_span/4, + set_span/2, + with_span/3, + with_span/4, + current_ctx/1, + current_span_ctx/1, + end_span/1, + end_span/2]). + +%% tracer access functions +-export([span_module/1]). + +-include("opentelemetry.hrl"). + +-type traced_fun(T) :: fun((opentelemetry:span_ctx()) -> T). +-type tracer_ctx() :: term(). + +-export_type([traced_fun/1]). + +-callback start_span(opentelemetry:tracer(), + opentelemetry:span_name(), + ot_span:start_opts()) -> opentelemetry:span_ctx(). +-callback start_span(ot_ctx:ctx(), + opentelemetry:tracer(), + opentelemetry:span_name(), + ot_span:start_opts()) -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +-callback start_inactive_span(opentelemetry:tracer(), + opentelemetry:span_name(), + ot_span:start_opts()) -> opentelemetry:span_ctx(). +-callback start_inactive_span(ot_ctx:ctx(), + opentelemetry:tracer(), + opentelemetry:span_name(), + ot_span:start_opts()) -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +-callback set_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> ok. +-callback with_span(opentelemetry:tracer(), opentelemetry:span_name(), traced_fun(T)) -> T. +-callback with_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts(), traced_fun(T)) -> T. +-callback end_span(ot_ctx:ctx(), opentelemetry:tracer()) -> boolean() | {error, term()}. +-callback end_span(opentelemetry:tracer()) -> boolean() | {error, term()}. +-callback current_ctx(opentelemetry:tracer()) -> tracer_ctx(). +-callback current_span_ctx(opentelemetry:tracer()) -> opentelemetry:span_ctx(). +-callback span_module(opentelemetry:tracer()) -> module(). + +-spec start_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> opentelemetry:span_ctx(). +start_span(Tracer={Module, _}, Name, Opts) -> + Module:start_span(Tracer, Name, Opts). + +-spec start_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_span(Ctx, Tracer={Module, _}, Name, Opts) -> + Module:start_span(Ctx, Tracer, Name, Opts). + +-spec start_inactive_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> opentelemetry:span_ctx(). +start_inactive_span(Tracer={Module, _}, Name, Opts) -> + Module:start_inactive_span(Tracer, Name, Opts). + +-spec start_inactive_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), + ot_span:start_opts()) -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_inactive_span(Ctx, Tracer={Module, _}, Name, Opts) -> + Module:start_inactive_span(Ctx, Tracer, Name, Opts). + +-spec set_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> ok. +set_span(Tracer={Module, _}, SpanCtx) when is_atom(Module) -> + Module:set_span(Tracer, SpanCtx). + +-spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), traced_fun(T)) -> T. +with_span(Tracer={Module, _}, SpanName, Fun) when is_atom(Module) -> + Module:with_span(Tracer, SpanName, Fun). + +-spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts(), traced_fun(T)) -> T. +with_span(Tracer={Module, _}, SpanName, Opts, Fun) when is_atom(Module) -> + Module:with_span(Tracer, SpanName, Opts, Fun). + +-spec end_span(ot_ctx:ctx(), opentelemetry:tracer()) -> boolean() | {error, term()}. +end_span(Ctx, Tracer={Module, _}) -> + Module:end_span(Ctx, Tracer). + +-spec end_span(opentelemetry:tracer()) -> boolean() | {error, term()}. +end_span(Tracer={Module, _}) -> + Module:end_span(Tracer). + +-spec current_ctx(opentelemetry:tracer()) -> ot_tracer:tracer_ctx(). +current_ctx(Tracer={Module, _}) -> + Module:current_ctx(Tracer). + +-spec current_span_ctx(opentelemetry:tracer()) -> opentelemetry:span_ctx(). +current_span_ctx(Tracer={Module, _}) -> + Module:current_span_ctx(Tracer). + +%% tracer access functions + +span_module(Tracer={Module, _}) -> + Module:span_module(Tracer). diff --git a/apps/opentelemetry_api/src/ot_tracer_noop.erl b/apps/opentelemetry_api/src/ot_tracer_noop.erl new file mode 100644 index 00000000..48e25970 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_tracer_noop.erl @@ -0,0 +1,94 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_tracer_noop). + +-behaviour(ot_tracer). + +-export([start_span/3, + start_span/4, + start_inactive_span/3, + start_inactive_span/4, + set_span/2, + with_span/3, + with_span/4, + end_span/1, + end_span/2, + span_module/1, + current_ctx/1, + current_span_ctx/1]). + +-include("opentelemetry.hrl"). + +-define(NOOP_SPAN_CTX, #span_ctx{trace_id=0, + span_id=0, + trace_flags=0, + tracestate=[], + is_valid=false, + is_recording=false}). +-define(NOOP_TRACER_CTX, []). + +-spec start_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) -> opentelemetry:span_ctx(). +start_span(_, _Name, _) -> + ?NOOP_SPAN_CTX. + +-spec start_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_span(Ctx, _, _Name, _) -> + {?NOOP_SPAN_CTX, Ctx}. + +-spec start_inactive_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_span:start_opts()) + -> opentelemetry:span_ctx(). +start_inactive_span(_, _Name, _Opts) -> + ?NOOP_SPAN_CTX. + +-spec start_inactive_span(ot_ctx:ctx(), opentelemetry:tracer(), opentelemetry:span_name(), + ot_span:start_opts()) -> {opentelemetry:span_ctx(), ot_ctx:ctx()}. +start_inactive_span(Ctx, _, _Name, _Opts) -> + {?NOOP_SPAN_CTX, Ctx}. + +-spec set_span(opentelemetry:tracer(), opentelemetry:span_ctx()) -> ok. +set_span(_, _SpanCtx) -> + ok. + +-spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), ot_tracer:traced_fun(T)) -> T. +with_span(_, _SpanName, Fun) -> + Fun(?NOOP_SPAN_CTX). + +-spec with_span(opentelemetry:tracer(), opentelemetry:span_name(), + ot_span:start_opts(), ot_tracer:traced_fun(T)) -> T. +with_span(_, _SpanName, _Opts, Fun) -> + Fun(?NOOP_SPAN_CTX). + +-spec current_ctx(opentelemetry:tracer()) -> ot_tracer:tracer_ctx(). +current_ctx(_Tracer) -> + ?NOOP_TRACER_CTX. + +-spec current_span_ctx(opentelemetry:tracer()) -> opentelemetry:span_ctx(). +current_span_ctx(_) -> + ?NOOP_SPAN_CTX. + +span_module(_) -> + ot_span_noop. + +-spec end_span(ot_ctx:ctx(), opentelemetry:tracer()) -> boolean() | {error, term()}. +end_span(_, _) -> + true. + +-spec end_span(opentelemetry:tracer()) -> boolean() | {error, term()}. +end_span(_) -> + true. diff --git a/apps/opentelemetry_api/src/ot_tracer_provider.erl b/apps/opentelemetry_api/src/ot_tracer_provider.erl new file mode 100644 index 00000000..f1fe18a5 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_tracer_provider.erl @@ -0,0 +1,107 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_tracer_provider). + +-behaviour(gen_server). + +-export([start_link/2, + resource/0, + register_application_tracer/1, + register_tracer/2]). + +-export([init/1, + handle_call/3, + handle_cast/2, + code_change/3]). + +-type cb_state() :: term(). + +-callback init(term()) -> {ok, cb_state()}. +-callback register_tracer(atom(), string(), cb_state()) -> boolean(). +-callback resource(cb_state()) -> term() | undefined. + +-record(state, {callback :: module(), + cb_state :: term()}). + +start_link(ProviderModule, Opts) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [ProviderModule, Opts], []). + +-spec resource() -> term() | undefined. +resource() -> + try + gen_server:call(?MODULE, resource) + catch exit:{noproc, _} -> + %% ignore because no SDK has been included and started + undefined + end. + + +-spec register_tracer(atom(), string()) -> boolean(). +register_tracer(Name, Vsn) -> + try + gen_server:call(?MODULE, {register_tracer, Name, Vsn}) + catch exit:{noproc, _} -> + %% ignore register_tracer because no SDK has been included and started + false + end. + +-spec register_application_tracer(atom()) -> boolean(). +register_application_tracer(Name) -> + try + {ok, Vsn} = application:get_key(Name, vsn), + {ok, Modules} = application:get_key(Name, modules), + gen_server:call(?MODULE, {register_tracer, Name, Vsn, Modules}) + catch exit:{noproc, _} -> + %% ignore register_tracer because no SDK has been included and started + false + end. + +init([ProviderModule, Opts]) -> + case ProviderModule:init(Opts) of + {ok, CbState} -> + {ok, #state{callback=ProviderModule, + cb_state=CbState}}; + Other -> + Other + end. + +handle_call({register_tracer, Name, Vsn, Modules}, _From, State=#state{callback=Cb, + cb_state=CbState}) -> + _ = Cb:register_tracer(Name, Vsn, Modules, CbState), + {reply, true, State}; +handle_call({register_tracer, Name, Vsn}, _From, State=#state{callback=Cb, + cb_state=CbState}) -> + _ = Cb:register_tracer(Name, Vsn, CbState), + {reply, true, State}; +handle_call(resource, _From, State=#state{callback=Cb, + cb_state=CbState}) -> + Resource = Cb:resource(CbState), + {reply, Resource, State}; +handle_call(_Msg, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +%% TODO: Use `Extra' as options to update the state like the sampler? +code_change(_OldVsn, State=#state{callback=Cb, + cb_state=CbState}, _Extra) -> + NewCbState = Cb:code_change(CbState), + {ok, State#state{cb_state=NewCbState}}. + +%% diff --git a/apps/opentelemetry_api/src/ot_updown_counter.erl b/apps/opentelemetry_api/src/ot_updown_counter.erl new file mode 100644 index 00000000..1405aa17 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_updown_counter.erl @@ -0,0 +1,62 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_updown_counter). + +-behaviour(ot_instrument). + +-export([new/2, + new/3, + definition/1, + definition/2, + add/2, + add/4, + measurement/2]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => false, + synchronous => true}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec add(ot_meter:bound_instrument(), number()) -> ok. +add(BoundInstrument, Number) -> + ot_meter:record(BoundInstrument, Number). + +-spec add(opentelemetry:meter(), ot_meter:name(), number(), ot_meter:label_set()) -> ok. +add(Meter, Name, Number, LabelSet) -> + ot_meter:record(Meter, Name, Number, LabelSet). + +-spec measurement(ot_meter:bound_instrument() | ot_meter:name(), number()) + -> {ot_meter:bound_instrument() | ot_meter:name(), number()}. +measurement(NameOrInstrument, Number) -> + {NameOrInstrument, Number}. diff --git a/apps/opentelemetry_api/src/ot_updown_sum_observer.erl b/apps/opentelemetry_api/src/ot_updown_sum_observer.erl new file mode 100644 index 00000000..3c8a9696 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_updown_sum_observer.erl @@ -0,0 +1,57 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_updown_sum_observer). + +-behaviour(ot_instrument). +-behaviour(ot_observer). + +-export([new/2, + new/3, + definition/1, + definition/2, + set_callback/3, + observe/3]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => false, + synchronous => false}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec set_callback(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) -> ok. +set_callback(Meter, Observer, Callback) -> + ot_meter:set_observer_callback(Meter, Observer, Callback). + +-spec observe(ot_observer:instrument(), number(), ot_meter:label_set()) -> ok. +observe(ObserverInstrument, Number, LabelSet) -> + ot_meter:observe(ObserverInstrument, Number, LabelSet). diff --git a/apps/opentelemetry_api/src/ot_value_observer.erl b/apps/opentelemetry_api/src/ot_value_observer.erl new file mode 100644 index 00000000..dac98f61 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_value_observer.erl @@ -0,0 +1,57 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2020, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_value_observer). + +-behaviour(ot_instrument). +-behaviour(ot_observer). + +-export([new/2, + new/3, + definition/1, + definition/2, + set_callback/3, + observe/3]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => false, + synchronous => false}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec set_callback(opentelemetry:meter(), ot_meter:name(), ot_observer:callback()) -> ok. +set_callback(Meter, Observer, Callback) -> + ot_meter:set_observer_callback(Meter, Observer, Callback). + +-spec observe(ot_observer:instrument(), number(), ot_meter:labels()) -> ok. +observe(ObserverInstrument, Number, LabelSet) -> + ot_meter:observe(ObserverInstrument, Number, LabelSet). diff --git a/apps/opentelemetry_api/src/ot_value_recorder.erl b/apps/opentelemetry_api/src/ot_value_recorder.erl new file mode 100644 index 00000000..adc9034b --- /dev/null +++ b/apps/opentelemetry_api/src/ot_value_recorder.erl @@ -0,0 +1,62 @@ +%%%------------------------------------------------------------------------ +%% Copyright 2019, OpenTelemetry Authors +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% @doc +%% @end +%%%------------------------------------------------------------------------- +-module(ot_value_recorder). + +-behaviour(ot_instrument). + +-export([new/2, + new/3, + definition/1, + definition/2, + record/2, + record/4, + measurement/2]). + +-include("meter.hrl"). + +-define(PROPERTIES, #{monotonic => false, + synchronous => true}). + +-spec new(opentelemetry:meter(), ot_meter:name()) -> boolean(). +new(Meter, Name) -> + new(Meter, Name, #{}). + +-spec new(opentelemetry:meter(), ot_meter:name(), ot_meter:instrument_opts()) -> boolean(). +new(Meter, Name, Opts) -> + ot_meter:new_instrument(Meter, Name, ?MODULE, Opts). + +-spec definition(ot_meter:name()) -> ot_meter:instrument_definition(). +definition(Name) -> + definition(Name, #{}). + +-spec definition(ot_meter:name(), ot_meter:instrument_opts()) -> ot_meter:instrument_definition(). +definition(Name, Opts) -> + ot_meter:instrument_definition(?MODULE, Name, ?PROPERTIES, Opts). + +-spec record(ot_meter:bound_instrument(), number()) -> ok. +record(BoundInstrument, Number) -> + ot_meter:record(BoundInstrument, Number). + +-spec record(opentelemetry:meter(), ot_meter:name(), number(), ot_meter:label_set()) -> ok. +record(Meter, Name, Number, LabelSet) -> + ot_meter:record(Meter, Name, Number, LabelSet). + +-spec measurement(ot_meter:bound_instrument() | ot_meter:name(), number()) + -> {ot_meter:bound_instrument() | ot_meter:name(), number()}. +measurement(NameOrInstrument, Number) -> + {NameOrInstrument, Number}. diff --git a/apps/opentelemetry_api/test/open_telemetry_test.exs b/apps/opentelemetry_api/test/open_telemetry_test.exs new file mode 100644 index 00000000..2d6f7c9e --- /dev/null +++ b/apps/opentelemetry_api/test/open_telemetry_test.exs @@ -0,0 +1,72 @@ +defmodule OpenTelemetryTest do + use ExUnit.Case, async: true + + require OpenTelemetry.Tracer, as: Tracer + require OpenTelemetry.Span, as: Span + + require Record + @fields Record.extract(:span_ctx, from_lib: "opentelemetry_api/include/opentelemetry.hrl") + Record.defrecordp(:span_ctx, @fields) + + @fields Record.extract(:link, from_lib: "opentelemetry_api/include/opentelemetry.hrl") + Record.defrecordp(:link, @fields) + + test "current_span tracks nesting" do + _ctx1 = Tracer.start_span("span-1") + ctx2 = Tracer.start_span("span-2") + + assert ctx2 == Tracer.current_span_ctx() + end + + test "link creation" do + ctx = span_ctx(trace_id: 1, span_id: 2, tracestate: []) + + link(trace_id: t, span_id: s, attributes: a, tracestate: ts) = OpenTelemetry.link(ctx) + + assert 1 == t + assert 2 == s + assert [] == ts + assert [] == a + + link(trace_id: t, span_id: s, attributes: a, tracestate: ts) = + OpenTelemetry.link(ctx, [{"attr-1", "value-1"}]) + + assert 1 == t + assert 2 == s + assert [] == ts + assert [{"attr-1", "value-1"}] == a + + end + + test "closing a span makes the parent current" do + ctx1 = Tracer.start_span("span-1") + ctx2 = Tracer.start_span("span-2") + + assert ctx2 == Tracer.current_span_ctx() + OpenTelemetry.Tracer.end_span() + assert ctx1 == Tracer.current_span_ctx() + end + + test "macro start_span" do + Tracer.with_span "span-1" do + Tracer.with_span "span-2" do + Span.set_attribute("attr-1", "value-1") + + event1 = OpenTelemetry.event("event-1", []) + event2 = OpenTelemetry.event("event-2", []) + + Span.add_events([event1, event2]) + end + end + end + + test "can deconstruct a span context" do + Tracer.with_span "span-1" do + span = Tracer.current_span_ctx() + + assert nil != Span.trace_id(span) + assert nil != Span.span_id(span) + assert [] = Span.tracestate(span) + end + end +end diff --git a/apps/opentelemetry_api/test/opentelemetry_api_SUITE.erl b/apps/opentelemetry_api/test/opentelemetry_api_SUITE.erl new file mode 100644 index 00000000..a7b47a5c --- /dev/null +++ b/apps/opentelemetry_api/test/opentelemetry_api_SUITE.erl @@ -0,0 +1,140 @@ +-module(opentelemetry_api_SUITE). + +-compile(export_all). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-include("opentelemetry.hrl"). +-include("tracer.hrl"). + +all() -> + [noop_tracer, update_span_data, noop_with_span, macros, can_create_link_from_span]. + +init_per_suite(Config) -> + application:load(opentelemetry_api), + Config. + +end_per_suite(_Config) -> + ok. + +can_create_link_from_span(_Config) -> + %% start a span to create a link to + SpanCtx = ?start_span(<<"span-1">>), + + %% extract individual values from span context + TraceId = ot_span:trace_id(SpanCtx), + SpanId = ot_span:span_id(SpanCtx), + Tracestate = ot_span:tracestate(SpanCtx), + + %% end span, so there's no current span set + ?end_span(), + + Attributes = [{<<"attr-1">>, <<"value-1">>}], + + ?assertMatch(undefined, opentelemetry:link(undefined)), + ?assertMatch(undefined, opentelemetry:link(undefined, Attributes)), + + ?assertMatch(#link{trace_id=TraceId, + span_id=SpanId, + attributes=Attributes, + tracestate=Tracestate}, + opentelemetry:link(TraceId, SpanId, Attributes, Tracestate)), + + ?assertMatch(#link{trace_id=TraceId, + span_id=SpanId, + attributes=[], + tracestate=Tracestate}, + opentelemetry:link(SpanCtx)), + + ?assertMatch(#link{trace_id=TraceId, + span_id=SpanId, + attributes=Attributes, + tracestate=Tracestate}, + opentelemetry:link(SpanCtx, Attributes)), + + ?assertMatch([#link{trace_id=TraceId, + span_id=SpanId, + attributes=Attributes, + tracestate=Tracestate}, + #link{trace_id=TraceId, + span_id=SpanId, + attributes=[], + tracestate=Tracestate}], + opentelemetry:links([undefined, {SpanCtx, Attributes}, SpanCtx])). + + +noop_tracer(_Config) -> + %% start a span and 2 children + SpanCtx1 = ?start_span(<<"span-1">>), + SpanCtx2 = ?start_span(<<"span-2">>), + SpanCtx3 = ?start_span(<<"span-3">>), + + %% end the 3rd span + ?assertMatch(SpanCtx3, ?current_span_ctx()), + ?end_span(), + + %% 2nd span should be the current span ctx now + ?assertMatch(SpanCtx2, ?current_span_ctx()), + + %% start another child of the 2nd span + SpanCtx4 = ?start_span(<<"span-4">>), + ?assertMatch(SpanCtx4, ?current_span_ctx()), + + %% end 4th span and 2nd should be current + ?end_span(), + ?assertMatch(SpanCtx2, ?current_span_ctx()), + + %% end 2th span and 1st should be current + ?end_span(), + ?assertMatch(SpanCtx1, ?current_span_ctx()), + + %% end first and no span should be current ctx + ?end_span(), + + %% always returns a noop span + ?assertMatch(SpanCtx1, ?current_span_ctx()). + +%% just shouldn't crash +update_span_data(_Config) -> + Links = [#link{trace_id=0, + span_id=0, + attributes=[], + tracestate=[]}], + + SpanCtx1 = ?start_span(<<"span-1">>, #{links => Links}), + ?set_attribute(<<"key-1">>, <<"value-1">>), + + Events = opentelemetry:events([{opentelemetry:timestamp(), + <<"timed-event-name">>, []}]), + Status = ot_http_status:to_status(200), + ?assertMatch(#status{code = ?OTEL_STATUS_OK, message = <<"Ok">>}, Status), + + %% with spanctx and tracer passed as an argument + Tracer = opentelemetry:get_tracer(), + ot_span:set_status(Tracer, SpanCtx1, Status), + + ot_span:add_events(Tracer, SpanCtx1, Events), + + ?assertMatch(SpanCtx1, ?current_span_ctx()), + ?end_span(), + + ok. + +noop_with_span(_Config) -> + Tracer = opentelemetry:get_tracer(), + ?assertMatch({ot_tracer_noop, _}, Tracer), + + Result = some_result, + ?assertEqual(Result, ot_tracer:with_span(Tracer, <<"span1">>, fun(_) -> Result end)), + ok. + +macros(_Config) -> + _SpanCtx1 = ?start_span(<<"span-1">>), + SpanCtx2 = ?start_span(<<"span-2">>), + + ?assertMatch(SpanCtx2, ?current_span_ctx()), + ?end_span(), + + %% 2nd span should be the current span ctx now + ?assertMatch(SpanCtx2, ?current_span_ctx()). diff --git a/apps/opentelemetry_api/test/otel_metrics_SUITE.erl b/apps/opentelemetry_api/test/otel_metrics_SUITE.erl new file mode 100644 index 00000000..2a9d7da5 --- /dev/null +++ b/apps/opentelemetry_api/test/otel_metrics_SUITE.erl @@ -0,0 +1,48 @@ +-module(otel_metrics_SUITE). + +-compile(export_all). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-include("opentelemetry.hrl"). +-include("meter.hrl"). + +all() -> + [noop_metrics, macros, non_overridable]. + +init_per_suite(Config) -> + application:load(opentelemetry_api), + Config. + +end_per_suite(_Config) -> + ok. + +noop_metrics(_Config) -> + Meter = opentelemetry:get_meter(), + ?assertMatch({ot_meter_noop, _}, Meter), + + ?assert(ot_counter:new(Meter, <<"noop-measure-1">>, #{description => <<"some description">>})), + + ok. + +macros(_Config) -> + ?ot_new_instruments([#{name => <<"macros-measure-1">>, + description => <<"some description">>, + kind => counter, + label_keys => [], + monotonic => true, + absolute => true, + unit => one}]), + ok. + +%% checks that opts from the user can't override static attributes of an instrument +non_overridable(_Config) -> + {_, _, Instrument} = ot_counter:definition(<<"noop-measure-1">>, #{description => <<"some description">>, + monotonic => false, + synchronous => false}), + + ?assert(maps:get(monotonic, Instrument)), + ?assert(maps:get(synchronous, Instrument)), + + ok. diff --git a/apps/opentelemetry_api/test/test_helper.exs b/apps/opentelemetry_api/test/test_helper.exs new file mode 100644 index 00000000..869559e7 --- /dev/null +++ b/apps/opentelemetry_api/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/rebar.config b/rebar.config index 8a132c7a..aa4cb9c1 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,5 @@ {erl_opts, [debug_info]}. -{deps, - [{opentelemetry_api, {git, "https://github.com/tsloughter/opentelemetry-erlang-api", - {branch, "metrics-api-0.4.0"}}}]}. +{deps, []}. {shell, [{apps, [opentelemetry]}, {config, "config/sys.config"}]}. @@ -20,7 +18,7 @@ [{preprocess, true}]}]}, {bench, [{deps, [benchee]}, - {src_dirs, ["src", "samples"]}, + {src_dirs, ["samples"]}, {plugins, [rebar_mix]}, {provider_hooks, [{pre, [{compile, {mix, find_elixir_libs}}]}]}]}]}. diff --git a/rebar.lock b/rebar.lock index b6fd7cdf..57afcca0 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1 @@ -[{<<"opentelemetry_api">>, - {git,"https://github.com/tsloughter/opentelemetry-erlang-api", - {ref,"26b4963e5a2ce5be373ae8303dc46bae12e06406"}}, - 0}]. +[].