diff --git a/docs/docs.logflare.com/docs/backends/datadog.mdx b/docs/docs.logflare.com/docs/backends/datadog.mdx
index 968f485ab..df7d80930 100644
--- a/docs/docs.logflare.com/docs/backends/datadog.mdx
+++ b/docs/docs.logflare.com/docs/backends/datadog.mdx
@@ -21,4 +21,6 @@ The following values are required when creating a webhook backend:
Implementation is based on the [webhook backend](/backends/webhook).
-Events will be gzipped and batch with a maximum of 250 events per request. The source name will be set as the service name.
+The entire event body will be set to the `data` key of the events sent to Datadog.
+
+Events will be gzipped and batch with a maximum of 250 events per request. The service name of the source will be used as the `ddservice`. If not set, it will default to the source name.
diff --git a/lib/logflare/backends/adaptor/datadog_adaptor.ex b/lib/logflare/backends/adaptor/datadog_adaptor.ex
index 7bf80aa10..ac60a53c4 100644
--- a/lib/logflare/backends/adaptor/datadog_adaptor.ex
+++ b/lib/logflare/backends/adaptor/datadog_adaptor.ex
@@ -76,9 +76,10 @@ defmodule Logflare.Backends.Adaptor.DatadogAdaptor do
%Logflare.LogEvent{
le
| body: %{
- message: formatted_ts <> " " <> Jason.encode!(le.body),
- ddsource: "Logflare by Supabase",
- service: le.source.name
+ "message" => formatted_ts <> " " <> (le.body["message"] || le.body["event_message"]),
+ "ddsource" => "Supabase",
+ "service" => le.source.service_name || le.source.name,
+ "data" => le.body
}
}
end
diff --git a/lib/logflare/sources/source.ex b/lib/logflare/sources/source.ex
index ab4d74159..979b892c9 100644
--- a/lib/logflare/sources/source.ex
+++ b/lib/logflare/sources/source.ex
@@ -12,6 +12,7 @@ defmodule Logflare.Source do
@derive {Jason.Encoder,
only: [
:name,
+ :service_name,
:token,
:id,
:favorite,
@@ -104,6 +105,7 @@ defmodule Logflare.Source do
schema "sources" do
field(:name, :string)
+ field(:service_name, :string)
field(:token, Ecto.UUID.Atom, autogenerate: true)
field(:public_token, :string)
field(:favorite, :boolean, default: false)
@@ -159,6 +161,7 @@ defmodule Logflare.Source do
source
|> cast(attrs, [
:name,
+ :service_name,
:token,
:public_token,
:favorite,
@@ -186,6 +189,7 @@ defmodule Logflare.Source do
source
|> cast(attrs, [
:name,
+ :service_name,
:token,
:public_token,
:favorite,
diff --git a/lib/logflare_web/templates/source/dashboard.html.eex b/lib/logflare_web/templates/source/dashboard.html.eex
deleted file mode 100644
index 07d5fef43..000000000
--- a/lib/logflare_web/templates/source/dashboard.html.eex
+++ /dev/null
@@ -1,196 +0,0 @@
-
-
-
~/logs
-
-
- -
-
- ingest API key
-
CLICK ME
- -
- <%= link to: ~p"/access-tokens" do %>
- access tokens
- <% end %>
-
- <%= if LogflareWeb.Utils.flag("multibackend", @user) do %>
- -
- <%= link to: ~p"/backends" do %>
- backends
- <% end %>
-
- <% end %>
- - <%= link to: Routes.vercel_log_drains_path(@conn, :edit) do %>▲ vercel
- integration<% end %>
- - <%= link to: Routes.billing_account_path(@conn, :edit) do %> billing<% end %>
- - help
-
-
-
-
-
-
-
-
-
-
- <%= if Enum.all?(@sources, &Map.get(&1, :saved_searches) == []), do: "Your saved searches will show up here. Save some searches!" %>
- <%= for source <- @sources do %>
- <%= for saved_search <- source.saved_searches do %>
- -
- <%= link "#{source.name}:#{saved_search.querystring}", to: Routes.live_path(@conn, LogflareWeb.Source.SearchLV, source.id, %{querystring: saved_search.querystring, tailing: saved_search.tailing}), class: "" %>
- <%= link to: Routes.source_saved_searches_path(@conn, :delete, source.id, saved_search), class: "dashboard-links", method: :delete do %>
-
-
- <% end %>
- <% end %>
- <% end %>
-
-
-
- <%= if @home_team do %>
- <%# not a team user %>
- <%= if @team.id == @home_team.id do %>
- - <%= @home_team.name %> home team
- <% else %>
- -
- <%= link @home_team.name, to: Routes.team_user_path(@conn, :change_team, %{"user_id" => @home_team.user_id}) %>
- home team
- <% end %>
- <% else %>
- <%# is a team_user, has no home team yet %>
- -
- <%= link "Create your own Logflare account.", to: Routes.auth_path(@conn, :create_and_sign_in), method: "POST", class: "" %>
-
- <% end %>
-
- <%# Helper text %>
- <%= if Enum.empty?(@team_users), do: "Other teams you are a member of will be listed here." %>
-
- <%# List team users %>
- <%= for team_user <- @team_users do %>
- <%= if @team.id == team_user.team_id do %>
- - <%= team_user.team.name %>
- <% else %>
- -
- <%= link team_user.team.name, to: Routes.team_user_path(@conn, :change_team, %{"user_id" => team_user.team.user_id, "team_user_id" => team_user.id}) %>
-
- <% end %>
- <% end %>
-
-
- <%= render(LogflareWeb.SharedView, "team_members.html", assigns) %>
- <%= link("Invite more team members.", to: Routes.user_path(@conn, :edit) <> "#team-members") %>
-
-
-
-
-
- <%= link "New source", to: Routes.source_path(@conn, :new), class: "btn btn-primary", id: "new-source-button" %>
-
-
-
-
-
-
- -
- <%= link "Cloudflare", to: "https://cloudflareapps.com/apps/logflare" %>
-
-
- -
- <%= link "Vercel", to: "https://docs.logflare.app/integrations/vercel/" %>
-
- -
- <%= link "Fly", to: "https://github.com/Logflare/fly-log-shipper" %>
-
- -
- <%= link "Postgres FDW", to: "https://docs.logflare.app/integrations/postgres-fdw" %>
- SQL
-
- -
- <%= link "pino-logflare", to: "https://docs.logflare.app/integrations/postgres-fdw" %>
- Javascript
-
-
- -
- <%= link "LoggerBackend", to: "https://github.com/Logflare/logflare_logger_backend" %>
- Elixir
-
-
- -
- <%= link "logflare_erl ", to: "https://github.com/Logflare/logflare_erl" %>
- Erlang
-
-
- <%# TODO: change this to the docs site https://docs.logflare.app/category/integrations %>
- <%= link "View all integrations", class: "tw-text-sm", to: "https://github.com/Logflare/logflare#integrations" %>
-
-
-
-
- -
- <%= link "docs.logflare.app", to: "https://docs.logflare.app" %>
-
-
- -
- <%= link "OpenAPI", to: ~p"/swaggerui" %>
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/logflare_web/templates/source/dashboard.html.heex b/lib/logflare_web/templates/source/dashboard.html.heex
new file mode 100644
index 000000000..56a2fa263
--- /dev/null
+++ b/lib/logflare_web/templates/source/dashboard.html.heex
@@ -0,0 +1,194 @@
+
+
+
~/logs
+
+
+ -
+
+ ingest API key
CLICK ME
+
+ -
+ <%= link to: ~p"/access-tokens" do %>
+ access tokens
+ <% end %>
+
+ <%= if LogflareWeb.Utils.flag("multibackend", @user) do %>
+ -
+ <%= link to: ~p"/backends" do %>
+ backends
+ <% end %>
+
+ <% end %>
+ -
+ <%= link to: Routes.vercel_log_drains_path(@conn, :edit) do %>
+ ▲ vercel
+ integration
+ <% end %>
+
+ -
+ <%= link to: Routes.billing_account_path(@conn, :edit) do %>
+ billing
+ <% end %>
+
+ - help
+
+
+
+
+
+
+
+
+
+
+ <%= if Enum.all?(@sources, &(Map.get(&1, :saved_searches) == [])), do: "Your saved searches will show up here. Save some searches!" %>
+ <%= for source <- @sources, saved_search <- source.saved_searches do %>
+ -
+ <%= link("#{source.name}:#{saved_search.querystring}", to: Routes.live_path(@conn, LogflareWeb.Source.SearchLV, source.id, %{querystring: saved_search.querystring, tailing: saved_search.tailing}), class: "") %>
+ <%= link to: Routes.source_saved_searches_path(@conn, :delete, source.id, saved_search), class: "dashboard-links", method: :delete do %>
+
+ <% end %>
+
+ <% end %>
+
+
+
+ <%= if @home_team do %>
+ <%= if @team.id == @home_team.id do %>
+ - <%= @home_team.name %>home team
+ <% else %>
+ -
+ <%= link(@home_team.name, to: Routes.team_user_path(@conn, :change_team, %{"user_id" => @home_team.user_id})) %>
+ home team
+
+ <% end %>
+ <% else %>
+ -
+ <%= link("Create your own Logflare account.", to: Routes.auth_path(@conn, :create_and_sign_in), method: "POST", class: "") %>
+
+ <% end %>
+
+ <%= if Enum.empty?(@team_users), do: "Other teams you are a member of will be listed here." %>
+
+ <%= for team_user <- @team_users do %>
+ <%= if @team.id == team_user.team_id do %>
+ - <%= team_user.team.name %>
+ <% else %>
+ -
+ <%= link(team_user.team.name, to: Routes.team_user_path(@conn, :change_team, %{"user_id" => team_user.team.user_id, "team_user_id" => team_user.id})) %>
+
+ <% end %>
+ <% end %>
+
+
+ <%= render(LogflareWeb.SharedView, "team_members.html", assigns) %>
+ <%= link("Invite more team members.", to: Routes.user_path(@conn, :edit) <> "#team-members") %>
+
+
+
+
+ <.link href={~p"/sources/new"} class="btn btn-primary btn-sm" id="new-source-button">
+ New source
+
+
+
+
+
+
+
+
+ -
+ <%= link("Cloudflare", to: "https://cloudflareapps.com/apps/logflare") %>
+
+
+ -
+ <%= link("Vercel", to: "https://docs.logflare.app/integrations/vercel/") %>
+
+ -
+ <%= link("Fly", to: "https://github.com/Logflare/fly-log-shipper") %>
+
+ -
+ <%= link("Postgres FDW", to: "https://docs.logflare.app/integrations/postgres-fdw") %>
+ SQL
+
+ -
+ <%= link("pino-logflare", to: "https://docs.logflare.app/integrations/postgres-fdw") %>
+ Javascript
+
+
+ -
+ <%= link("LoggerBackend", to: "https://github.com/Logflare/logflare_logger_backend") %>
+ Elixir
+
+
+ -
+ <%= link("logflare_erl ", to: "https://github.com/Logflare/logflare_erl") %>
+ Erlang
+
+
+ <%= link("View all integrations", class: "tw-text-sm", to: "https://github.com/Logflare/logflare#integrations") %>
+
+
+
+ -
+ <%= link("docs.logflare.app", to: "https://docs.logflare.app") %>
+
+
+ -
+ <%= link("OpenAPI", to: ~p"/swaggerui") %>
+
+
+
+
+
+
+
+
diff --git a/lib/logflare_web/templates/source/edit.html.eex b/lib/logflare_web/templates/source/edit.html.eex
index e107860ad..78e541e41 100644
--- a/lib/logflare_web/templates/source/edit.html.eex
+++ b/lib/logflare_web/templates/source/edit.html.eex
@@ -24,6 +24,17 @@
<%= submit "Update name", class: "btn btn-primary form-button" %>
<% end %>
+
+ <%= section_header("Service Group") %>
+ Organize your sources into a common business domain service group.
+ <%= form_for @changeset, Routes.source_path(@conn, :update, @source), fn a -> %>
+
+ <%= text_input a, :service_name, placeholder: "Payments", class: "form-control form-control-margin" %>
+ <%= error_tag a, :service_name %>
+
+ <%= submit "Update service group", class: "btn btn-primary form-button" %>
+ <% end %>
+
<%= section_header("Alert Frequency") %>
How often do you want to receive alerts?
diff --git a/priv/repo/migrations/20240806105357_add_service_group_column_to_source_table.exs b/priv/repo/migrations/20240806105357_add_service_group_column_to_source_table.exs
new file mode 100644
index 000000000..7e430e5da
--- /dev/null
+++ b/priv/repo/migrations/20240806105357_add_service_group_column_to_source_table.exs
@@ -0,0 +1,10 @@
+defmodule Logflare.Repo.Migrations.AddServiceGroupColumnToSourceTable do
+ use Ecto.Migration
+
+ def change do
+
+ alter table(:sources) do
+ add :service_name, :string
+ end
+ end
+end
diff --git a/test/logflare/backends/adaptor/datadog_adaptor_test.exs b/test/logflare/backends/adaptor/datadog_adaptor_test.exs
index 2869914fd..622dda9d9 100644
--- a/test/logflare/backends/adaptor/datadog_adaptor_test.exs
+++ b/test/logflare/backends/adaptor/datadog_adaptor_test.exs
@@ -66,7 +66,24 @@ defmodule Logflare.Backends.Adaptor.DatadogAdaptorTest do
assert_receive ^ref, 2000
end
- test "service field is set to source name", %{source: source} do
+ test "service field is set to source.service_name", %{source: source} do
+ this = self()
+ ref = make_ref()
+
+ @client
+ |> expect(:send, fn req ->
+ send(this, {ref, req[:body]})
+ %Tesla.Env{status: 200, body: ""}
+ end)
+
+ le = build(:log_event, source: %{source | service_name: "some name"})
+
+ assert {:ok, _} = Backends.ingest_logs([le], source)
+ assert_receive {^ref, [log_entry]}, 2000
+ assert log_entry["service"] == "some name"
+ end
+
+ test "service field falls back to source name", %{source: source} do
this = self()
ref = make_ref()
@@ -80,10 +97,10 @@ defmodule Logflare.Backends.Adaptor.DatadogAdaptorTest do
assert {:ok, _} = Backends.ingest_logs([le], source)
assert_receive {^ref, [log_entry]}, 2000
- assert log_entry.service == source.name
+ assert log_entry["service"] == source.name
end
- test "message is JSON encoded log event", %{source: source} do
+ test "message contains log event body", %{source: source} do
this = self()
ref = make_ref()
@@ -97,7 +114,7 @@ defmodule Logflare.Backends.Adaptor.DatadogAdaptorTest do
assert {:ok, _} = Backends.ingest_logs([le], source)
assert_receive {^ref, [log_entry]}, 2000
- assert log_entry.message =~ Jason.encode!(le.body)
+ assert log_entry["data"] == le.body
end
end
end