Skip to content

Commit

Permalink
Support Json source generators, OTLP exporter, remove serilog file si…
Browse files Browse the repository at this point in the history
…nk, Docker .NET 8 image, slim builder, update NuGet packages
  • Loading branch information
VladimirKhil committed Jun 2, 2024
1 parent 72487d6 commit 8b5f5d0
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 99 deletions.
34 changes: 15 additions & 19 deletions src/Spard.Service/BackgroundServices/ExamplesLoader.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using Spard.Service.Contracts;
using Spard.Service.Models;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Spard.Service.BackgroundServices;

/// <summary>
/// Loads SPARD examples from <see cref="ExampleFolderName" /> folder into <see cref="IExamplesRepository" />.
/// </summary>
internal sealed class ExamplesLoader : BackgroundService
internal sealed class ExamplesLoader(
IExamplesRepository examplesRepository,
ILogger<ExamplesLoader> logger) : BackgroundService
{
/// <summary>
/// Folder name containing examples.
Expand All @@ -19,45 +22,35 @@ internal sealed class ExamplesLoader : BackgroundService
/// </summary>
public const string IndexFileName = "index.json";

private readonly IExamplesRepository _examplesRepository;
private readonly ILogger<ExamplesLoader> _logger;

/// <summary>
/// Initializes a new instance of <see cref="ExamplesLoader" /> class.
/// </summary>
public ExamplesLoader(IExamplesRepository examplesRepository, ILogger<ExamplesLoader> logger)
{
_examplesRepository = examplesRepository;
_logger = logger;
}

protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
var examplesFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ExampleFolderName);
var indexFilePath = Path.Combine(examplesFolderPath, IndexFileName);

if (!File.Exists(indexFilePath))
{
_logger.LogWarning("Examples file {fileName} not found!", indexFilePath);
logger.LogWarning("Examples file {fileName} not found!", indexFilePath);
return Task.CompletedTask;
}

var examplesDict = JsonSerializer.Deserialize<Dictionary<string, ExampleModel>>(File.ReadAllText(indexFilePath))
?? new Dictionary<string, ExampleModel>();
var examplesDict = JsonSerializer.Deserialize(
File.ReadAllText(indexFilePath),
ExampleModelsContext.Default.DictionaryStringExampleModel)
?? [];

foreach (var exampleEntry in examplesDict)
{
if (!int.TryParse(exampleEntry.Key, out var id))
{
_logger.LogWarning("Example id {exampleId} is not a number!", exampleEntry.Key);
logger.LogWarning("Example id {exampleId} is not a number!", exampleEntry.Key);
continue;
}

var spardFileName = Path.Combine(examplesFolderPath, $"{exampleEntry.Key}.spard");

if (!File.Exists(spardFileName))
{
_logger.LogWarning("Example SPARD file {fileName} not found!", spardFileName);
logger.LogWarning("Example SPARD file {fileName} not found!", spardFileName);
continue;
}

Expand All @@ -70,9 +63,12 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)
Transform = spardText
};

_examplesRepository.AddExample(id, example);
examplesRepository.AddExample(id, example);
}

return Task.CompletedTask;
}
}

[JsonSerializable(typeof(Dictionary<string, ExampleModel>))]
internal partial class ExampleModelsContext : JsonSerializerContext { }
11 changes: 6 additions & 5 deletions src/Spard.Service/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
FROM mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine AS base
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS base
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
RUN apk add --no-cache icu-libs
WORKDIR /app
EXPOSE 5000
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Spard.Service/Spard.Service.csproj", "src/Spard.Service/"]
COPY ["src/Spard.Service.Contract/Spard.Service.Contract.csproj", "src/Spard.Service.Contract/"]
COPY ["src/Spard/Spard.csproj", "src/Spard/"]
RUN dotnet restore "src/Spard.Service/Spard.Service.csproj" -r linux-musl-x64
COPY . .
WORKDIR "/src/src/Spard.Service"
RUN dotnet publish "Spard.Service.csproj" -p:PublishSingleFile=true -r linux-musl-x64 -p:PublishTrimmed=true -p:InvariantGlobalization=false -c Release -o /app/publish
RUN dotnet publish "Spard.Service.csproj" -p:PublishSingleFile=true -r linux-musl-x64 -p:PublishTrimmed=true -p:InvariantGlobalization=false -c $BUILD_CONFIGURATION -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["./Spard.Service", "--urls", "http://*:5000"]
ENTRYPOINT ["./Spard.Service"]
9 changes: 4 additions & 5 deletions src/Spard.Service/Metrics/OtelMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace Spard.Service.Metrics;
/// </summary>
public sealed class OtelMetrics
{
public const string MeterName = "Spard";

private Counter<int> TransformCounter { get; }

private Counter<int> TransformTableCounter { get; }
Expand All @@ -15,12 +17,9 @@ public sealed class OtelMetrics

private Counter<int> GenerateSourceCodeCounter { get; }

public string MeterName { get; }

public OtelMetrics(string meterName = "Spard")
public OtelMetrics(IMeterFactory meterFactory)
{
var meter = new Meter(meterName);
MeterName = meterName;
var meter = meterFactory.Create(MeterName);

TransformCounter = meter.CreateCounter<int>("transforms");
TransformTableCounter = meter.CreateCounter<int>("table-transforms");
Expand Down
41 changes: 30 additions & 11 deletions src/Spard.Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
using Serilog;
using Spard.Service.BackgroundServices;
using Spard.Service.Configuration;
using Spard.Service.Contract;
using Spard.Service.Contracts;
using Spard.Service.EndpointDefinitions;
using Spard.Service.Helpers;
using Spard.Service.Metrics;
using Spard.Service.Services;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);
var builder = WebApplication.CreateSlimBuilder(args);

builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console(new Serilog.Formatting.Display.MessageTemplateTextFormatter(
"[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}"))
.ReadFrom.Configuration(ctx.Configuration)
.Filter.ByExcluding(logEvent =>
logEvent.Exception is BadHttpRequestException
Expand All @@ -38,7 +41,12 @@ static void ConfigureServices(IServiceCollection services, IConfiguration config
services.AddHostedService<ExamplesLoader>();

AddRateLimits(services, configuration);
AddMetrics(services);
AddMetrics(services, configuration);

services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, SpardSerializerContext.Default);
});
}

static void AddRateLimits(IServiceCollection services, IConfiguration configuration)
Expand All @@ -50,20 +58,26 @@ static void AddRateLimits(IServiceCollection services, IConfiguration configurat
services.AddInMemoryRateLimiting();
}

static void AddMetrics(IServiceCollection services)
static void AddMetrics(IServiceCollection services, IConfiguration configuration)
{
var meters = new OtelMetrics();
services.AddSingleton<OtelMetrics>();

services.AddOpenTelemetry().WithMetrics(builder =>
builder
.ConfigureResource(rb => rb.AddService("Spard"))
.AddMeter(meters.MeterName)
.AddMeter(OtelMetrics.MeterName)
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddProcessInstrumentation()
.AddPrometheusExporter());

services.AddSingleton(meters);
.AddOtlpExporter(options =>
{
var otelUri = configuration["OpenTelemetry:ServiceUri"];
if (otelUri != null)
{
options.Endpoint = new Uri(otelUri);
}
}));
}

static void Configure(WebApplication app)
Expand All @@ -78,6 +92,11 @@ static void Configure(WebApplication app)
TransformEndpointDefinitions.DefineExamplesEndpoint(app);

app.UseIpRateLimiting();
}

app.UseOpenTelemetryPrometheusScrapingEndpoint();
}
[JsonSerializable(typeof(IEnumerable<SpardExampleBaseInfo>))]
[JsonSerializable(typeof(SpardExampleInfo))]
[JsonSerializable(typeof(TransformRequest))]
[JsonSerializable(typeof(TransformTableResult))]
[JsonSerializable(typeof(ProcessResult<string>))]
internal partial class SpardSerializerContext : JsonSerializerContext { }
10 changes: 5 additions & 5 deletions src/Spard.Service/Spard.Service.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@
<ItemGroup>
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="Ensure.That" Version="10.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.4.0-rc.4" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.5.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="0.5.0-beta.2" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="7.0.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
24 changes: 1 addition & 23 deletions src/Spard.Service/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,14 @@
]
},
"Serilog": {
"Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console" ],
"Using": [ "Serilog.Sinks.Console" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}",
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console"
}
},
{
"Name": "File",
"Args": {
"outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level}] {Message:lj} {Exception}{NewLine}",
"path": "logs/spard.log",
"shared": "true",
"fileSizeLimitBytes": "5000000",
"rollOnFileSizeLimit": true,
"rollingInterval": "Day",
"retainedFileCountLimit": 5,
"flushToDiskInterval": "1"
}
}
],
"Enrich": [ "FromLogContext", "WithThreadId", "WithMachineName" ]
}
}
2 changes: 1 addition & 1 deletion src/Spard/Spard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
<None Include="key.snk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.9.2" />
</ItemGroup>
</Project>
12 changes: 6 additions & 6 deletions test/Spard.Service.IntegrationTests/ExamplesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ public sealed class ExamplesTests : TestsBase
public async Task GetExamples_OkAsync()
{
var examples = await SpardClient.Examples.GetExamplesAsync();
Assert.Greater(examples.Length, 0);
Assert.That(examples.Length, Is.GreaterThan(0));

var firstExample = examples.OrderBy(e => e.Id).First();
var example = await SpardClient.Examples.GetExampleAsync(firstExample.Id);
Assert.NotNull(example);
Assert.AreEqual(firstExample.Id, example.Id);
Assert.AreEqual(firstExample.Name, example.Name);
Assert.That(example, Is.Not.Null);
Assert.That(firstExample.Id, Is.EqualTo(example.Id));
Assert.That(firstExample.Name, Is.EqualTo(example.Name));
}

[Test]
Expand All @@ -32,9 +32,9 @@ public async Task GetExample_Localized_OkAsync()
var spardClientRus = ServiceCollectionExtensions.CreateSpardClient(options);

var example = await spardClientRus.Examples.GetExampleAsync(5);
Assert.AreEqual("Äâîéíàÿ çàìåíà", example.Name);
Assert.That(example.Name, Is.EqualTo("Äâîéíàÿ çàìåíà"));

var example2 = await SpardClient.Examples.GetExampleAsync(5);
Assert.AreEqual("Double replacement", example2.Name);
Assert.That(example2.Name, Is.EqualTo("Double replacement"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="nunit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
</ItemGroup>

<ItemGroup>
Expand Down
8 changes: 4 additions & 4 deletions test/Spard.Service.IntegrationTests/SpardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public async Task GenerateTable_OkAsync()

var actualResult = await SpardClient.Spard.GenerateTableAsync(transform);

Assert.AreEqual(result.Replace("\r", ""), actualResult.Result.Replace("\r", ""));
Assert.Greater(actualResult.Duration, TimeSpan.Zero);
Assert.That(result.Replace("\r", ""), Is.EqualTo(actualResult.Result.Replace("\r", "")));
Assert.That(actualResult.Duration, Is.GreaterThan(TimeSpan.Zero));
}

[Test]
Expand All @@ -26,7 +26,7 @@ public async Task GenerateSourceCode_OkAsync()

var actualResult = await SpardClient.Spard.GenerateSourceCodeAsync(transform);

Assert.AreEqual(result.Replace("\r", ""), actualResult.Result.Replace("\r", ""));
Assert.Greater(actualResult.Duration, TimeSpan.Zero);
Assert.That(result.Replace("\r", ""), Is.EqualTo(actualResult.Result.Replace("\r", "")));
Assert.That(actualResult.Duration, Is.GreaterThan(TimeSpan.Zero));
}
}
18 changes: 9 additions & 9 deletions test/Spard.Service.IntegrationTests/TransformTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public async Task RunAllExamples_OkAsync()
{
if (!exampleResults.TryGetValue(example.Id, out var expectedResult))
{
Assert.Fail("Unknown example: {0}", example.Id);
Assert.Fail($"Unknown example: {example.Id}");
}

var exampleData = await SpardClient.Examples.GetExampleAsync(example.Id);
Expand All @@ -31,8 +31,8 @@ public async Task RunAllExamples_OkAsync()
Transform = exampleData.Transform
});

Assert.AreEqual(expectedResult.Replace("\r", ""), result.Result.Replace("\r", ""));
Assert.Greater(result.Duration, TimeSpan.Zero);
Assert.That(expectedResult.Replace("\r", ""), Is.EqualTo(result.Result.Replace("\r", "")));
Assert.That(result.Duration, Is.GreaterThan(TimeSpan.Zero));
}
}

Expand All @@ -49,11 +49,11 @@ public async Task RunTableTransform_OkAsync()
Transform = transform
});

Assert.AreEqual(result, actualResult.Result);
Assert.IsTrue(actualResult.IsStandardResultTheSame);
Assert.Greater(actualResult.ParseDuration, TimeSpan.Zero);
Assert.Greater(actualResult.StandardTransformDuration, TimeSpan.Zero);
Assert.Greater(actualResult.TableBuildDuration, TimeSpan.Zero);
Assert.Greater(actualResult.TableTransformDuration, TimeSpan.Zero);
Assert.That(result, Is.EqualTo(actualResult.Result));
Assert.That(actualResult.IsStandardResultTheSame, Is.True);
Assert.That(actualResult.ParseDuration, Is.GreaterThan(TimeSpan.Zero));
Assert.That(actualResult.StandardTransformDuration, Is.GreaterThan(TimeSpan.Zero));
Assert.That(actualResult.TableBuildDuration, Is.GreaterThan(TimeSpan.Zero));
Assert.That(actualResult.TableTransformDuration, Is.GreaterThan(TimeSpan.Zero));
}
}
Loading

0 comments on commit 8b5f5d0

Please sign in to comment.