Feature Flag Service

This service is written in Erlang/Elixir and it is responsible for creating, reading, updating and deleting feature flags in a PostgreSQL DB. It is called by Product Catalog and Shipping services.

Feature Flag Service Source

Traces

Initializing Tracing

In order to set up OpenTelemetry instrumentation for Phoenix, and Ecto, we need to call the setup methods of their instrumentation packages before starting the Supervisor.

This is done in the application.ex as follows:

@impl true
  def start(_type, _args) do
    OpentelemetryEcto.setup([:featureflagservice, :repo])
    OpentelemetryPhoenix.setup()

    children = [
      # Start the Ecto repository
      Featureflagservice.Repo,
      # Start the PubSub system
      {Phoenix.PubSub, name: Featureflagservice.PubSub},
      # Start the Endpoint (http/https)
      FeatureflagserviceWeb.Endpoint
      # Start a worker by calling: Featureflagservice.Worker.start_link(arg)
      # {Featureflagservice.Worker, arg}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Featureflagservice.Supervisor]
    Supervisor.start_link(children, opts)
  end

To add tracing to grpcbox, we need to add the appropriate interceptor.

This is configured in the runtime.exs file, as follows:

config :grpcbox,
  servers: [
    %{
      :grpc_opts => %{
        :service_protos => [:ffs_demo_pb],
        :unary_interceptor => {:otel_grpcbox_interceptor, :unary},
        :services => %{:"oteldemo.FeatureFlagService" => :ffs_service}
      },
      :listen_opts => %{:port => grpc_port}
    }
  ]

Add attributes to auto-instrumented spans

Adding attributes to a span is accomplished by using ?set_attribute on the span object. In the get_flag function two attributes are added to the span.

-include_lib("grpcbox/include/grpcbox.hrl").

-include_lib("opentelemetry_api/include/otel_tracer.hrl").

-spec get_flag(ctx:t(), ffs_demo_pb:get_flag_request()) ->
    {ok, ffs_demo_pb:get_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
get_flag(Ctx, #{name := Name}) ->
    case 'Elixir.Featureflagservice.FeatureFlags':get_feature_flag_by_name(Name) of
        nil ->
            {grpc_error, {?GRPC_STATUS_NOT_FOUND, <<"the requested feature flag does not exist">>}};
        #{'__struct__' := 'Elixir.Featureflagservice.FeatureFlags.FeatureFlag',
          description := Description,
          enabled := Enabled,
          inserted_at := CreatedAt,
          updated_at := UpdatedAt
         } ->
            ?set_attribute('app.featureflag.name', Name),
            ?set_attribute('app.featureflag.enabled', Enabled),
            {ok, Epoch} = 'Elixir.NaiveDateTime':from_erl({{1970, 1, 1}, {0, 0, 0}}),
            CreatedAtSeconds = 'Elixir.NaiveDateTime':diff(CreatedAt, Epoch),
            UpdatedAtSeconds = 'Elixir.NaiveDateTime':diff(UpdatedAt, Epoch),
            Flag = #{name => Name,
                     description => Description,
                     enabled => Enabled,
                     created_at => #{seconds => CreatedAtSeconds, nanos => 0},
                     updated_at => #{seconds => UpdatedAtSeconds, nanos => 0}},
            {ok, #{flag => Flag}, Ctx}
    end.

Metrics

TBD

Logs

TBD

Last modified December 2, 2023: Remove extra comma (#3638) (75471b41)