From c25ac353602cf1ce8d655e2f5c7e850e147151ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jan=20Niemier?= Date: Wed, 23 Sep 2020 14:49:30 +0200 Subject: [PATCH] Merge API and SDK (#99) * Initial commit * api initial commit ot_tracer: behavour that an otel tracer must implement ot_span: behaviour an otel span must implement otel: friendly user api on top of ot_tracer and ot_span * ot_span: add type specs on functions * add functions for creating records and align with latest otel protos * update readme to describe use of api application * updates for fred's PR review * separate context propagation otep * add with_value function to run function with scoped context * add http propagation for baggage, correlation and trace * remove callbacks from otel module that were accidentally copeid from ot_span * fix type specs after dialyzing opentelemetry * fix baggage and correlations to/from http propagation * add verification checks that tracer and context implement behaviours * add optional config value to context manager like the tracer * add some additional type specs * make propagator funs fully qualified important for being able to run upgrades. without fully qualified functions the funs will eventually become badfuns after the third module reload. * update span start_opts to include attributes, sampling hint and start_time (#6) * Initial Elixir API (#3) * start on the elixir API * add .gitignore, address feedback * update span start_opts to include attributes, sampling hint and start_time (#6) * use VERSION file for application version * read version from version file Co-authored-by: Tristan Sloughter * remove baggage. it was removed from otep-66 (#9) * remove resource from span record (#12) * add CODEOWNERS file (#15) * Registering named tracers (#16) * add tracer provider interface for named tracers * update readme with use about named tracer * fix register_tracer type spec * move span record to sdk * add additional details about what register_tracer does * new registeration, plus add set_span/2, end_span/2 and replace otel with macros in tracer.hrl (#17) * support registering an application's tracer * add set_span/2, end_span/2 and replace otel with macros in tracer.hrl * update readme to use register_application_tracer/1 * Set Erlang Approvers as CODEOWNERS (#19) * Elixir API based on the Erlang macro api and application registered tracers (#18) * add set_span/2, end_span/2 and replace otel with macros in tracer.hrl * Elixir API macros to match the Erlang macros * include erlang docs in elixir docs (#20) * include erlang docs in elixir docs * make erlang_docs function in mix.exs more readable * make edocs before mix docs and add hex metadata * make use of is_recording in ot_span and update events field to latest spec (#21) * use is_recording to skip possibly expensive span ops * update Event type to otel beta spec * missed a couple of is_recording and events changes (#22) * remove old is_recording_events callback * fix add_events callback type spec to new events record type * fixing up mix.lock and use of event (#23) * update elixir opentelemetry api to replace time events with events * update mix lock file * expand the docs in open_telemtry.ex, the readme and add CONTRIBUTING.md (#24) * add elixir docs to OpenTelemetry module * add register_application_tracer function to OpenTelemetry module and elixir to readme * add readme section about including the SDK * add contributing doc * add docs to span.ex and tracer.ex * chore: cleanup Elixir code - Macros use `:bind_quoted` where possible - Remove needless aliases in tests * Add create_span and new end_span/2 (#27) * Share configuration between Rebar3 and Mix (#11) * ft: share configuration between Rebar3 and Mix This should reduce amount of places where the configuration mismatch can happen. Current solution uses Rebar3 configuration as a source of truth. TBD: - support for Rebar3 profile sharing * fix(docs): build pages for the Erlang files * chore: update mix.lock * a bit of cleanup (#29) * remove unused context with_value function * fix is_recording macro in tracer header * opentelemetry metrics API (#7) * opentelemetry metrics API This patch includes a behaviour for implementing the Meter SDK and API modules for calling the meter for creating and recording measurements. * Update src/ot_meter.erl Co-Authored-By: Arkadiusz Gil * cleanup of metrics api based on comments on PR * add Meter Provider * remove gauge * add meter macros and registration * add elixir meter api module * add register_application_* functions to OpenTelemetry module * add observer api * add elixir modules for counter, measure and observer api * add meter to bound instrument return to simplify api * updates to observer api * remove label set function * more simplification and some docs * fix meter lookup * add counter and measure specific macros * fix get_meter typespec and add miissing callbacks to noop meter * fix provider return values to booleans for registering traer/meter Co-authored-by: Arkadiusz Gil * take default argument in ot_ctx:ctx and some simple bug fixes (#38) * Add callbacks for getting the Resource from a tracer provider (#40) * handle codechange in ot_tracer_provider * add callback to get resource from tracer provider * fix spec for provider resource fetching as resource is defined in the SDK * bump VERSION file to 0.3.0 (#41) * add include dir to files published to hex (#42) * removing wts dependency (#43) * add hex metadata * remove use of wts. get time_offset at export instead of at creation * use system time in nano seconds for an event time * version bump to 0.3.1 (#44) * add otel exporter to list and add lists for integrations (#45) * update readme to use set_attribute not the old add_attribute (#50) * drop attributes and events that aren't a list (#51) * Repair typespec (#46) I'm pretty sure you didn't intend to document [`register_application_tracer/1`][rat1] as: > Current time in UNIX Epoch time, nanoseconds since 00:00:00 UTC on > 1 January 1970. ... but that's how it turned out, perhaps due to a slip-up in 54ce3ad. [rat1]: https://hexdocs.pm/opentelemetry_api/0.3.1/OpenTelemetry.html#register_application_tracer/1 Co-authored-by: Tristan Sloughter * fix bug where there is no current span ctx and update_name is called (#52) * replace create_span with start_inactive_span (#53) * Add accessors to deconstruct Span (#54) * Add accessors to deconstruct Span When trying to link traces across separate systems (either linked via HTTP calls or a message queue), having direct access to the TraceID and the SpanID make it easier to serialize this properly and recreate a Link on the receiving end. This PR adds support for deconstructing a Span Context to get access to these values. I've added a couple of tests but they aren't all passing, so could use a hand getting the test for establishing links to parent trace in shape. * Rework CT test * Use the right type on type specs * Removed test that should be part of an SDK instead * Fix issues/48 (#56) - OpenTelemetry removing `/"` and replacing this with `"` as it creates confusion. - Updated the example doc for the new API updates. Modified `OpenTelemetry.Span` Following changes were done. - Changes related to documentation example `add_event` - Updated `add_event/1` to `add_event/2` to accept `event_name` and `event_attributes`. * Update readme (#59) * version bump to 0.3.2 (#55) * Fixing the string formatting acrosst the project. (#62) Changes to be committed: modified: lib/open_telemetry/measure.ex modified: lib/open_telemetry/meter.ex modified: lib/open_telemetry/observer.ex modified: lib/open_telemetry/tracer.ex * Update span add_event docs variable name (#64) Seems like the variables on lines 9,10 (`ecto_attributes`, and `ecto_event`) are the same variable, but named differently? * Unspecified is not a valid span kind (#67) * move context implementation to api app (#66) * moving to github actions to use the same as the rest of OpenTelemetry org (#68) * fix sampler typespec to term() since sampler is in SDK app * move to github actions for CI * support passing just a span_ctx to link functions (#69) * support passing just a span_ctx to link functions * use covertool and codecov instead of coveralls * fix elixir otp version in main.yml * fix link/2 to take undefined atom and fix typespecs * correct docs for timestamp functions (#70) * Add http status helper (#78) * Add http status helper * Update module doc * Fix message fallback * Use macro * Update headers typing (#79) Co-authored-by: Tristan Sloughter * Add atom support (#80) * Remove CircleCI (#81) Github actions appears to be the CI system now * Metrics api 0.4.0 (#71) * remove uses of 'labelset' which was removed from the spec * initial work on updated metrics api 0.4.0 * rename instrument function to definition and add elixir api * prefix meter macros with ot_ * Update lib/open_telemetry/sum_observer.ex Co-authored-by: Tino Breddin * Update lib/open_telemetry/updown_sum_observer.ex Co-authored-by: Tino Breddin * add some docs to ot_instrument Co-authored-by: Tino Breddin * ctx api update and add optional explicit ctx argument to start span functions (#83) * rename correlations to baggage * update context api * make context to an optional explicit argument to start_span * rename ot_ctx:ctx() type to ot_ctx:t() to be consistent * feat: move opentelemetry app under apps * fix: remove CircleCI configuration * fix(cover): upload coverage report for API * fix: run CT in CI as well as EUnit * chore(cover): properly mark each coverage report This should result with better reports as soon as coverage reports start to parse. Co-authored-by: Sergey Kanzhelev Co-authored-by: Tristan Sloughter Co-authored-by: Zach Daniel Co-authored-by: Greg Mefford Co-authored-by: Arkadiusz Gil Co-authored-by: Garth Kidd Co-authored-by: Leandro Ostera Co-authored-by: Yatender Singh Co-authored-by: Marc Delagrammatikas Co-authored-by: Bryan Naegele Co-authored-by: Dave Lucia Co-authored-by: Tino Breddin --- .github/workflows/main.yml | 14 +- .../opentelemetry/include}/ot_resource.hrl | 0 .../opentelemetry/include}/ot_sampler.hrl | 0 .../opentelemetry/include}/ot_span.hrl | 0 apps/opentelemetry/rebar.config | 2 + .../opentelemetry/src}/opentelemetry.app.src | 0 .../opentelemetry/src}/opentelemetry_app.erl | 0 .../opentelemetry/src}/opentelemetry_sup.erl | 0 .../opentelemetry/src}/ot_batch_processor.erl | 0 .../opentelemetry/src}/ot_exporter.erl | 0 .../opentelemetry/src}/ot_exporter_pid.erl | 0 .../opentelemetry/src}/ot_exporter_stdout.erl | 0 .../opentelemetry/src}/ot_exporter_tab.erl | 0 {src => apps/opentelemetry/src}/ot_meter.hrl | 0 .../opentelemetry/src}/ot_meter_default.erl | 0 .../opentelemetry/src}/ot_meter_server.erl | 0 .../src}/ot_metric_accumulator.erl | 0 .../src}/ot_metric_aggregator.erl | 0 .../src}/ot_metric_aggregator_array.erl | 0 .../src}/ot_metric_aggregator_last_value.erl | 0 .../src}/ot_metric_aggregator_mmsc.erl | 0 .../src}/ot_metric_aggregator_sum.erl | 0 .../src}/ot_metric_controller_push.erl | 0 .../opentelemetry/src}/ot_metric_exporter.erl | 0 .../src}/ot_metric_exporter_stdout.erl | 0 .../src}/ot_metric_integrator.erl | 0 .../opentelemetry/src}/ot_metric_sup.erl | 0 .../src}/ot_propagation_http_b3.erl | 0 .../src}/ot_propagation_http_w3c.erl | 0 .../opentelemetry/src}/ot_resource.erl | 0 .../src}/ot_resource_app_env.erl | 0 .../src}/ot_resource_env_var.erl | 0 .../opentelemetry/src}/ot_sampler.erl | 0 .../opentelemetry/src}/ot_span_ets.erl | 0 .../opentelemetry/src}/ot_span_ets.hrl | 0 .../opentelemetry/src}/ot_span_processor.erl | 0 .../opentelemetry/src}/ot_span_sup.erl | 0 .../opentelemetry/src}/ot_span_sweeper.erl | 0 .../opentelemetry/src}/ot_span_utils.erl | 0 {src => apps/opentelemetry/src}/ot_tracer.hrl | 0 .../opentelemetry/src}/ot_tracer_default.erl | 0 .../opentelemetry/src}/ot_tracer_server.erl | 0 {src => apps/opentelemetry/src}/ot_utils.erl | 0 .../test}/opentelemetry_SUITE.erl | 0 .../test}/ot_batch_processor_SUITE.erl | 0 .../opentelemetry/test}/ot_metric_SUITE.erl | 0 .../opentelemetry/test}/ot_resource_SUITE.erl | 0 .../opentelemetry/test}/ot_samplers_SUITE.erl | 0 .../opentelemetry/test}/ot_sweeper_SUITE.erl | 0 .../opentelemetry/test}/ot_test_utils.hrl | 0 .../.github/workflows/main.yml | 55 +++ apps/opentelemetry_api/.gitignore | 32 ++ apps/opentelemetry_api/CODEOWNERS | 15 + apps/opentelemetry_api/CONTRIBUTING.md | 118 ++++++ apps/opentelemetry_api/LICENSE | 201 +++++++++ apps/opentelemetry_api/README.md | 140 +++++++ apps/opentelemetry_api/VERSION | 1 + apps/opentelemetry_api/include/meter.hrl | 52 +++ .../include/opentelemetry.hrl | 90 ++++ apps/opentelemetry_api/include/ot_sampler.hrl | 3 + apps/opentelemetry_api/include/tracer.hrl | 53 +++ apps/opentelemetry_api/lib/open_telemetry.ex | 212 ++++++++++ .../lib/open_telemetry/counter.ex | 31 ++ .../lib/open_telemetry/meter.ex | 70 ++++ .../lib/open_telemetry/span.ex | 137 ++++++ .../lib/open_telemetry/sum_observer.ex | 27 ++ .../lib/open_telemetry/tracer.ex | 118 ++++++ .../lib/open_telemetry/updown_counter.ex | 31 ++ .../lib/open_telemetry/updown_sum_observer.ex | 27 ++ .../lib/open_telemetry/value_observer.ex | 27 ++ .../lib/open_telemetry/value_recorder.ex | 38 ++ apps/opentelemetry_api/mix.exs | 90 ++++ apps/opentelemetry_api/mix.lock | 9 + apps/opentelemetry_api/rebar.config | 22 + apps/opentelemetry_api/rebar.lock | 1 + apps/opentelemetry_api/src/opentelemetry.erl | 396 ++++++++++++++++++ .../src/opentelemetry_api.app.src | 14 + apps/opentelemetry_api/src/ot_baggage.erl | 87 ++++ apps/opentelemetry_api/src/ot_counter.erl | 62 +++ apps/opentelemetry_api/src/ot_ctx.erl | 141 +++++++ apps/opentelemetry_api/src/ot_http_status.erl | 106 +++++ apps/opentelemetry_api/src/ot_instrument.erl | 50 +++ apps/opentelemetry_api/src/ot_meter.erl | 136 ++++++ .../opentelemetry_api/src/ot_meter_noop.erl | 54 ++- .../src/ot_meter_provider.erl | 84 ++++ apps/opentelemetry_api/src/ot_observer.erl | 32 ++ apps/opentelemetry_api/src/ot_propagation.erl | 65 +++ apps/opentelemetry_api/src/ot_span.erl | 150 +++++++ .../opentelemetry_api/src/ot_sum_observer.erl | 57 +++ apps/opentelemetry_api/src/ot_tracer.erl | 119 ++++++ apps/opentelemetry_api/src/ot_tracer_noop.erl | 95 +++++ .../src/ot_tracer_provider.erl | 107 +++++ .../src/ot_updown_counter.erl | 62 +++ .../src/ot_updown_sum_observer.erl | 57 +++ .../src/ot_value_observer.erl | 57 +++ .../src/ot_value_recorder.erl | 62 +++ .../test/open_telemetry_test.exs | 72 ++++ .../test/opentelemetry_api_SUITE.erl | 140 +++++++ .../test/otel_metrics_SUITE.erl | 48 +++ apps/opentelemetry_api/test/test_helper.exs | 1 + rebar.config | 18 +- rebar.lock | 5 +- 102 files changed, 3870 insertions(+), 23 deletions(-) rename {include => apps/opentelemetry/include}/ot_resource.hrl (100%) rename {include => apps/opentelemetry/include}/ot_sampler.hrl (100%) rename {include => apps/opentelemetry/include}/ot_span.hrl (100%) create mode 100644 apps/opentelemetry/rebar.config rename {src => apps/opentelemetry/src}/opentelemetry.app.src (100%) rename {src => apps/opentelemetry/src}/opentelemetry_app.erl (100%) rename {src => apps/opentelemetry/src}/opentelemetry_sup.erl (100%) rename {src => apps/opentelemetry/src}/ot_batch_processor.erl (100%) rename {src => apps/opentelemetry/src}/ot_exporter.erl (100%) rename {src => apps/opentelemetry/src}/ot_exporter_pid.erl (100%) rename {src => apps/opentelemetry/src}/ot_exporter_stdout.erl (100%) rename {src => apps/opentelemetry/src}/ot_exporter_tab.erl (100%) rename {src => apps/opentelemetry/src}/ot_meter.hrl (100%) rename {src => apps/opentelemetry/src}/ot_meter_default.erl (100%) rename {src => apps/opentelemetry/src}/ot_meter_server.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_accumulator.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_aggregator.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_aggregator_array.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_aggregator_last_value.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_aggregator_mmsc.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_aggregator_sum.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_controller_push.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_exporter.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_exporter_stdout.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_integrator.erl (100%) rename {src => apps/opentelemetry/src}/ot_metric_sup.erl (100%) rename {src => apps/opentelemetry/src}/ot_propagation_http_b3.erl (100%) rename {src => apps/opentelemetry/src}/ot_propagation_http_w3c.erl (100%) rename {src => apps/opentelemetry/src}/ot_resource.erl (100%) rename {src => apps/opentelemetry/src}/ot_resource_app_env.erl (100%) rename {src => apps/opentelemetry/src}/ot_resource_env_var.erl (100%) rename {src => apps/opentelemetry/src}/ot_sampler.erl (100%) rename {src => apps/opentelemetry/src}/ot_span_ets.erl (100%) rename {src => apps/opentelemetry/src}/ot_span_ets.hrl (100%) rename {src => apps/opentelemetry/src}/ot_span_processor.erl (100%) rename {src => apps/opentelemetry/src}/ot_span_sup.erl (100%) rename {src => apps/opentelemetry/src}/ot_span_sweeper.erl (100%) rename {src => apps/opentelemetry/src}/ot_span_utils.erl (100%) rename {src => apps/opentelemetry/src}/ot_tracer.hrl (100%) rename {src => apps/opentelemetry/src}/ot_tracer_default.erl (100%) rename {src => apps/opentelemetry/src}/ot_tracer_server.erl (100%) rename {src => apps/opentelemetry/src}/ot_utils.erl (100%) rename {test => apps/opentelemetry/test}/opentelemetry_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_batch_processor_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_metric_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_resource_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_samplers_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_sweeper_SUITE.erl (100%) rename {test => apps/opentelemetry/test}/ot_test_utils.hrl (100%) create mode 100644 apps/opentelemetry_api/.github/workflows/main.yml create mode 100644 apps/opentelemetry_api/.gitignore create mode 100644 apps/opentelemetry_api/CODEOWNERS create mode 100644 apps/opentelemetry_api/CONTRIBUTING.md create mode 100644 apps/opentelemetry_api/LICENSE create mode 100644 apps/opentelemetry_api/README.md create mode 100644 apps/opentelemetry_api/VERSION create mode 100644 apps/opentelemetry_api/include/meter.hrl create mode 100644 apps/opentelemetry_api/include/opentelemetry.hrl create mode 100644 apps/opentelemetry_api/include/ot_sampler.hrl create mode 100644 apps/opentelemetry_api/include/tracer.hrl create mode 100644 apps/opentelemetry_api/lib/open_telemetry.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/counter.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/meter.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/span.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/sum_observer.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/tracer.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/updown_counter.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/updown_sum_observer.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/value_observer.ex create mode 100644 apps/opentelemetry_api/lib/open_telemetry/value_recorder.ex create mode 100644 apps/opentelemetry_api/mix.exs create mode 100644 apps/opentelemetry_api/mix.lock create mode 100644 apps/opentelemetry_api/rebar.config create mode 100644 apps/opentelemetry_api/rebar.lock create mode 100644 apps/opentelemetry_api/src/opentelemetry.erl create mode 100644 apps/opentelemetry_api/src/opentelemetry_api.app.src create mode 100644 apps/opentelemetry_api/src/ot_baggage.erl create mode 100644 apps/opentelemetry_api/src/ot_counter.erl create mode 100644 apps/opentelemetry_api/src/ot_ctx.erl create mode 100644 apps/opentelemetry_api/src/ot_http_status.erl create mode 100644 apps/opentelemetry_api/src/ot_instrument.erl create mode 100644 apps/opentelemetry_api/src/ot_meter.erl rename samples/ot_benchmarks.erl => apps/opentelemetry_api/src/ot_meter_noop.erl (54%) create mode 100644 apps/opentelemetry_api/src/ot_meter_provider.erl create mode 100644 apps/opentelemetry_api/src/ot_observer.erl create mode 100644 apps/opentelemetry_api/src/ot_propagation.erl create mode 100644 apps/opentelemetry_api/src/ot_span.erl create mode 100644 apps/opentelemetry_api/src/ot_sum_observer.erl create mode 100644 apps/opentelemetry_api/src/ot_tracer.erl create mode 100644 apps/opentelemetry_api/src/ot_tracer_noop.erl create mode 100644 apps/opentelemetry_api/src/ot_tracer_provider.erl create mode 100644 apps/opentelemetry_api/src/ot_updown_counter.erl create mode 100644 apps/opentelemetry_api/src/ot_updown_sum_observer.erl create mode 100644 apps/opentelemetry_api/src/ot_value_observer.erl create mode 100644 apps/opentelemetry_api/src/ot_value_recorder.erl create mode 100644 apps/opentelemetry_api/test/open_telemetry_test.exs create mode 100644 apps/opentelemetry_api/test/opentelemetry_api_SUITE.erl create mode 100644 apps/opentelemetry_api/test/otel_metrics_SUITE.erl create mode 100644 apps/opentelemetry_api/test/test_helper.exs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d32b4a96..87fee2ac 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,9 @@ jobs: otp_version: ['23.0.2', '22.3.4.2', '21.3.8.16'] os: [ubuntu-latest] + env: + OTP_VERSION: ${{ matrix.otp_version }} + steps: - uses: actions/checkout@v2 - uses: gleam-lang/setup-erlang@v1.0.0 @@ -27,7 +30,9 @@ jobs: - name: Compile run: rebar3 compile - name: EUnit tests - run: rebar3 eunit + run: rebar3 eunit --cover + - name: Common Test tests + run: rebar3 ct --cover - name: Dialyzer run: rebar3 dialyzer - name: XRef @@ -37,3 +42,10 @@ jobs: - uses: codecov/codecov-action@v1 with: file: _build/test/covertool/opentelemetry.covertool.xml + env_vars: OTP_VERSION + flags: sdk + - uses: codecov/codecov-action@v1 + with: + file: _build/test/covertool/opentelemetry_api.covertool.xml + env_vars: OTP_VERSION + flags: api 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 100% rename from src/opentelemetry.app.src rename to apps/opentelemetry/src/opentelemetry.app.src 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 100% rename from src/ot_propagation_http_w3c.erl rename to apps/opentelemetry/src/ot_propagation_http_w3c.erl 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 100% rename from src/ot_span_ets.erl rename to apps/opentelemetry/src/ot_span_ets.erl 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 100% rename from src/ot_span_utils.erl rename to apps/opentelemetry/src/ot_span_utils.erl 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 100% rename from src/ot_tracer_default.erl rename to apps/opentelemetry/src/ot_tracer_default.erl 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 100% rename from test/opentelemetry_SUITE.erl rename to apps/opentelemetry/test/opentelemetry_SUITE.erl diff --git a/test/ot_batch_processor_SUITE.erl b/apps/opentelemetry/test/ot_batch_processor_SUITE.erl similarity index 100% rename from test/ot_batch_processor_SUITE.erl rename to apps/opentelemetry/test/ot_batch_processor_SUITE.erl 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/.github/workflows/main.yml b/apps/opentelemetry_api/.github/workflows/main.yml new file mode 100644 index 00000000..f627741a --- /dev/null +++ b/apps/opentelemetry_api/.github/workflows/main.yml @@ -0,0 +1,55 @@ +name: Common Test + +on: + pull_request: + branches: + - 'master' + push: + branches: + - 'master' + +jobs: + erlang_tests: + name: OTP ${{ matrix.otp_version }} + runs-on: ${{ matrix.os }} + + strategy: + matrix: + otp_version: ['23.0.2', '22.3.4.2', '21.3.8.16'] + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v2 + - uses: gleam-lang/setup-erlang@v1.0.0 + with: + otp-version: ${{ matrix.otp_version }} + + - name: Compile + run: rebar3 compile + - name: EUnit tests + run: rebar3 eunit + - name: Dialyzer + run: rebar3 dialyzer + - name: XRef + run: rebar3 xref + - name: Covertool + run: rebar3 covertool generate + - uses: codecov/codecov-action@v1 + with: + file: _build/test/covertool/opentelemetry.covertool.xml + + elixir_tests: + runs-on: ubuntu-latest + name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}} + strategy: + matrix: + otp_version: ['23.0.2', '22.3.4.2', '21.3.8.16'] + elixir: ['1.9.4'] + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-elixir@v1 + with: + otp-version: ${{matrix.otp_version}} + elixir-version: ${{matrix.elixir}} + - run: mix deps.get + - run: mix test diff --git a/apps/opentelemetry_api/.gitignore b/apps/opentelemetry_api/.gitignore new file mode 100644 index 00000000..4df786a4 --- /dev/null +++ b/apps/opentelemetry_api/.gitignore @@ -0,0 +1,32 @@ +edoc +.rebar3 +_* +.eunit +*.o +*.beam +*.plt +*.swp +*.swo +.erlang.cookie +ebin +log +erl_crash.dump +.rebar +logs +_build +.idea +*.iml +rebar3.crashdump +*~ + +/_build +/cover +/deps +/doc +/.fetch +erl_crash.dump +*.ez +*.beam +/config/*.secret.exs +.elixir_ls/ + diff --git a/apps/opentelemetry_api/CODEOWNERS b/apps/opentelemetry_api/CODEOWNERS new file mode 100644 index 00000000..3de69340 --- /dev/null +++ b/apps/opentelemetry_api/CODEOWNERS @@ -0,0 +1,15 @@ +##################################################### +# +# List of approvers for OpenTelemetry Erlang API repository +# +##################################################### +# +# Learn about membership in OpenTelemetry community: +# https://github.com/open-telemetry/community/blob/master/community-membership.md +# +# +# Learn about CODEOWNERS file format: +# https://help.github.com/en/articles/about-code-owners +# + +* @open-telemetry/erlang-approvers diff --git a/apps/opentelemetry_api/CONTRIBUTING.md b/apps/opentelemetry_api/CONTRIBUTING.md new file mode 100644 index 00000000..a74b42e9 --- /dev/null +++ b/apps/opentelemetry_api/CONTRIBUTING.md @@ -0,0 +1,118 @@ +# Contributing to opentelemetry-erlang-api + +The Erlang/Elixir special interest group (SIG) meets as part of the +[Erlang Ecosystem Foundation](https://erlef.org/wg/observability). See the +OpenTelemetry [community](https://github.com/open-telemetry/community#erlangelixir-sdk) +repo for information on this and other language SIGs. + + +## Pull Requests + +### How to Send Pull Requests + +Everyone is welcome to contribute code to `opentelemetry-erlang-api` via +GitHub pull requests (PRs). + +To create a new PR, fork the project in GitHub and clone the upstream +repo: + +```sh +$ git clone https://github.com/open-telemetry/opentelemetry-erlang-api/ +``` + +This would put the project in the `opentelemetry-erlang-api` directory in +current working directory. + +Enter the newly created directory and add your fork as a new remote: + +```sh +$ git remote add git@github.com:/opentelemetry-erlang-api +``` + +Check out a new branch, make modifications, run tests, and +push the branch to your fork: + +```sh +$ git checkout -b +# edit files +$ rebar3 ct +$ mix test +$ git add -p +$ git commit +$ git push +``` + +Open a pull request against the main `opentelemetry-erlang-api` repo. + +### How to Receive Comments + +* If the PR is not ready for review, please put `[WIP]` in the title, + tag it as `work-in-progress`, or mark it as + [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). +* Make sure CLA is signed and CI is clear. + +### How to Get PRs Merged + +A PR is considered to be **ready to merge** when: + +* It has received two approvals from Collaborators/Maintainers (at + different companies). +* Major feedbacks are resolved. +* It has been open for review for at least one working day. This gives + people reasonable time to review. +* Trivial change (typo, cosmetic, doc, etc.) doesn't have to wait for + one day. +* Urgent fix can take exception as long as it has been actively + communicated. + +Any Collaborator/Maintainer can merge the PR once it is **ready to +merge**. + +## Design Choices + +As with other OpenTelemetry clients, opentelemetry-erlang-api follows the +[opentelemetry-specification](https://github.com/open-telemetry/opentelemetry-specification). + +It's especially valuable to read through the [library +guidelines](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/library-guidelines.md). + +### Focus on Capabilities, Not Structure Compliance + +OpenTelemetry is an evolving specification, one where the desires and +use cases are clear, but the method to satisfy those uses cases are +not. + +As such, Contributions should provide functionality and behavior that +conforms to the specification, but the interface and structure is +flexible. + +It is preferable to have contributions follow the idioms of the +language rather than conform to specific API names or argument +patterns in the spec. + +For a deeper discussion, see: +https://github.com/open-telemetry/opentelemetry-specification/issues/165 + +## Style Guide + +* Make sure to run `make precommit` - this will find and fix the code + formatting. + +## Approvers and Maintainers + +Approvers: + +- [Fred Hebert](https://github.com/ferd), Postmates +- [Greg Mefford](https://github.com/gregmefford), Bleacher Report +- [Zach Daniel](https://github.com/zachdaniel), Variance + +Maintainers: + +- [Tristan Sloughter](https://github.com/tsloughter), Postmates +- [Ilya Khaprov](https://github.com/deadtrickster), Kobil Systems GmbH +- [Ɓukasz Jan Niemier](https://github.com/hauleth), Kobil Systems GmbH + +### Become an Approver or a Maintainer + +See the [community membership document in OpenTelemetry community +repo](https://github.com/open-telemetry/community/blob/master/community-membership.md). 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..f06dd36f --- /dev/null +++ b/apps/opentelemetry_api/rebar.config @@ -0,0 +1,22 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{profiles, + [{docs, [{deps, [edown]}, + {edoc_opts, + [{doclet, edown_doclet}, + {preprocess, true}, + {dir, "edoc"}, + {subpackages, true}]}]}, + {test, [{erl_opts, [nowarn_export_all]}, + {deps, []}, + {ct_opts, [{ct_hooks, [cth_surefire]}]}]}]}. + +{xref_checks, [undefined_function_calls, undefined_functions, + deprecated_function_calls, deprecated_functions]}. +{xref_ignores, []}. + +{project_plugins, [covertool]}. +{cover_enabled, true}. +{cover_export_enabled, true}. +{covertool, [{coverdata_files, ["ct.coverdata"]}]}. diff --git a/apps/opentelemetry_api/rebar.lock b/apps/opentelemetry_api/rebar.lock new file mode 100644 index 00000000..57afcca0 --- /dev/null +++ b/apps/opentelemetry_api/rebar.lock @@ -0,0 +1 @@ +[]. 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..e4152426 --- /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 t() :: map(). +-type key() :: term(). +-type value() :: term(). +-type token() :: reference(). + +-export_type([t/0, + key/0, + value/0]). + +-define(CURRENT_CTX, '$__current_otel_ctx'). + +-spec new() -> t(). +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(t(), 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(t(), 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/samples/ot_benchmarks.erl b/apps/opentelemetry_api/src/ot_meter_noop.erl similarity index 54% rename from samples/ot_benchmarks.erl rename to apps/opentelemetry_api/src/ot_meter_noop.erl index a394c147..3846f70a 100644 --- a/samples/ot_benchmarks.erl +++ b/apps/opentelemetry_api/src/ot_meter_noop.erl @@ -15,17 +15,51 @@ %% @doc %% @end %%%------------------------------------------------------------------------- --module(ot_benchmarks). +-module(ot_meter_noop). --export([run/0]). +-behaviour(ot_meter). --include_lib("opentelemetry_api/include/tracer.hrl"). +-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]). -run() -> - Iterations = 10, - PDictCtxFun = fun() -> - [?start_span(<<"span-", (integer_to_binary(X))/binary>>) - || X <- lists:seq(1, Iterations)] - end, - benchee:run(#{<<"pdict_ctx">> => PDictCtxFun}), +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..8dbf3e1e --- /dev/null +++ b/apps/opentelemetry_api/src/ot_tracer.erl @@ -0,0 +1,119 @@ +%%%------------------------------------------------------------------------ +%% 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(), opentelemetry:tracer() | opentelemetry:span_ctx()) -> 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(opentelemetry:tracer()) -> boolean() | {error, term()}. +end_span(Tracer={Module, _}) -> + Module:end_span(Tracer). + +-spec end_span(ot_ctx:ctx() | opentelemetry:tracer(), opentelemetry:tracer() | opentelemetry:span_ctx()) + -> boolean() | {error, term()}. +end_span(Ctx, Tracer={Module, _}) -> + Module:end_span(Ctx, Tracer); +end_span(Tracer={Module, _}, SpanCtx) -> + Module:end_span(Tracer, SpanCtx). + +-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..2a800430 --- /dev/null +++ b/apps/opentelemetry_api/src/ot_tracer_noop.erl @@ -0,0 +1,95 @@ +%%%------------------------------------------------------------------------ +%% 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(), opentelemetry:tracer() | opentelemetry:span_ctx()) + -> 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 c09565a6..906cbb03 100644 --- a/rebar.config +++ b/rebar.config @@ -1,12 +1,11 @@ {erl_opts, [debug_info]}. -{deps, - [{opentelemetry_api, {git, "https://github.com/open-telemetry/opentelemetry-erlang-api", - {branch, "master"}}}]}. +{deps, []}. {shell, [{apps, [opentelemetry]}, {config, "config/sys.config"}]}. -{project_plugins, [covertool]}. +{project_plugins, [covertool, + erlfmt]}. {profiles, [{test, [{erl_opts, [nowarn_export_all]}, @@ -15,14 +14,17 @@ {interop, [{deps, [jsone]}, {extra_src_dirs, ["interop"]}]}, - {docs, [{deps, []}, + {docs, [{deps, [edown]}, {edoc_opts, - [{preprocess, true}]}]}, + [{doclet, edown_doclet}, + {preprocess, true}, + {dir, "edoc"}, + {subpackages, true}]}]}, {bench, [{deps, [benchee]}, - {src_dirs, ["src", "samples"]}, + {extra_src_dirs, ["bench"]}, {plugins, [rebar_mix]}, - {provider_hooks, [{pre, [{compile, {mix, find_elixir_libs}}]}]}]}]}. + {provider_hooks, [{pre, [{compile, {mix, find_elixir_libs}}]}]}]}]}. {xref_checks, [undefined_function_calls, undefined_functions, deprecated_function_calls, deprecated_functions]}. diff --git a/rebar.lock b/rebar.lock index 336d44c7..57afcca0 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1 @@ -[{<<"opentelemetry_api">>, - {git,"https://github.com/open-telemetry/opentelemetry-erlang-api", - {ref,"3f1cbc2d973cf5e5b40dabdb105c395851295ca5"}}, - 0}]. +[].