Skip to content

add otel exports#3373

Draft
kinjalh wants to merge 1 commit intomasterfrom
otel-exports
Draft

add otel exports#3373
kinjalh wants to merge 1 commit intomasterfrom
otel-exports

Conversation

@kinjalh
Copy link
Copy Markdown
Member

@kinjalh kinjalh commented Mar 30, 2026

Add OTel exports

Test Plan

Unit tests

Checklist

  • If required, I have updated the Plural documentation accordingly.
  • I have added tests to cover my changes.
  • I have added a meaningful title and summary to convey the impact of this PR to a user.

Plural Flow: console

@kinjalh kinjalh added the enhancement New feature or request label Mar 30, 2026
Copy link
Copy Markdown
Member

@michaeljguarino michaeljguarino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would need graphql exposure of the deployment_settings metrics fields, and also integration into the k8s operator too. You can probably get cursor agent to do it for you tbh.

@@ -0,0 +1,95 @@
defmodule Console.Otel.Exporter do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no library that handles this at the moment? There might not be, but would be nice to use if so

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think existing library opentelemetry_exporter is still dev status (not guaranteed to be stable)?

"""
@spec maybe_export_metrics() :: :ok | {:error, term}
def maybe_export_metrics do
case Settings.fetch() do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convert this into a with expression (almost all nested cases can become them)

Use this for direct invocation (testing, manual runs).
"""
@spec export_metrics() :: :ok | {:error, term}
def export_metrics do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still needed?

end
end

defp should_run?(crontab) do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this will work w/o some statefulness (you need to keep around the last run date somewhere to compute the next run time, we do that everywhere in the db-bound crons).

A way to simulate that would be to convert this into a genserver, keep the last run time in genserver state, and only run the metrics export if the node is a determined leader (use the Console.ClusterRing module to determine that)

[]
|> Stream.concat(build_service_metrics(timestamp))
|> Stream.concat(build_cluster_metrics(timestamp))
|> Enum.to_list()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doing an enum.to_list eliminates the whole purpose of the stream (maintaining O(1) memory usage). What you should do here is instead pipe the concat'ed stream into Stream.chunk_every(100 or some other chunk size, and then pipe that to the exporter.

|> Stream.flat_map(&build_cluster_metrics_list(&1, timestamp))
end

defp build_service_metric(%Service{} = service, timestamp) do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly a style nit, but i'd consider moving this metric formatting code to another module

|> DateTime.to_naive()
|> NaiveDateTime.add(60, :second)

next = Crontab.Scheduler.get_next_run_date!(expr, base_time)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd generally make these a with expression with {:ok, _} tuple matches down the line so you don't needlessly crash the genserver on bad input

Copy link
Copy Markdown
Member

@michaeljguarino michaeljguarino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks mostly fine, minus a few nits

defp do_export(endpoint) do
timestamp = DateTime.utc_now()

Repo.transaction(fn ->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't worry about the transaction here, it's read-only so no need to roll back

count
end
end)
|> case do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can probably just make this |> then(&Logger.info("Exported #{&1} metrics to #{enpoint}"). It's not hard to no 0 metrics implies it didn't export

Copy link
Copy Markdown
Member

@michaeljguarino michaeljguarino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one thing that still isn't here is the kubernetes CRD definition for setting these fields. You need to:

  1. Expose the inputs in gql so the client can modify them
  2. Modify the crd type
  3. Modify the controller to wire it up

Oftentimes i can just get ai to do this for me now, but you probably need to familiarize yourself enough with the structure to prompt it well

@kinjalh kinjalh force-pushed the otel-exports branch 2 times, most recently from b4072cd to 85dfc18 Compare April 7, 2026 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants