diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index bd911fe4..05192bb0 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -98,6 +98,15 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: opentelemetry_ecto_test + mariadb: + image: mariadb:11.0 + ports: ['3306:3306'] + options: --health-cmd "healthcheck.sh --connect --innodb_initialized" --health-interval 10s --health-timeout 5s --health-retries 5 + env: + MARIADB_RANDOM_ROOT_PASSWORD: "yes" + MARIADB_USER: mariadb + MARIADB_PASSWORD: mariadb + MARIADB_DATABASE: opentelemetry_ecto_test steps: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 diff --git a/instrumentation/opentelemetry_ecto/config/test.exs b/instrumentation/opentelemetry_ecto/config/test.exs index 7a781d01..3c84a1a8 100644 --- a/instrumentation/opentelemetry_ecto/config/test.exs +++ b/instrumentation/opentelemetry_ecto/config/test.exs @@ -1,14 +1,26 @@ import Config config :opentelemetry_ecto, - ecto_repos: [OpentelemetryEcto.TestRepo] + ecto_repos: [ + OpentelemetryEcto.MariaDBTestRepo, + OpentelemetryEcto.TestRepo + ] config :opentelemetry_ecto, OpentelemetryEcto.TestRepo, hostname: "localhost", username: "postgres", password: "postgres", database: "opentelemetry_ecto_test", - pool: Ecto.Adapters.SQL.Sandbox + pool: Ecto.Adapters.SQL.Sandbox, + telemetry_prefix: [:opentelemetry_ecto, :test_repo] + +config :opentelemetry_ecto, OpentelemetryEcto.MariaDBTestRepo, + hostname: "localhost", + username: "mariadb", + password: "mariadb", + database: "opentelemetry_ecto_test", + pool: Ecto.Adapters.SQL.Sandbox, + telemetry_prefix: [:opentelemetry_ecto, :mariadb_test_repo] config :opentelemetry, processors: [{:otel_batch_processor, %{scheduled_delay_ms: 1}}] diff --git a/instrumentation/opentelemetry_ecto/docker-compose.yml b/instrumentation/opentelemetry_ecto/docker-compose.yml index fc8cfe35..1eecaaf5 100644 --- a/instrumentation/opentelemetry_ecto/docker-compose.yml +++ b/instrumentation/opentelemetry_ecto/docker-compose.yml @@ -9,3 +9,13 @@ services: - POSTGRES_DB=opentelemetry_ecto_test ports: - 5432:5432 + + mariadb: + image: mariadb:11.0 + environment: + - MARIADB_RANDOM_ROOT_PASSWORD=yes + - MARIADB_USER=mariadb + - MARIADB_PASSWORD=mariadb + - MARIADB_DATABASE=opentelemetry_ecto_test + ports: + - 3306:3306 diff --git a/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex index c6f05132..53ff45b9 100644 --- a/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex +++ b/instrumentation/opentelemetry_ecto/lib/opentelemetry_ecto.ex @@ -19,6 +19,61 @@ defmodule OpentelemetryEcto do require OpenTelemetry.Tracer + @db_systems [ + "other_sql", + "mssql", + "mssqlcompact", + "mysql", + "oracle", + "db2", + "postgresql", + "redshift", + "hive", + "cloudscape", + "hsqldb", + "progress", + "maxdb", + "hanadb", + "ingres", + "firstsql", + "edb", + "cache", + "adabas", + "firebird", + "derby", + "filemaker", + "informix", + "instantdb", + "interbase", + "mariadb", + "netezza", + "pervasive", + "pointbase", + "sqlite", + "sybase", + "teradata", + "vertica", + "h2", + "coldfusion", + "cassandra", + "hbase", + "mongodb", + "redis", + "couchbase", + "couchdb", + "cosmosdb", + "dynamodb", + "neo4j", + "geode", + "elasticsearch", + "memcached", + "cockroachdb", + "opensearch", + "clickhouse", + "spanner", + "trino" + ] + @doc """ Attaches the OpentelemetryEcto handler to your repo events. This should be called from your application behaviour on startup. @@ -44,6 +99,10 @@ defmodule OpentelemetryEcto do sanitized version of it. This is useful for removing sensitive information from the query string. Unless this option is `:enabled` or a function, query statements will not be recorded on spans. + * `:db_system` - The identifier for the database management system (DBMS). + defaults to the mapped value of the ecto adapter used. + Must follow the list of well-known db systems from semantic conventions. + See `https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md` """ def setup(event_prefix, config \\ []) do event = event_prefix ++ [:query] @@ -54,7 +113,7 @@ defmodule OpentelemetryEcto do def handle_event( event, measurements, - %{query: query, source: source, result: query_result, repo: repo, type: type}, + %{query: query, source: source, result: query_result, repo: repo}, config ) do # Doing all this even if the span isn't sampled so the sampler @@ -64,16 +123,7 @@ defmodule OpentelemetryEcto do end_time = :opentelemetry.timestamp() start_time = end_time - total_time database = repo.config()[:database] - - url = - case repo.config()[:url] do - nil -> - # TODO: add port - URI.to_string(%URI{scheme: "ecto", host: repo.config()[:hostname]}) - - url -> - url - end + adapter = repo.__adapter__() span_prefix = case Keyword.fetch(config, :span_prefix) do @@ -87,20 +137,13 @@ defmodule OpentelemetryEcto do time_unit = Keyword.get(config, :time_unit, :microsecond) additional_attributes = Keyword.get(config, :additional_attributes, %{}) - db_type = - case type do - :ecto_sql_query -> :sql - _ -> type - end - # TODO: need connection information to complete the required attributes # net.peer.name or net.peer.ip and net.peer.port base_attributes = %{ - "db.type": db_type, - source: source, - "db.instance": database, + "ecto.db.adapter": to_string(adapter), + "db.system": db_system(config[:db_system], adapter), "db.name": database, - "db.url": url, + "db.sql.table": source, "total_time_#{time_unit}s": System.convert_time_unit(total_time, :native, time_unit) } @@ -110,7 +153,6 @@ defmodule OpentelemetryEcto do base_attributes |> add_measurements(measurements, time_unit) |> maybe_add_db_statement(db_statement_config, query) - |> maybe_add_db_system(repo.__adapter__()) |> add_additional_attributes(additional_attributes) parent_context = @@ -189,27 +231,15 @@ defmodule OpentelemetryEcto do attributes end - defp maybe_add_db_system(attributes, Ecto.Adapters.Postgres) do - Map.put(attributes, :"db.system", :postgresql) - end - - defp maybe_add_db_system(attributes, Ecto.Adapters.MyXQL) do - Map.put(attributes, :"db.system", :mysql) - end - - defp maybe_add_db_system(attributes, Ecto.Adapters.SQLite3) do - Map.put(attributes, :"db.system", :sqlite) - end - - defp maybe_add_db_system(attributes, Ecto.Adapters.Tds) do - Map.put(attributes, :"db.system", :mssql) - end - - defp maybe_add_db_system(attributes, _) do - attributes - end - defp add_additional_attributes(attributes, additional_attributes) do Map.merge(attributes, additional_attributes) end + + defp db_system(db_system) when db_system in @db_systems, do: db_system + defp db_system(_), do: "other_sql" + + defp db_system(nil, Ecto.Adapters.Postgres), do: "postgresql" + defp db_system(nil, Ecto.Adapters.MyXQL), do: "mysql" + defp db_system(nil, Ecto.Adapters.Tds), do: "mssql" + defp db_system(db_system, _), do: db_system(db_system) end diff --git a/instrumentation/opentelemetry_ecto/mix.exs b/instrumentation/opentelemetry_ecto/mix.exs index 3dcfec56..23e5cabe 100644 --- a/instrumentation/opentelemetry_ecto/mix.exs +++ b/instrumentation/opentelemetry_ecto/mix.exs @@ -61,6 +61,7 @@ defmodule OpentelemetryEcto.MixProject do {:ex_doc, "~> 0.29", only: [:dev], runtime: false}, {:ecto_sql, ">= 3.0.0", only: [:dev, :test]}, {:postgrex, ">= 0.15.0", only: [:dev, :test]}, + {:myxql, "~> 0.6.0", only: [:dev, :test]}, {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, {:opentelemetry_process_propagator, "~> 0.2"} ] diff --git a/instrumentation/opentelemetry_ecto/mix.lock b/instrumentation/opentelemetry_ecto/mix.lock index 0d6fc2f0..967240f2 100644 --- a/instrumentation/opentelemetry_ecto/mix.lock +++ b/instrumentation/opentelemetry_ecto/mix.lock @@ -16,6 +16,7 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "myxql": {:hex, :myxql, "0.6.1", "ed97d2af9e15c7da7ef7df1a29b2d0d4194444cbb146843715fab9f098c78de2", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.4", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "cc34c3e6d5151d09175c107bd45d99865c26d149e29fc5a78a2f1b799325d76d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "opentelemetry": {:hex, :opentelemetry, "1.3.0", "988ac3c26acac9720a1d4fb8d9dc52e95b45ecfec2d5b5583276a09e8936bc5e", [:rebar3], [{:opentelemetry_api, "~> 1.2.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "8e09edc26aad11161509d7ecad854a3285d88580f93b63b0b1cf0bac332bfcc0"}, "opentelemetry_api": {:hex, :opentelemetry_api, "1.2.1", "7b69ed4f40025c005de0b74fce8c0549625d59cb4df12d15c32fe6dc5076ff42", [:mix, :rebar3], [{:opentelemetry_semantic_conventions, "~> 0.2", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}], "hexpm", "6d7a27b7cad2ad69a09cabf6670514cafcec717c8441beb5c96322bac3d05350"}, diff --git a/instrumentation/opentelemetry_ecto/priv/maria_db_test_repo/migrations/1_setup_tables.exs b/instrumentation/opentelemetry_ecto/priv/maria_db_test_repo/migrations/1_setup_tables.exs new file mode 100644 index 00000000..01f21550 --- /dev/null +++ b/instrumentation/opentelemetry_ecto/priv/maria_db_test_repo/migrations/1_setup_tables.exs @@ -0,0 +1,19 @@ +defmodule OpentelemetryEcto.MariaDBTestRepo.Migrations.SetupTables do + use Ecto.Migration + + def change do + create table(:users) do + add :email, :string + end + + create table(:posts) do + add :body, :text + add :user_id, references(:users) + end + + create table(:comments) do + add :body, :text + add :user_id, references(:users) + end + end +end diff --git a/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs b/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs index 9e35646b..5a5bc8cb 100644 --- a/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs +++ b/instrumentation/opentelemetry_ecto/test/opentelemetry_ecto_test.exs @@ -3,6 +3,7 @@ defmodule OpentelemetryEctoTest do import Ecto.Query require OpenTelemetry.Tracer + alias OpentelemetryEcto.MariaDBTestRepo, as: MariaDBRepo alias OpentelemetryEcto.TestRepo, as: Repo alias OpentelemetryEcto.TestModels.{Comment, User, Post} @@ -49,14 +50,13 @@ defmodule OpentelemetryEctoTest do )} assert %{ - "db.system": :postgresql, - "db.instance": "opentelemetry_ecto_test", - "db.type": :sql, - "db.url": "ecto://localhost", + "db.name": "opentelemetry_ecto_test", + "db.sql.table": "users", + "db.system": "postgresql", + "ecto.db.adapter": "Elixir.Ecto.Adapters.Postgres", decode_time_microseconds: _, query_time_microseconds: _, queue_time_microseconds: _, - source: "users", total_time_microseconds: _ } = :otel_attributes.map(attributes) end @@ -112,14 +112,13 @@ defmodule OpentelemetryEctoTest do )} assert %{ - "db.system": :postgresql, - "db.instance": "opentelemetry_ecto_test", - "db.type": :sql, - "db.url": "ecto://localhost", + "db.name": "opentelemetry_ecto_test", + "db.sql.table": "posts", + "db.system": "postgresql", + "ecto.db.adapter": "Elixir.Ecto.Adapters.Postgres", decode_time_milliseconds: _, query_time_milliseconds: _, queue_time_milliseconds: _, - source: "posts", total_time_milliseconds: _ } = :otel_attributes.map(attributes) end @@ -305,11 +304,45 @@ defmodule OpentelemetryEctoTest do )} end - def attach_handler(config \\ []) do + test "changes the db system" do + attach_handler([db_system: "mariadb"], [:opentelemetry_ecto, :mariadb_test_repo]) + + MariaDBRepo.all(User) + + assert_receive {:span, + span( + name: "opentelemetry_ecto.mariadb_test_repo.query:users", + attributes: attributes + )} + + assert %{ + "ecto.db.adapter": "Elixir.Ecto.Adapters.MyXQL", + "db.system": "mariadb" + } = :otel_attributes.map(attributes) + end + + test "fallbacks to default if it is not a well-known db systems" do + attach_handler([db_system: "maria_db"], [:opentelemetry_ecto, :mariadb_test_repo]) + + MariaDBRepo.all(User) + + assert_receive {:span, + span( + name: "opentelemetry_ecto.mariadb_test_repo.query:users", + attributes: attributes + )} + + assert %{ + "ecto.db.adapter": "Elixir.Ecto.Adapters.MyXQL", + "db.system": "other_sql" + } = :otel_attributes.map(attributes) + end + + def attach_handler(config \\ [], event_name \\ @event_name) do # For now setup the handler manually in each test handler = {__MODULE__, self()} - :telemetry.attach(handler, @event_name ++ [:query], &OpentelemetryEcto.handle_event/4, config) + :telemetry.attach(handler, event_name ++ [:query], &OpentelemetryEcto.handle_event/4, config) on_exit(fn -> :telemetry.detach(handler) diff --git a/instrumentation/opentelemetry_ecto/test/support/mariadb_test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/mariadb_test_repo.ex new file mode 100644 index 00000000..4ba39e77 --- /dev/null +++ b/instrumentation/opentelemetry_ecto/test/support/mariadb_test_repo.ex @@ -0,0 +1,5 @@ +defmodule OpentelemetryEcto.MariaDBTestRepo do + use Ecto.Repo, + otp_app: :opentelemetry_ecto, + adapter: Ecto.Adapters.MyXQL +end diff --git a/instrumentation/opentelemetry_ecto/test/support/test_repo.ex b/instrumentation/opentelemetry_ecto/test/support/test_repo.ex index 74e818be..d6aa9c3a 100644 --- a/instrumentation/opentelemetry_ecto/test/support/test_repo.ex +++ b/instrumentation/opentelemetry_ecto/test/support/test_repo.ex @@ -1,6 +1,5 @@ defmodule OpentelemetryEcto.TestRepo do use Ecto.Repo, otp_app: :opentelemetry_ecto, - adapter: Ecto.Adapters.Postgres, - telemetry_prefix: [:opentelemetry_ecto, :test_repo] + adapter: Ecto.Adapters.Postgres end diff --git a/instrumentation/opentelemetry_ecto/test/test_helper.exs b/instrumentation/opentelemetry_ecto/test/test_helper.exs index 7e95f222..e6ad2744 100644 --- a/instrumentation/opentelemetry_ecto/test/test_helper.exs +++ b/instrumentation/opentelemetry_ecto/test/test_helper.exs @@ -1,5 +1,7 @@ +OpentelemetryEcto.MariaDBTestRepo.start_link() OpentelemetryEcto.TestRepo.start_link() ExUnit.start(capture_log: true) +Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.MariaDBTestRepo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(OpentelemetryEcto.TestRepo, {:shared, self()})