diff --git a/Mocha.sln b/Mocha.sln index 7acd0a3..2513889 100644 --- a/Mocha.sln +++ b/Mocha.sln @@ -32,7 +32,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Core.Benchmarks", "te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Storage.Tests", "tests\Mocha.Storage.Tests\Mocha.Storage.Tests.csproj", "{FC0D810E-4ACC-4567-95D8-D7F617E412FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Query.Jaeger", "src\Mocha.Query.Jaeger\Mocha.Query.Jaeger.csproj", "{DC281C3B-455F-4391-92EF-D5D99FC2B9AA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mocha.Query", "src\Mocha.Query\Mocha.Query.csproj", "{DC281C3B-455F-4391-92EF-D5D99FC2B9AA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{53AF2923-4CB8-44C8-885B-B0EEB8574FEB}" EndProject @@ -51,9 +51,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "distributor", "distributor" docker\distributor\Dockerfile = docker\distributor\Dockerfile EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jaeger-query", "jaeger-query", "{C7222A9C-C50C-4FF0-A02D-778A9BB4DD2C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "query", "query", "{C7222A9C-C50C-4FF0-A02D-778A9BB4DD2C}" ProjectSection(SolutionItems) = preProject - docker\jaeger-query\Dockerfile = docker\jaeger-query\Dockerfile + docker\jaeger-query\Dockerfile = docker\query\Dockerfile EndProjectSection EndProject Global diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 70dba7f..f685bd3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -55,11 +55,11 @@ services: networks: - mocha - jaeger-query: + query: build: context: .. - dockerfile: ./docker/jaeger-query/Dockerfile - container_name: mocha-jaeger-query + dockerfile: ./docker/query/Dockerfile + container_name: mocha-query ports: - "5775:5775" expose: diff --git a/docker/jaeger-query/Dockerfile b/docker/query/Dockerfile similarity index 55% rename from docker/jaeger-query/Dockerfile rename to docker/query/Dockerfile index 38596d1..35f7af2 100644 --- a/docker/jaeger-query/Dockerfile +++ b/docker/query/Dockerfile @@ -5,20 +5,20 @@ EXPOSE 8080 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["src/Mocha.Query.Jaeger/Mocha.Query.Jaeger.csproj", "src/Mocha.Query.Jaeger/"] +COPY ["src/Mocha.Query/Mocha.Query.csproj", "src/Mocha.Query/"] COPY ["src/Mocha.Core/Mocha.Core.csproj", "src/Mocha.Core/"] COPY ["src/Mocha.Protocol.Generated/Mocha.Protocol.Generated.csproj", "src/Mocha.Protocol.Generated/"] COPY ["src/Mocha.Storage/Mocha.Storage.csproj", "src/Mocha.Storage/"] -RUN dotnet restore "src/Mocha.Query.Jaeger/Mocha.Query.Jaeger.csproj" +RUN dotnet restore "src/Mocha.Query/Mocha.Query.csproj" COPY . . -WORKDIR "/src/src/Mocha.Query.Jaeger" -RUN dotnet build "Mocha.Query.Jaeger.csproj" -c $BUILD_CONFIGURATION -o /app/build +WORKDIR "/src/src/Mocha.Query" +RUN dotnet build "Mocha.Query.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "Mocha.Query.Jaeger.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "Mocha.Query.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Mocha.Query.Jaeger.dll"] +ENTRYPOINT ["dotnet", "Mocha.Query.dll"] diff --git a/docs/quick-start/docker-compose/asserts/add-jaeger-data-source-4.png b/docs/quick-start/docker-compose/asserts/add-jaeger-data-source-4.png index 3c10bca..b0d0359 100644 Binary files a/docs/quick-start/docker-compose/asserts/add-jaeger-data-source-4.png and b/docs/quick-start/docker-compose/asserts/add-jaeger-data-source-4.png differ diff --git a/docs/quick-start/docker-compose/quick-start.en-US.md b/docs/quick-start/docker-compose/quick-start.en-US.md index a2cc02b..302208a 100644 --- a/docs/quick-start/docker-compose/quick-start.en-US.md +++ b/docs/quick-start/docker-compose/quick-start.en-US.md @@ -32,7 +32,7 @@ After logging in, click the menu on the left, select Data Sources, and then clic Select Jaeger. ![](./asserts/add-jaeger-data-source-3.png) -Configure the URL of the Jaeger data source as `http://jaeger-query:5775`. +Configure the URL of the Jaeger data source as `http://query:5775/jaeger`. ![](./asserts/add-jaeger-data-source-4.png) @@ -50,4 +50,4 @@ Click the menu on the left, select Explore, and then select the Jaeger data sour ![](./asserts/query-trace.png) -![](./asserts/query-trace-2.png) \ No newline at end of file +![](./asserts/query-trace-2.png) diff --git a/docs/quick-start/docker-compose/quick-start.zh-CN.md b/docs/quick-start/docker-compose/quick-start.zh-CN.md index 769db3d..e2305e8 100644 --- a/docs/quick-start/docker-compose/quick-start.zh-CN.md +++ b/docs/quick-start/docker-compose/quick-start.zh-CN.md @@ -32,7 +32,7 @@ docker-compose up -d 选择 Jaeger。 ![](./asserts/add-jaeger-data-source-3.png) -配置 Jaeger 数据源的 URL 为 `http://jaeger-query:5775`。 +配置 Jaeger 数据源的 URL 为 `http://query:5775/jaeger`。 ![](./asserts/add-jaeger-data-source-4.png) 点击 Save & Test,如果显示如下信息,则说明配置成功。 diff --git a/src/Mocha.Query.Jaeger/Controllers/TraceController.cs b/src/Mocha.Query.Jaeger/Controllers/TraceController.cs deleted file mode 100644 index 6c4c943..0000000 --- a/src/Mocha.Query.Jaeger/Controllers/TraceController.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Net; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Mvc; -using Mocha.Core.Extensions; -using Mocha.Core.Storage.Jaeger; -using Mocha.Core.Storage.Jaeger.Trace; -using Mocha.Query.Jaeger.DTOs; - -namespace Mocha.Query.Jaeger.Controllers -{ - [Route("/api")] - public class TraceController(IJaegerSpanReader spanReader) : Controller - { - [HttpGet("services")] - public async Task>> GetSeries() - { - return new(await spanReader.GetServicesAsync()); - } - - [HttpGet("services/{serviceName}/operations")] - public async Task>> GetOperations(string serviceName) - { - return new(await spanReader.GetOperationsAsync(serviceName)); - } - - [HttpGet("traces")] - public async Task>> FindTraces([FromQuery] FindTracesRequest request) - { - static ulong? ParseAsNanoseconds(string? input) - { - if (string.IsNullOrWhiteSpace(input)) - { - return null; - } - - var m = Regex.Match(input, - @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?((?\d+)ms)?((?\d+)μs)?$", - RegexOptions.ExplicitCapture - | RegexOptions.Compiled - | RegexOptions.CultureInvariant - | RegexOptions.RightToLeft); - - if (!m.Success) - { - return null; - } - - var days = m.Groups["days"].Success ? long.Parse(m.Groups["days"].Value) : 0; - var hours = m.Groups["hours"].Success ? long.Parse(m.Groups["hours"].Value) : 0; - var minutes = m.Groups["minutes"].Success ? long.Parse(m.Groups["minutes"].Value) : 0; - var seconds = m.Groups["seconds"].Success ? long.Parse(m.Groups["seconds"].Value) : 0; - var milliseconds = m.Groups["milliseconds"].Success ? long.Parse(m.Groups["milliseconds"].Value) : 0; - var microseconds = m.Groups["microseconds"].Success ? long.Parse(m.Groups["microseconds"].Value) : 0; - - return - (ulong)(((days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1000 + milliseconds) - * 1000 + microseconds) * 1000; - } - - var startTimeMin = request.Start * 1000; - - - var startTimeMax = request.End * 1000; - - var lookBack = ParseAsNanoseconds(request.LookBack); - - if (lookBack.HasValue) - { - var now = DateTimeOffset.Now.ToUnixTimeNanoseconds(); - startTimeMin = now - lookBack.Value; - startTimeMax = now; - } - - IEnumerable traces; - - if (request.TraceID?.Any() ?? false) - { - traces = await spanReader.FindTracesAsync(request.TraceID, startTimeMin, startTimeMax); - } - else - { - traces = await spanReader.FindTracesAsync(new JaegerTraceQueryParameters - { - ServiceName = request.Service, - OperationName = request.Operation, - Tags = (request.Tags ?? "{}").FromJson>()!, - StartTimeMinUnixNano = startTimeMin, - StartTimeMaxUnixNano = startTimeMax, - DurationMinNanoseconds = - string.IsNullOrWhiteSpace(request.MinDuration) - ? null - : ParseAsNanoseconds(request.MinDuration)!, - DurationMaxNanoseconds = - string.IsNullOrWhiteSpace(request.MaxDuration) - ? null - : ParseAsNanoseconds(request.MaxDuration)!, - NumTraces = request.Limit - }); - } - - JaegerResponseError? error = null; - if (traces.Any() is false) - { - error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; - } - - return new JaegerResponse>(traces) { Error = error }; - } - - [HttpGet("traces/{traceID}")] - public async Task>> GetTrace(string traceID) - { - var traces = await spanReader.FindTracesAsync([traceID]); - - JaegerResponseError? error = null; - if (traces.Any() is false) - { - error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; - } - - return new JaegerResponse>(traces) { Error = error }; - } - } -} diff --git a/src/Mocha.Query/Jaeger/Controllers/JaegerTraceController.cs b/src/Mocha.Query/Jaeger/Controllers/JaegerTraceController.cs new file mode 100644 index 0000000..7cff27e --- /dev/null +++ b/src/Mocha.Query/Jaeger/Controllers/JaegerTraceController.cs @@ -0,0 +1,123 @@ +using System.Net; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Mvc; +using Mocha.Core.Extensions; +using Mocha.Core.Storage.Jaeger; +using Mocha.Core.Storage.Jaeger.Trace; +using Mocha.Query.Jaeger.DTOs; + +namespace Mocha.Query.Jaeger.Controllers; + +[Route("/jaeger/api")] +public class JaegerTraceController(IJaegerSpanReader spanReader) : Controller +{ + [HttpGet("services")] + public async Task>> GetSeries() + { + return new(await spanReader.GetServicesAsync()); + } + + [HttpGet("services/{serviceName}/operations")] + public async Task>> GetOperations(string serviceName) + { + return new(await spanReader.GetOperationsAsync(serviceName)); + } + + [HttpGet("traces")] + public async Task>> FindTraces([FromQuery] FindTracesRequest request) + { + static ulong? ParseAsNanoseconds(string? input) + { + if (string.IsNullOrWhiteSpace(input)) + { + return null; + } + + var m = Regex.Match(input, + @"^((?\d+)d)?((?\d+)h)?((?\d+)m)?((?\d+)s)?((?\d+)ms)?((?\d+)μs)?$", + RegexOptions.ExplicitCapture + | RegexOptions.Compiled + | RegexOptions.CultureInvariant + | RegexOptions.RightToLeft); + + if (!m.Success) + { + return null; + } + + var days = m.Groups["days"].Success ? long.Parse(m.Groups["days"].Value) : 0; + var hours = m.Groups["hours"].Success ? long.Parse(m.Groups["hours"].Value) : 0; + var minutes = m.Groups["minutes"].Success ? long.Parse(m.Groups["minutes"].Value) : 0; + var seconds = m.Groups["seconds"].Success ? long.Parse(m.Groups["seconds"].Value) : 0; + var milliseconds = m.Groups["milliseconds"].Success ? long.Parse(m.Groups["milliseconds"].Value) : 0; + var microseconds = m.Groups["microseconds"].Success ? long.Parse(m.Groups["microseconds"].Value) : 0; + + return + (ulong)(((days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1000 + milliseconds) + * 1000 + microseconds) * 1000; + } + + var startTimeMin = request.Start * 1000; + + + var startTimeMax = request.End * 1000; + + var lookBack = ParseAsNanoseconds(request.LookBack); + + if (lookBack.HasValue) + { + var now = DateTimeOffset.Now.ToUnixTimeNanoseconds(); + startTimeMin = now - lookBack.Value; + startTimeMax = now; + } + + IEnumerable traces; + + if (request.TraceID?.Any() ?? false) + { + traces = await spanReader.FindTracesAsync(request.TraceID, startTimeMin, startTimeMax); + } + else + { + traces = await spanReader.FindTracesAsync(new JaegerTraceQueryParameters + { + ServiceName = request.Service, + OperationName = request.Operation, + Tags = (request.Tags ?? "{}").FromJson>()!, + StartTimeMinUnixNano = startTimeMin, + StartTimeMaxUnixNano = startTimeMax, + DurationMinNanoseconds = + string.IsNullOrWhiteSpace(request.MinDuration) + ? null + : ParseAsNanoseconds(request.MinDuration)!, + DurationMaxNanoseconds = + string.IsNullOrWhiteSpace(request.MaxDuration) + ? null + : ParseAsNanoseconds(request.MaxDuration)!, + NumTraces = request.Limit + }); + } + + JaegerResponseError? error = null; + if (traces.Any() is false) + { + error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; + } + + return new JaegerResponse>(traces) { Error = error }; + } + + [HttpGet("traces/{traceID}")] + public async Task>> GetTrace(string traceID) + { + var traces = await spanReader.FindTracesAsync([traceID]); + + JaegerResponseError? error = null; + if (traces.Any() is false) + { + error = new JaegerResponseError { Code = (int)HttpStatusCode.NotFound, Message = "trace not found" }; + } + + return new JaegerResponse>(traces) { Error = error }; + } +} diff --git a/src/Mocha.Query.Jaeger/DTOs/FindTracesRequest.cs b/src/Mocha.Query/Jaeger/DTOs/FindTracesRequest.cs similarity index 100% rename from src/Mocha.Query.Jaeger/DTOs/FindTracesRequest.cs rename to src/Mocha.Query/Jaeger/DTOs/FindTracesRequest.cs diff --git a/src/Mocha.Query.Jaeger/DTOs/JaegerResponse.cs b/src/Mocha.Query/Jaeger/DTOs/JaegerResponse.cs similarity index 100% rename from src/Mocha.Query.Jaeger/DTOs/JaegerResponse.cs rename to src/Mocha.Query/Jaeger/DTOs/JaegerResponse.cs diff --git a/src/Mocha.Query.Jaeger/DTOs/JaegerResponseError.cs b/src/Mocha.Query/Jaeger/DTOs/JaegerResponseError.cs similarity index 100% rename from src/Mocha.Query.Jaeger/DTOs/JaegerResponseError.cs rename to src/Mocha.Query/Jaeger/DTOs/JaegerResponseError.cs diff --git a/src/Mocha.Query.Jaeger/Mocha.Query.Jaeger.csproj b/src/Mocha.Query/Mocha.Query.csproj similarity index 100% rename from src/Mocha.Query.Jaeger/Mocha.Query.Jaeger.csproj rename to src/Mocha.Query/Mocha.Query.csproj diff --git a/src/Mocha.Query.Jaeger/Program.cs b/src/Mocha.Query/Program.cs similarity index 94% rename from src/Mocha.Query.Jaeger/Program.cs rename to src/Mocha.Query/Program.cs index 8e098e8..ca271af 100644 --- a/src/Mocha.Query.Jaeger/Program.cs +++ b/src/Mocha.Query/Program.cs @@ -2,7 +2,7 @@ using Mocha.Storage; using Mocha.Storage.EntityFrameworkCore; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateSlimBuilder(args); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/src/Mocha.Query.Jaeger/Properties/launchSettings.json b/src/Mocha.Query/Properties/launchSettings.json similarity index 100% rename from src/Mocha.Query.Jaeger/Properties/launchSettings.json rename to src/Mocha.Query/Properties/launchSettings.json diff --git a/src/Mocha.Query.Jaeger/appsettings.Development.json b/src/Mocha.Query/appsettings.Development.json similarity index 100% rename from src/Mocha.Query.Jaeger/appsettings.Development.json rename to src/Mocha.Query/appsettings.Development.json diff --git a/src/Mocha.Query.Jaeger/appsettings.json b/src/Mocha.Query/appsettings.json similarity index 100% rename from src/Mocha.Query.Jaeger/appsettings.json rename to src/Mocha.Query/appsettings.json diff --git a/tests/Mocha.Storage.Tests/EntityFrameworkCore/EFJaegerSpanReaderTests.cs b/tests/Mocha.Storage.Tests/EntityFrameworkCore/EFJaegerSpanReaderTests.cs index 1b804ae..7658520 100644 --- a/tests/Mocha.Storage.Tests/EntityFrameworkCore/EFJaegerSpanReaderTests.cs +++ b/tests/Mocha.Storage.Tests/EntityFrameworkCore/EFJaegerSpanReaderTests.cs @@ -171,6 +171,18 @@ public async Task FindTracesAsync_JaegerTraceQueryParameters() } }; + var efResourceAttributes = new List + { + new() + { + TraceId = "TraceId1", + SpanId = "SpanId1", + Key = "service.name", + ValueType = EFAttributeValueType.StringValue, + Value = "ServiceName1" + } + }; + var efSpanAttributes = new List { new() @@ -286,6 +298,7 @@ public async Task FindTracesAsync_JaegerTraceQueryParameters() await using var context = await _dbContextFactory.CreateDbContextAsync(); await context.Spans.AddRangeAsync(efSpans); + await context.ResourceAttributes.AddRangeAsync(efResourceAttributes); await context.SpanAttributes.AddRangeAsync(efSpanAttributes); await context.SpanEvents.AddRangeAsync(efSpanEvents); await context.SpanEventAttributes.AddRangeAsync(efSpanEventAttributes); @@ -310,7 +323,16 @@ public async Task FindTracesAsync_JaegerTraceQueryParameters() }; var traces = await _jaegerSpanReader.FindTracesAsync(queryParameters); - Assert.Single(traces); + var trace = traces.Single(); + var span = trace.Spans.Single(); + var process = trace.Processes.Single(); + + Assert.Equal("TraceId1", trace.TraceID); + Assert.Equal("SpanId1", span.SpanID); + Assert.Equal("SpanName1", span.OperationName); + Assert.Equal("ServiceName1", process.Value.ServiceName); + Assert.Equivalent(new JaegerTag { Key = "span.kind", Type = JaegerTagType.String, Value = "server" }, + span.Tags.Single(t => t.Key == "span.kind")); } [Fact]