Coder Social home page Coder Social logo

marcdel / open_telemetry_decorator Goto Github PK

View Code? Open in Web Editor NEW
38.0 0.0 21.0 215 KB

A function decorator for OpenTelemetry traces.

Home Page: https://hex.pm/packages/open_telemetry_decorator

Elixir 98.82% Makefile 1.18%
elixir telemetry opentelemetry observability o11y

open_telemetry_decorator's Introduction

OpenTelemetryDecorator

Build status badge Hex version badge Hex downloads badge

A function decorator for OpenTelemetry traces.

Installation

Add open_telemetry_decorator to your list of dependencies in mix.exs. We include the opentelemetry_api package, but you'll need to add opentelemetry yourself in order to report spans and traces.

def deps do
  [
    {:opentelemetry, "~> 1.4"},
    {:opentelemetry_exporter, "~> 1.7"},
    {:open_telemetry_decorator, "~> 1.5"}
  ]
end

Then follow the directions for the exporter of your choice to send traces to to zipkin, honeycomb, etc. https://github.com/open-telemetry/opentelemetry-erlang/tree/main/apps/opentelemetry_zipkin

Honeycomb Example

config/runtime.exs

api_key = System.fetch_env!("HONEYCOMB_KEY")

config :opentelemetry_exporter,
  otlp_endpoint: "https://api.honeycomb.io:443",
  otlp_headers: [{"x-honeycomb-team", api_key}]

Usage

Add use OpenTelemetryDecorator to the module, and decorate any methods you want to trace with @decorate with_span("span name").

The with_span decorator will automatically wrap the decorated function in an opentelemetry span with the provided name.

defmodule MyApp.Worker do
  use OpenTelemetryDecorator

  @decorate with_span("worker.do_work")
  def do_work(arg1, arg2) do
    ...doing work
  end
end

Span Attributes

The with_span decorator allows you to specify an include option which gives you more flexibility with what you can include in the span attributes. Omitting the include option with with_span means no attributes will be added to the span by the decorator.

defmodule MyApp.Worker do
  use OpenTelemetryDecorator

  @decorate with_span("worker.do_work", include: [:arg1, :arg2])
  def do_work(arg1, arg2) do
    # ...doing work
  end
end

The O11y module includes a helper for setting additional attributes outside of the include option. Attributes added in either a set call or in the include that are not primitive OTLP values will be converted to strings with Kernel.inspect/1.

defmodule MyApp.Worker do
  use OpenTelemetryDecorator

  @decorate with_span("worker.do_work")
  def do_work(arg1, arg2) do
    O11y.set_attributes(arg1: arg1, arg2: arg2)
    # ...doing work
    Attributes.set_attribute(:output, "something")
  end
end

The decorator uses a macro to insert code into your function at compile time to wrap the body in a new span and link it to the currently active span. In the example above, the do_work method would become something like this:

defmodule MyApp.Worker do
  require OpenTelemetry.Tracer, as: Tracer

  def do_work(arg1, arg2) do
    Tracer.with_span "my_app.worker.do_work" do
      # ...doing work
      Tracer.set_attributes(arg1: arg1, arg2: arg2)
    end
  end
end

Configuration

Prefixing Span Attributes

Honeycomb suggests that you namespace custom fields, specifically prefixing manual instrumentation with app

✳️ You can now set this in the underlying o11y library with the attribute_namespace option ✳️

config :o11y, :attribute_namespace, "app"

⚠️ You can still configure it with the attr_prefix option in config/config.exs, but that will be deprecated in a future release. ⚠️

config :open_telemetry_decorator, attr_prefix: "app."

Changing the join character for nested attributes

⚠️ This configuration option is no longer available ⚠️

Additional Examples

You can provide span attributes by specifying a list of variable names as atoms.

This list can include...

Any variables (in the top level closure) available when the function exits. Note that variables declared as part of an if, case, cond, or with block are in a separate scope so NOT available for include attributes.

defmodule MyApp.Math do
  use OpenTelemetryDecorator

  @decorate with_span("my_app.math.add", include: [:a, :b, :sum])
  def add(a, b) do
    sum = a + b
    {:ok, sum}
  end
end

The result of the function by including the atom :result:

defmodule MyApp.Math do
  use OpenTelemetryDecorator

  @decorate with_span("my_app.math.add", include: [:result])
  def add(a, b) do
    {:ok, a + b}
  end
end

Structs will be converted to maps and included in the span attributes. You can specify which fields to include with the @derive attribute provided by O11y.

defmodule User do
  use OpenTelemetryDecorator

  @derive {O11y.SpanAttributes, only: [:id, :name]}
  defstruct [:id, :name, :email, :password]

  @decorate with_span("user.create", include: [:user])
  def create(user) do
    {:ok, user}
  end
end

Development

make check before you commit! If you'd prefer to do it manually:

  • mix do deps.get, deps.unlock --unused, deps.clean --unused if you change dependencies
  • mix compile --warnings-as-errors for a stricter compile
  • mix coveralls.html to check for test coverage
  • mix credo to suggest more idiomatic style for your code
  • mix dialyzer to find problems typing might reveal… albeit slowly
  • mix docs to generate documentation

open_telemetry_decorator's People

Contributors

dependabot-preview[bot] avatar dependabot[bot] avatar erszcz avatar leggebroten avatar luizmiranda7 avatar marcdel avatar ulissesalmeida avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

open_telemetry_decorator's Issues

Make call to set_attributes to match the OTLP specs

Currently, the result of Attributes.get_attributes returns a Keyword which is then passed directly to Tracer.set_attributes.

This is not consistent with the spec which expects a list of two element tuples, {attribute_name_as_binary, OTLP_acceptable_value}

Where a OTLP_acceptable_value is: string, integer, double or boolean

This could lead to integration issues with certain providers.

Example

[{"/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}]

included attributes are not converted to string and `pathing` is not available on `:result`

Issues

  1. OTLP attribute values can only accept strings, integers, doubles, or boolean.
    https://hexdocs.pm/opentelemetry_api/OpenTelemetry.html#t:attributes_map/0

Currently if the target of the :include is not one of these primitives, the attribute will not added to the span.

  1. :include cannot handle string-key maps.

  2. the :result lacks functionality as return values are rarely primitives.

Suggested solutions

I would like to submit a PR to address these.

  1. do an inspect if attribute value is not one of the allowed primitives.
  2. Handle indexing into string-keyed map
  3. Add indexing/pathing functionality to :result. e.g. include: [[:result, :key]]

changelog?

so it would be nice to have a changelog. I see you use releases but changelog are likely much better as tools like dependabot can use them.

I cannot find any details about 1.3.x in changelog or release to make sure it won t break anything . any details?

Dialyzer issues due to parent span argument

Hey, @marcdel! Thanks for providing this library. We get a lot of value from it.

Using v0.5.3 of this library along with v0.5.0 of opentelemetry and opentelemetry_api, we are seeing dialyzer errors for every use of the trace decorator.

The error looks like this:

lib/myapp/some_file.ex:1: The call otel_tracer:with_span({atom(),_},<<..list of bytes elided..>>,_@1::#{'parent':='undefined' | {'span_ctx',non_neg_integer(),non_neg_integer(),'undefined' | integer(),'undefined' | [{_,_}],'false' | 'true' | 'undefined','false' | 'true' | 'undefined','false' | 'true' | 'undefined','undefined' | {atom(),_}}, 'sampler'=>_},fun((_) -> #{'__struct__':='Elixir.Ecto.Changeset', 'action':=atom(), 'changes':=#{atom()=>_}, 'constraints':=[map()], 'data':=map(), 'empty_values':=_, 'errors':=[{_,_}], 'filters':=#{atom()=>_}, 'params':='nil' | #{binary()=>_}, 'prepare':=[fun((_) -> any())], 'repo':=atom(), 'repo_opts':=[{_,_}], 'required':=[atom()], 'types':=#{atom()=>atom() | {_,_} | {_,_,_}}, 'valid?':=boolean(), 'validations':=[{_,_}]})) breaks the contract (opentelemetry:tracer(),opentelemetry:span_name(),otel_span:start_opts(),traced_fun(T)) -> T

I've traced the issue down to the passing of parent as a span argument. The problem is actually upstream in opentelemetry_api:

The Elixir API specifies that parent is an allowed option, but the Erlang API does not.

Since the Elixir version of with_span macroexpands to the Erlang version, its typespec wins and dialyzer complains.

The reason I'm reporting the bug here and not upstream is that in v0.6.0 of opentelemetry_api, the parent option has been removed entirely. with_span always creates the new span as a child of the current span. See https://github.com/open-telemetry/opentelemetry-erlang/blob/v0.6.0/apps/opentelemetry_api/lib/open_telemetry/tracer.ex#L20-L50.

Given that v0.5.4 of this library is now based on 0.6.0, it looks like it should no longer be providing a parent argument to with_span.

I can try to submit a PR for this if you like, but I don't have a good place to test that my changes would work in a real-world context, since we're still on an older version of everything.

Feature Request: `with_event` decorator

What are the thoughts on adding a new decorator alongside the main with_span decorator that handles adding a span event?

In our use of OpenTelemetry we noticed that we have some functions that we would like to trace, but only when it is called while in an already started span. We did not want to create a new span and introduce noise (in our situation). A with_event or with_span_event decorator that utilizes some of the same attribute parsing logic seems useful and makes sense to add into this library. I wouldn't mind implementing this as well.

Thoughts?

Feature request, ability to add a link to the created span

For event driven architectures, many spans do not have a parent-child relationship but rather a "causal" relationship.
OpenTelemetry supports this functionality by adding one or more Links when creating the new span

A good blog from Lightstep discusses the issue.

I would like to submit a PR supporting this capability perhaps as a new links: option (sibling to the current include: option).

The value passed to the links: option would be a "path" specification exactly analogous to that given to include:

Example

Traced function being invoked as an event with a causal_link field containing the OTLP link to the originating span

      @decorate trace("Example.with.link", include: [:msg], links: [[:msg, :causal_link]])
      def link(msg), do: {:ok, msg}

Alternatively, a user could use pattern matching to make the links list easier to grok

      @decorate trace("Example.with.link", include: [:msg], links: [:causal_link])
      def link(%{causal_link: causal_link}), do: {:ok, msg}

Example test showing how the causal link is created, passed to the traced function, and added to the created span

    test "when `link` option present, the traced span will have the specified OTLP Link" do
      # Simulate a trace that has completed
      causal_span = Tracer.start_span("causal-span")
      span_ctx(span_id: causal_span_id) = Span.end_span(causal_span)

      # Create a link to that other trace
      link_to_causal_span = OpenTelemetry.link(causal_span)

      # Simulate receiving a message containing a link to a causal span
      Example.link(%{causal_link: link_to_causal_span})

      # Validate that the "traced" span was created with the link
      assert_receive {:span, span(name: "Example.with.link", links: links)}

      [link] = get_span_links(links)

      link(span_id: ^causal_span_id) = link
    end

Join nested fields using dot

Spurred on by your quick action a couple a days ago, I have another idea for improvement.

I'd like the nested accessed fields to be joined using a dot instead of an underscore. This would align with the default OTel way of name spacing things, e.g. service.name or http.route. I think it's also nice to have a different character to separate the nesting from naming, e.g. my_struct.other_struct.field vs my_struct_other_struct_field, making it clearer where one name ends and the next starts.

Of course this could be configurable not to break things for existing users.

Add MFA functionality for fetching value from `:result`

the :result functionality is largely useless as return values are rarely simple primitives.

Typically, the return value is a tuple and "pathing" isn't sufficient.

I'd like to submit a PR that extends the pathing feature I mention in issue 102.

It would allow the client to provide a function in the :include path to more flexibly fetch the desired value

Option to always add PID as a span attribute

It would be nice to have option to always add the PID as an attribute

When correlating inter-process spans, it would be very helpful to always have PID as an attribute.
For instance, if I had a with_span on a process' init call, you could correlate all spans made by that process instance.

Seems like there would be two options:

  • General config.exs where you turn it on for all spans
  • A special :pid option (similar to :result) in the include: list.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.