diff --git a/.gitignore b/.gitignore index e8c36c132..d307ccd2d 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,7 @@ gcloud_prod.json # google keys .*.gcloud.json -supabase \ No newline at end of file +supabase + +examples/**/.dev.vars +examples/**/node_modules \ No newline at end of file diff --git a/VERSION b/VERSION index 0203dc6b9..58084a6aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.10.9 \ No newline at end of file +1.10.10 \ No newline at end of file diff --git a/docs/docs.logflare.com/docs/integrations/open-telemetry/index.mdx b/docs/docs.logflare.com/docs/integrations/open-telemetry/index.mdx index 0fc88bff1..3c6a1b8d0 100644 --- a/docs/docs.logflare.com/docs/integrations/open-telemetry/index.mdx +++ b/docs/docs.logflare.com/docs/integrations/open-telemetry/index.mdx @@ -49,7 +49,7 @@ config :opentelemetry, config :opentelemetry_exporter, otlp_protocol: :grpc, - otlp_compression: :gzip, + otlp_compression: :gzip, otlp_endpoint: "https://otel.logflare.app:443", otlp_headers: [ {"x-source", "my-source-uuid"}, @@ -63,5 +63,4 @@ config :opentelemetry_exporter, ### Limitations - Only **traces** are currently supported. -- Only GRPC as the transport protocol is currently supported. HTTP Protobuf is not yet supported. - Gzip compression is required. diff --git a/lib/logflare/utils/map.ex b/lib/logflare/utils/map.ex new file mode 100644 index 000000000..2a584e77b --- /dev/null +++ b/lib/logflare/utils/map.ex @@ -0,0 +1,14 @@ +defmodule Logflare.Utils.Map do + @doc """ + Retrieves a key, regardless of whether it is a string map or an atom map + + iex> #{__MODULE__}.get(%{test: 123}, :test) + 123 + iex> #{__MODULE__}.get(%{"test"=> 123}, :test) + 123 + """ + @spec get(map :: map(), key :: atom()) :: term() | nil + def get(map, key) when is_atom(key) do + Map.get(map, key) || Map.get(map, Atom.to_string(key)) + end +end diff --git a/lib/logflare_grpc/endpoint.ex b/lib/logflare_grpc/endpoint.ex index ffddd0f3b..44d21560f 100644 --- a/lib/logflare_grpc/endpoint.ex +++ b/lib/logflare_grpc/endpoint.ex @@ -1,5 +1,7 @@ defmodule LogflareGrpc.Endpoint do use GRPC.Endpoint + intercept(GRPC.Server.Interceptors.Logger, level: :debug) + intercept LogflareGrpc.HttpProtobufInterceptor run(LogflareGrpc.Trace.Server) end diff --git a/lib/logflare_grpc/http_protobuf_interceptor.ex b/lib/logflare_grpc/http_protobuf_interceptor.ex new file mode 100644 index 000000000..19f532487 --- /dev/null +++ b/lib/logflare_grpc/http_protobuf_interceptor.ex @@ -0,0 +1,14 @@ +defmodule LogflareGrpc.HttpProtobufInterceptor do + @moduledoc false + + @behaviour GRPC.Server.Interceptor + + def init(opts) do + opts + end + + def call(rpc_req, stream, next, opts) do + dbg({rpc_req, stream, next, opts}) + {:ok, stream} + end +end diff --git a/lib/logflare_grpc/opentelemetry/proto/logs/v1/logs.pb.ex b/lib/logflare_grpc/opentelemetry/proto/logs/v1/logs.pb.ex index 3034d7d04..5280b12d4 100644 --- a/lib/logflare_grpc/opentelemetry/proto/logs/v1/logs.pb.ex +++ b/lib/logflare_grpc/opentelemetry/proto/logs/v1/logs.pb.ex @@ -100,4 +100,5 @@ defmodule Opentelemetry.Proto.Logs.V1.LogRecord do field :flags, 8, type: :fixed32 field :trace_id, 9, type: :bytes, json_name: "traceId" field :span_id, 10, type: :bytes, json_name: "spanId" + field :event_name, 12, type: :string, json_name: "eventName" end diff --git a/lib/logflare_grpc/opentelemetry/proto/profiles/v1development/profiles.pb.ex b/lib/logflare_grpc/opentelemetry/proto/profiles/v1development/profiles.pb.ex index 8988affe4..fb7be6ed3 100644 --- a/lib/logflare_grpc/opentelemetry/proto/profiles/v1development/profiles.pb.ex +++ b/lib/logflare_grpc/opentelemetry/proto/profiles/v1development/profiles.pb.ex @@ -66,46 +66,44 @@ defmodule Opentelemetry.Proto.Profiles.V1development.Profile do type: Opentelemetry.Proto.Profiles.V1development.Location, json_name: "locationTable" - field :location_indices, 15, repeated: true, type: :int32, json_name: "locationIndices" + field :location_indices, 5, repeated: true, type: :int32, json_name: "locationIndices" - field :function_table, 5, + field :function_table, 6, repeated: true, type: Opentelemetry.Proto.Profiles.V1development.Function, json_name: "functionTable" - field :attribute_table, 16, + field :attribute_table, 7, repeated: true, type: Opentelemetry.Proto.Common.V1.KeyValue, json_name: "attributeTable" - field :attribute_units, 17, + field :attribute_units, 8, repeated: true, type: Opentelemetry.Proto.Profiles.V1development.AttributeUnit, json_name: "attributeUnits" - field :link_table, 18, + field :link_table, 9, repeated: true, type: Opentelemetry.Proto.Profiles.V1development.Link, json_name: "linkTable" - field :string_table, 6, repeated: true, type: :string, json_name: "stringTable" - field :drop_frames_strindex, 7, type: :int32, json_name: "dropFramesStrindex" - field :keep_frames_strindex, 8, type: :int32, json_name: "keepFramesStrindex" - field :time_nanos, 9, type: :int64, json_name: "timeNanos" - field :duration_nanos, 10, type: :int64, json_name: "durationNanos" + field :string_table, 10, repeated: true, type: :string, json_name: "stringTable" + field :time_nanos, 11, type: :int64, json_name: "timeNanos" + field :duration_nanos, 12, type: :int64, json_name: "durationNanos" - field :period_type, 11, + field :period_type, 13, type: Opentelemetry.Proto.Profiles.V1development.ValueType, json_name: "periodType" - field :period, 12, type: :int64 - field :comment_strindices, 13, repeated: true, type: :int32, json_name: "commentStrindices" - field :default_sample_type_strindex, 14, type: :int32, json_name: "defaultSampleTypeStrindex" - field :profile_id, 19, type: :bytes, json_name: "profileId" - field :attributes, 20, repeated: true, type: Opentelemetry.Proto.Common.V1.KeyValue - field :dropped_attributes_count, 21, type: :uint32, json_name: "droppedAttributesCount" - field :original_payload_format, 22, type: :string, json_name: "originalPayloadFormat" - field :original_payload, 23, type: :bytes, json_name: "originalPayload" + field :period, 14, type: :int64 + field :comment_strindices, 15, repeated: true, type: :int32, json_name: "commentStrindices" + field :default_sample_type_strindex, 16, type: :int32, json_name: "defaultSampleTypeStrindex" + field :profile_id, 17, type: :bytes, json_name: "profileId" + field :dropped_attributes_count, 19, type: :uint32, json_name: "droppedAttributesCount" + field :original_payload_format, 20, type: :string, json_name: "originalPayloadFormat" + field :original_payload, 21, type: :bytes, json_name: "originalPayload" + field :attribute_indices, 22, repeated: true, type: :int32, json_name: "attributeIndices" end defmodule Opentelemetry.Proto.Profiles.V1development.AttributeUnit do @@ -145,24 +143,12 @@ defmodule Opentelemetry.Proto.Profiles.V1development.Sample do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" - field :location_index, 1, repeated: true, type: :int32, json_name: "locationIndex" - field :locations_start_index, 7, type: :int32, json_name: "locationsStartIndex" - field :locations_length, 8, type: :int32, json_name: "locationsLength" - field :value, 2, repeated: true, type: :int64 - field :attribute_indices, 10, repeated: true, type: :int32, json_name: "attributeIndices" - field :link_index, 12, type: :int32, json_name: "linkIndex" - field :timestamps_unix_nano, 13, repeated: true, type: :uint64, json_name: "timestampsUnixNano" -end - -defmodule Opentelemetry.Proto.Profiles.V1development.Label do - @moduledoc false - - use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" - - field :key_strindex, 1, type: :int32, json_name: "keyStrindex" - field :str_strindex, 2, type: :int32, json_name: "strStrindex" - field :num, 3, type: :int64 - field :num_unit_strindex, 4, type: :int32, json_name: "numUnitStrindex" + field :locations_start_index, 1, type: :int32, json_name: "locationsStartIndex" + field :locations_length, 2, type: :int32, json_name: "locationsLength" + field :value, 3, repeated: true, type: :int64 + field :attribute_indices, 4, repeated: true, type: :int32, json_name: "attributeIndices" + field :link_index, 5, proto3_optional: true, type: :int32, json_name: "linkIndex" + field :timestamps_unix_nano, 6, repeated: true, type: :uint64, json_name: "timestampsUnixNano" end defmodule Opentelemetry.Proto.Profiles.V1development.Mapping do @@ -170,16 +156,15 @@ defmodule Opentelemetry.Proto.Profiles.V1development.Mapping do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" - field :id, 1, type: :uint64 - field :memory_start, 2, type: :uint64, json_name: "memoryStart" - field :memory_limit, 3, type: :uint64, json_name: "memoryLimit" - field :file_offset, 4, type: :uint64, json_name: "fileOffset" - field :filename_strindex, 5, type: :int32, json_name: "filenameStrindex" - field :attribute_indices, 12, repeated: true, type: :int32, json_name: "attributeIndices" - field :has_functions, 7, type: :bool, json_name: "hasFunctions" - field :has_filenames, 8, type: :bool, json_name: "hasFilenames" - field :has_line_numbers, 9, type: :bool, json_name: "hasLineNumbers" - field :has_inline_frames, 10, type: :bool, json_name: "hasInlineFrames" + field :memory_start, 1, type: :uint64, json_name: "memoryStart" + field :memory_limit, 2, type: :uint64, json_name: "memoryLimit" + field :file_offset, 3, type: :uint64, json_name: "fileOffset" + field :filename_strindex, 4, type: :int32, json_name: "filenameStrindex" + field :attribute_indices, 5, repeated: true, type: :int32, json_name: "attributeIndices" + field :has_functions, 6, type: :bool, json_name: "hasFunctions" + field :has_filenames, 7, type: :bool, json_name: "hasFilenames" + field :has_line_numbers, 8, type: :bool, json_name: "hasLineNumbers" + field :has_inline_frames, 9, type: :bool, json_name: "hasInlineFrames" end defmodule Opentelemetry.Proto.Profiles.V1development.Location do @@ -187,12 +172,11 @@ defmodule Opentelemetry.Proto.Profiles.V1development.Location do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" - field :id, 1, type: :uint64 - field :mapping_index, 2, type: :int32, json_name: "mappingIndex" - field :address, 3, type: :uint64 - field :line, 4, repeated: true, type: Opentelemetry.Proto.Profiles.V1development.Line - field :is_folded, 5, type: :bool, json_name: "isFolded" - field :attribute_indices, 7, repeated: true, type: :int32, json_name: "attributeIndices" + field :mapping_index, 1, proto3_optional: true, type: :int32, json_name: "mappingIndex" + field :address, 2, type: :uint64 + field :line, 3, repeated: true, type: Opentelemetry.Proto.Profiles.V1development.Line + field :is_folded, 4, type: :bool, json_name: "isFolded" + field :attribute_indices, 5, repeated: true, type: :int32, json_name: "attributeIndices" end defmodule Opentelemetry.Proto.Profiles.V1development.Line do @@ -210,9 +194,8 @@ defmodule Opentelemetry.Proto.Profiles.V1development.Function do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" - field :id, 1, type: :uint64 - field :name_strindex, 2, type: :int32, json_name: "nameStrindex" - field :system_name_strindex, 3, type: :int32, json_name: "systemNameStrindex" - field :filename_strindex, 4, type: :int32, json_name: "filenameStrindex" - field :start_line, 5, type: :int64, json_name: "startLine" + field :name_strindex, 1, type: :int32, json_name: "nameStrindex" + field :system_name_strindex, 2, type: :int32, json_name: "systemNameStrindex" + field :filename_strindex, 3, type: :int32, json_name: "filenameStrindex" + field :start_line, 4, type: :int64, json_name: "startLine" end diff --git a/lib/logflare_grpc/trace/server.ex b/lib/logflare_grpc/trace/server.ex index d3cd186a3..5b675c16d 100644 --- a/lib/logflare_grpc/trace/server.ex +++ b/lib/logflare_grpc/trace/server.ex @@ -15,7 +15,8 @@ defmodule LogflareGrpc.Trace.Server do use GRPC.Server, service: Opentelemetry.Proto.Collector.Trace.V1.TraceService.Service, - compressors: [GRPC.Compressor.Gzip] + # compressors: [GRPC.Compressor.Gzip], + http_transcode: true require Logger @@ -47,10 +48,8 @@ defmodule LogflareGrpc.Trace.Server do defp get_source_token(stream) do case GRPC.Stream.get_headers(stream) do - %{"x-source-token" => token} -> {:ok, token} - %{"x-source-id" => token} -> {:ok, token} + %{"x-collection" => token} -> {:ok, token} %{"x-source" => token} -> {:ok, token} - %{"x-source-uuid" => token} -> {:ok, token} _ -> {:error, :unauthorized} end end diff --git a/lib/logflare_web/controllers/log_controller.ex b/lib/logflare_web/controllers/log_controller.ex index 873bd5b0f..f4131991d 100644 --- a/lib/logflare_web/controllers/log_controller.ex +++ b/lib/logflare_web/controllers/log_controller.ex @@ -2,7 +2,9 @@ defmodule LogflareWeb.LogController do use LogflareWeb, :controller use OpenApiSpex.ControllerSpecs + alias Logflare.Logs alias Logflare.Logs.Processor + alias Opentelemetry.Proto.Collector.Trace.V1.ExportTraceServiceRequest alias LogflareWeb.OpenApi.Created alias LogflareWeb.OpenApi.ServerError @@ -29,8 +31,6 @@ defmodule LogflareWeb.LogController do when action in [:browser_reports, :generic_json, :create] ) - alias Logflare.Logs - @message "Logged!" operation(:create, @@ -224,4 +224,12 @@ defmodule LogflareWeb.LogController do {String.replace(header, "-", "_"), data} end end + + def otel_traces( + %{assigns: %{source: source}} = conn, + %ExportTraceServiceRequest{resource_spans: spans} + ) do + Processor.ingest(spans, Logs.OtelTrace, source) + |> handle(conn) + end end diff --git a/lib/logflare_web/controllers/plugs/fetch_resource.ex b/lib/logflare_web/controllers/plugs/fetch_resource.ex index e544a624a..2c0842e4e 100644 --- a/lib/logflare_web/controllers/plugs/fetch_resource.ex +++ b/lib/logflare_web/controllers/plugs/fetch_resource.ex @@ -12,26 +12,9 @@ defmodule LogflareWeb.Plugs.FetchResource do import Plug.Conn alias Logflare.Sources alias Logflare.Endpoints + alias Logflare.Utils def init(_opts), do: nil - # ingest by source token - def call(%{assigns: %{resource_type: :source}, params: params} = conn, _opts) - when is_map_key(params, "source") or is_map_key(params, "collection") do - token = params["source"] || params["collection"] - - source = - case is_uuid?(token) do - true -> - Sources.Cache.get_by_and_preload_rules(token: token) - |> Sources.refresh_source_metrics_for_ingest() - - _ -> - nil - end - - assign(conn, :source, source) - end - # ingest by source name def call( %{assigns: %{user: user, resource_type: :source}, params: params} = @@ -48,6 +31,25 @@ defmodule LogflareWeb.Plugs.FetchResource do assign(conn, :source, source) end + # ingest by source token + def call(%{assigns: %{resource_type: :source}, params: params} = conn, _opts) do + token = + Utils.Map.get(params, :source) || Utils.Map.get(params, :collection) || + get_source_from_headers(conn) + + source = + case is_uuid?(token) do + true -> + Sources.Cache.get_by_and_preload_rules(token: token) + |> Sources.refresh_source_metrics_for_ingest() + + _ -> + nil + end + + assign(conn, :source, source) + end + def call( %{ assigns: %{resource_type: :endpoint} = assigns, @@ -95,4 +97,15 @@ defmodule LogflareWeb.Plugs.FetchResource do _ -> false end end + + defp is_uuid?(_), do: false + + def get_source_from_headers(conn) do + (Plug.Conn.get_req_header(conn, "x-source") || + Plug.Conn.get_req_header(conn, "x-collection")) + |> case do + [value] -> value + _ -> nil + end + end end diff --git a/lib/logflare_web/controllers/plugs/protobuf_parser.ex b/lib/logflare_web/controllers/plugs/protobuf_parser.ex new file mode 100644 index 000000000..ce39aa899 --- /dev/null +++ b/lib/logflare_web/controllers/plugs/protobuf_parser.ex @@ -0,0 +1,54 @@ +defmodule LogflareWeb.OtelProtobufParser do + @moduledoc """ + Parses BERT (http://bert-rpc.org) request body + """ + + alias Opentelemetry.Proto.Collector.Trace.V1.ExportTraceServiceRequest + @behaviour Plug.Parsers + + def init(opts) do + {body_reader, opts} = Keyword.pop(opts, :body_reader, {Plug.Conn, :read_body, []}) + {body_reader, opts} + end + + def parse(conn, "application", "x-protobuf", _headers, {{mod, fun, args}, _opts}) do + conn + |> then(&apply(mod, fun, [&1 | args])) + |> decode() + end + + @doc false + def parse(conn, _type, _subtype, _headers, _opts) do + {:next, conn} + end + + def decode({:ok, <<>>, conn}) do + {:ok, %{}, conn} + end + + def decode({:ok, body, conn}) do + {:ok, ExportTraceServiceRequest.decode(body), conn} + rescue + e -> + reraise Plug.Parsers.ParseError, [exception: e], __STACKTRACE__ + end + + def decode({:more, _, conn}) do + {:error, :too_large, conn} + end + + def decode({:error, :timeout}) do + raise Plug.TimeoutError + end + + def decode({:error, _}) do + raise Plug.BadRequestError + end + + def atoms() do + # fixes a bug in Bertex where Bertex.safe_decode errors because + # :bert and :dict atoms returned by :binary_to_term do not exist and are treated + # as coming from the binary + [:bert, :dict] + end +end diff --git a/lib/logflare_web/controllers/plugs/verify_api_access.ex b/lib/logflare_web/controllers/plugs/verify_api_access.ex index c5212601a..af3d12432 100644 --- a/lib/logflare_web/controllers/plugs/verify_api_access.ex +++ b/lib/logflare_web/controllers/plugs/verify_api_access.ex @@ -15,6 +15,7 @@ defmodule LogflareWeb.Plugs.VerifyApiAccess do alias Logflare.Partners.Partner alias Logflare.Partners alias LogflareWeb.Api.FallbackController + alias Logflare.Utils def init(args), do: args |> Enum.into(%{}) @@ -101,7 +102,7 @@ defmodule LogflareWeb.Plugs.VerifyApiAccess do api_key = conn |> Plug.Conn.get_req_header("x-api-key") - |> List.first(conn.params["api_key"]) + |> List.first(Utils.Map.get(conn.params, :api_key)) cond do bearer != nil -> {:ok, bearer} diff --git a/lib/logflare_web/router.ex b/lib/logflare_web/router.ex index 77cab34db..d72ad3d8e 100644 --- a/lib/logflare_web/router.ex +++ b/lib/logflare_web/router.ex @@ -10,6 +10,7 @@ defmodule LogflareWeb.Router do alias LogflareWeb.JsonParser alias LogflareWeb.SyslogParser alias LogflareWeb.NdjsonParser + alias LogflareWeb.OtelProtobufParser # TODO: move plug calls in SourceController and RuleController into here @@ -57,7 +58,7 @@ defmodule LogflareWeb.Router do plug(LogflareWeb.Plugs.MaybeContentTypeToJson) plug(Plug.Parsers, - parsers: [JsonParser, BertParser, SyslogParser, NdjsonParser], + parsers: [JsonParser, BertParser, SyslogParser, NdjsonParser, OtelProtobufParser], json_decoder: Jason, body_reader: {PlugCaisson, :read_body, []}, length: 12_000_000 @@ -457,6 +458,11 @@ defmodule LogflareWeb.Router do get("/:token_or_name", EndpointsController, :query) end + scope "/v1", LogflareWeb, assigns: %{resource_type: :source} do + pipe_through([:api, :require_ingest_api_auth]) + post("/traces", LogController, :otel_traces) + end + for path <- ["/logs", "/api/logs", "/api/events"] do scope path, LogflareWeb, assigns: %{resource_type: :source} do pipe_through([:api, :require_ingest_api_auth]) diff --git a/test/logflare/utils_test.exs b/test/logflare/utils_test.exs index 101309bb7..f2dcbd05c 100644 --- a/test/logflare/utils_test.exs +++ b/test/logflare/utils_test.exs @@ -5,4 +5,5 @@ defmodule Logflare.UtilsTest do import Logflare.EnumDeepUpdate doctest Logflare.EnumDeepUpdate doctest Logflare.Utils + doctest Logflare.Utils.Map end diff --git a/test/logflare_grpc/trace/server_test.exs b/test/logflare_grpc/trace/server_test.exs index bc871e0d3..548fabc85 100644 --- a/test/logflare_grpc/trace/server_test.exs +++ b/test/logflare_grpc/trace/server_test.exs @@ -36,7 +36,7 @@ defmodule LogflareGrpc.Trace.ServerTest do user: user, port: port } do - headers = [{"x-api-key", user.api_key}, {"x-source-id", source.token}] + headers = [{"x-api-key", user.api_key}, {"x-source", source.token}] {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", @@ -52,7 +52,7 @@ defmodule LogflareGrpc.Trace.ServerTest do end test "returns an error if invalid api key", %{source: source, port: port} do - headers = [{"x-api-key", "potato"}, {"x-source-id", source.token}] + headers = [{"x-api-key", "potato"}, {"x-source", source.token}] {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", @@ -71,7 +71,7 @@ defmodule LogflareGrpc.Trace.ServerTest do user: user, port: port } do - headers = [{"x-api-key", user.api_key}, {"x-source-id", "potato"}] + headers = [{"x-api-key", user.api_key}, {"x-source", "potato"}] {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", @@ -87,7 +87,7 @@ defmodule LogflareGrpc.Trace.ServerTest do end test "returns an error if missing x-api-key header", %{source: source, port: port} do - headers = [{"x-source-id", source.token}] + headers = [{"x-source", source.token}] {:ok, channel} = GRPC.Stub.connect("localhost:#{port}", @@ -102,7 +102,7 @@ defmodule LogflareGrpc.Trace.ServerTest do assert %GRPC.RPCError{message: "Invalid API Key or Source ID"} = result end - test "returns an error if missing x-source-id header", %{ + test "returns an error if missing x-source header", %{ user: user, port: port } do diff --git a/test/logflare_web/controllers/log_controller_test.exs b/test/logflare_web/controllers/log_controller_test.exs index d87387206..e1cf1749b 100644 --- a/test/logflare_web/controllers/log_controller_test.exs +++ b/test/logflare_web/controllers/log_controller_test.exs @@ -7,6 +7,7 @@ defmodule LogflareWeb.LogControllerTest do alias Logflare.Source.V1SourceSup alias Logflare.Sources alias Logflare.SystemMetrics.AllLogsLogged + alias Opentelemetry.Proto.Collector.Trace.V1.ExportTraceServiceRequest @valid %{"some" => "valid log entry", "event_message" => "hi!"} @valid_json Jason.encode!(@valid) @@ -123,6 +124,33 @@ defmodule LogflareWeb.LogControllerTest do log end + test ":otel_traces ingestion", %{conn: conn, source: source, user: user} do + this = self() + ref = make_ref() + + WebhookAdaptor.Client + |> expect(:send, fn req -> + send(this, {ref, req[:body]}) + %Tesla.Env{status: 200, body: ""} + end) + + body = TestUtilsGrpc.random_export_service_request() |> ExportTraceServiceRequest.encode() + + conn = + conn + |> put_req_header("x-api-key", user.api_key) + |> put_req_header("x-source", Atom.to_string(source.token)) + |> put_req_header("content-type", "application/x-protobuf") + |> post(Routes.log_path(conn, :otel_traces), body) + + assert json_response(conn, 200) == %{"message" => "Logged!"} + assert_receive {^ref, [event1, event2]}, 3000 + + assert event1["trace_id"] == event2["trace_id"] + assert %{"metadata" => _, "event_message" => _} = event1 + assert %{"metadata" => _, "event_message" => _} = event2 + end + test "invaild source token uuid checks", %{conn: conn, user: user} do conn = conn diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 58f1be4f1..6556eeab9 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -37,6 +37,7 @@ defmodule LogflareWeb.ConnCase do import unquote(__MODULE__) alias Logflare.TestUtils + alias Logflare.TestUtilsGrpc alias Logflare.User alias Logflare.Partners.Partner alias LogflareWeb.Router.Helpers, as: Routes