Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d64cd62
Replace JSON.NET with System.Text.Json across the codebase
niemyjski Feb 28, 2026
13aa277
fix: address STJ migration bugs and PR review feedback
niemyjski Mar 1, 2026
47f134e
Migrate serializer, config, and bootstrapper to Elastic.Clients.Elast…
niemyjski Mar 22, 2026
9f7914b
Migrate index configurations to new Elasticsearch 8 fluent API
niemyjski Mar 22, 2026
4559885
Migrate queries and repositories to Elastic.Clients.Elasticsearch
niemyjski Mar 22, 2026
6be3932
Migrate jobs, migrations, and controllers to Elastic.Clients.Elastics…
niemyjski Mar 22, 2026
3890cb4
fix: remove duplicate Id mapping in EventIndex
niemyjski Mar 22, 2026
acd90ef
Fix STJ serialization test failures
niemyjski Mar 22, 2026
5494e58
Address PR review feedback
niemyjski Mar 22, 2026
7415334
Fix EmptyCollectionModifier, deserialization, and test quality
niemyjski Mar 22, 2026
9a016f5
Fix return→continue in event upgraders, address review feedback
niemyjski Mar 22, 2026
26e26a4
Quote interpolated values in FilterExpression to prevent query injection
niemyjski Mar 22, 2026
b261663
Fix whitespace body check in DeserializeResponseAsync
niemyjski Mar 22, 2026
7c40963
Replace all Lucene FilterExpression strings with typed queries
niemyjski Mar 22, 2026
18d4850
Fix TokenRepository int enum mapping and SerializerTests
niemyjski Mar 23, 2026
c870828
Add enum serialization, fix response models, improve test coverage
niemyjski Mar 23, 2026
2635b2e
Revert enum string serialization, fix CodeQL issues
niemyjski Mar 23, 2026
2e33229
Fix API contract preservation, code quality, and test migration
niemyjski Mar 23, 2026
86734e3
Fix query visitor ternary, preserve ShouldSerialize predicates
niemyjski Mar 23, 2026
5dece58
Fix JsonExtensionData and SummaryData.Data serialization issues
niemyjski Mar 23, 2026
f5df2e1
Merge branch 'main' into feature/system-text-json-v2
niemyjski Mar 30, 2026
0f0a62e
Fix GetValue<T> deserialization for PascalCase dictionary keys
niemyjski Mar 30, 2026
aeac7b8
Fix nested snake_case deserialization in Error.Inner
niemyjski Mar 30, 2026
6e9c72d
Fix STJ review findings: case-insensitive dicts, DateTime.MinValue Ki…
niemyjski Mar 30, 2026
716533b
Eliminate reflection-based property counting in DataDictionary.GetVal…
niemyjski Mar 31, 2026
f2183e1
Replace ElasticSystemTextJsonSerializer with DefaultSourceSerializer
niemyjski Mar 31, 2026
5bdb52e
Use FieldEquals and StackStatus enum instead of raw TermQuery/strings
niemyjski Mar 31, 2026
b80f6f2
Fix OrganizationRepository suspended filter logic
niemyjski Mar 31, 2026
1629973
Use JsonSerializerOptions directly in JsonEventParserPlugin instead o…
niemyjski Mar 31, 2026
d2466e4
Remove dead Foundatio.JsonNet dependency; use type-safe converter rem…
niemyjski Mar 31, 2026
e7004d6
Remove unnecessary DateTime converters; restore test assertions
niemyjski Apr 2, 2026
70728e5
Merge remote-tracking branch 'origin/main' into feature/system-text-j…
niemyjski Apr 21, 2026
4bab7ac
Update Foundatio packages and adapt to NRT API changes
niemyjski Apr 21, 2026
32262f1
pr feedback
niemyjski Apr 27, 2026
1ddcd16
Merge remote-tracking branch 'origin/main' into feature/system-text-j…
niemyjski Apr 27, 2026
bf1d63b
Merge remote-tracking branch 'origin/main' into feature/system-text-j…
niemyjski Apr 28, 2026
cd8adf9
Merge remote-tracking branch 'origin/main' into feature/system-text-j…
niemyjski Apr 28, 2026
4c4b87b
Fix chart tooltip crash when date value is not a Date object
niemyjski Apr 28, 2026
8ae0483
Address Copilot PR review feedback
niemyjski Apr 28, 2026
2460812
Merge remote-tracking branch 'origin/main' into feature/system-text-j…
niemyjski Apr 29, 2026
6572f8e
Merge main: Size(0) fix for CleanupOrphanedDataJob
niemyjski Apr 29, 2026
0327231
Remove all Newtonsoft.Json usage, address PR feedback
niemyjski Apr 29, 2026
658e5a8
fix: restore SizeField mapping in EventIndex for mapper-size plugin
niemyjski Apr 29, 2026
f730e38
Replace incompatible AspNetCore.HealthChecks.Elasticsearch with inlin…
niemyjski Apr 29, 2026
07dc018
fix: StackStatus enum FieldEquals mismatch + nullable response arrays
niemyjski Apr 29, 2026
74f36c1
Merge branch 'main' into feature/system-text-json-v2
niemyjski Apr 30, 2026
ffc9230
Merge origin/main
niemyjski May 11, 2026
04cd078
Update Foundatio.Repositories preview
niemyjski May 11, 2026
2a83e16
fix: prevent extension data from overwriting explicit Data entries
niemyjski May 11, 2026
9ae41e4
fix: ToFormattedString now uses DeepClone to avoid mutating input node
niemyjski May 11, 2026
97f94d5
fix: apply PascalCase fallback to JSON string deserialization path
niemyjski May 11, 2026
870e437
fix: improve GetSlackToken error handling and TypeHelper dict comparison
niemyjski May 11, 2026
8d525fe
fix: use ILogger instead of Debug.WriteLine in GetSlackToken
niemyjski May 11, 2026
3097d2e
test: add serialization tests for 6 previously untested data models
niemyjski May 11, 2026
15448be
fix: add ObjectToInferredTypesConverter to fallback deserialization o…
niemyjski May 11, 2026
1e7caf5
Revert unnecessary PascalCase fallback machinery
niemyjski May 11, 2026
f0d5b27
Switch to JsonNamingPolicy.SnakeCaseLower with JsonPropertyName overr…
niemyjski May 11, 2026
ba7d464
Merge branch 'main' into feature/system-text-json-v2
niemyjski May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Available in `.claude/agents/`. Use `@agent-name` to invoke:
- Prefer additive documentation updates — don't replace strategic docs wholesale, extend them
- **Backwards compatibility:** Never break existing public APIs, WebSocket message formats, config keys, or exported library interfaces without explicit user approval. Call out any breaking change as a BLOCKER in reviews.
- **API test files:** Update `tests/http/*.http` files whenever endpoints change (new, modified, or removed).
- **Abbreviations:** Never abbreviate `Organization` as `org` in code (variable names, parameters, method names, or comments). Always spell out `organization`.
- **PR descriptions:** When creating a PR, fill out any existing PR template. Provide concise context: what changed, why, new APIs/features/behaviors, and any breaking changes. No essays — just enough for reviewers to understand the value and impact.
- **App URL for QA:** `http://localhost:7110` — probe `/api/v2/about` for health check.
- **Never test against production:** Always dogfood, QA test, and run API smoke tests against `localhost` only. Never use production URLs (e.g., `be.exceptionless.io`) in scripts, tests, or browser automation. Start the app locally via `aspire run` or the AppHost before testing.
Expand Down
1 change: 1 addition & 0 deletions src/Exceptionless.AppHost/Exceptionless.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<NoWarn>$(NoWarn);ASPIRECERTIFICATES001</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.19.21" />
<PackageReference Include="Aspire.Hosting.Azure.Storage" Version="13.3.0" />
<PackageReference Include="Aspire.Hosting.Browsers" Version="13.3.0-preview.1.26256.5" />
<PackageReference Include="Aspire.Hosting.JavaScript" Version="13.3.0" />
Expand Down
24 changes: 18 additions & 6 deletions src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using HealthChecks.Elasticsearch;
using Elastic.Clients.Elasticsearch;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

Expand Down Expand Up @@ -27,7 +27,6 @@ public static IResourceBuilder<ElasticsearchResource> AddElasticsearch(this IDis
var elasticsearch = new ElasticsearchResource(name);

string? connectionString = null;
ElasticsearchOptions? options = null;

builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(elasticsearch, async (@event, ct) =>
{
Expand All @@ -36,16 +35,13 @@ public static IResourceBuilder<ElasticsearchResource> AddElasticsearch(this IDis
{
throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{elasticsearch.Name}' resource but the connection string was null.");
}

options = new ElasticsearchOptions();
options.UseServer(connectionString);
});

string healthCheckKey = $"{name}_check";
builder.Services.AddHealthChecks()
.Add(new HealthCheckRegistration(
healthCheckKey,
sp => new ElasticsearchHealthCheck(options!),
sp => new ElasticsearchConnectionHealthCheck(() => connectionString),
failureStatus: null,
tags: null,
timeout: null));
Expand Down Expand Up @@ -127,3 +123,19 @@ internal static class ElasticsearchContainerImageTags
public const string KibanaImage = "kibana/kibana";
public const string Tag = "8.19.15";
}

internal sealed class ElasticsearchConnectionHealthCheck(Func<string?> connectionStringFactory) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var connectionString = connectionStringFactory();
if (string.IsNullOrEmpty(connectionString))
return new HealthCheckResult(context.Registration.FailureStatus, "Connection string not available.");

var client = new ElasticsearchClient(new Uri(connectionString));
var response = await client.PingAsync(cancellationToken);
return response.IsValidResponse
? HealthCheckResult.Healthy()
: new HealthCheckResult(context.Registration.FailureStatus, $"Elasticsearch ping failed: {response.DebugInformation}");
}
}
35 changes: 3 additions & 32 deletions src/Exceptionless.Core/Bootstrapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using Elastic.Clients.Elasticsearch;
using Exceptionless.Core.Authentication;
using Exceptionless.Core.Billing;
using Exceptionless.Core.Configuration;
Expand All @@ -7,7 +8,6 @@
using Exceptionless.Core.Jobs;
using Exceptionless.Core.Jobs.WorkItemHandlers;
using Exceptionless.Core.Mail;
using Exceptionless.Core.Models;
using Exceptionless.Core.Models.WorkItems;
using Exceptionless.Core.Pipeline;
using Exceptionless.Core.Plugins;
Expand All @@ -24,7 +24,6 @@
using Exceptionless.Core.Services;
using Exceptionless.Core.Utility;
using Exceptionless.Core.Validation;
using Exceptionless.Serializer;
using Foundatio.Caching;
using Foundatio.Extensions.Hosting.Jobs;
using Foundatio.Extensions.Hosting.Startup;
Expand All @@ -43,7 +42,6 @@
using Foundatio.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using DataDictionary = Exceptionless.Core.Models.DataDictionary;
using MaintainIndexesJob = Foundatio.Repositories.Elasticsearch.Jobs.MaintainIndexesJob;

namespace Exceptionless.Core;
Expand All @@ -52,27 +50,7 @@ public class Bootstrapper
{
public static void RegisterServices(IServiceCollection services, AppOptions appOptions)
{
// PERF: Work towards getting rid of JSON.NET.
Newtonsoft.Json.JsonConvert.DefaultSettings = () => new Newtonsoft.Json.JsonSerializerSettings
{
DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset
};

services.AddSingleton<Newtonsoft.Json.Serialization.IContractResolver>(_ => GetJsonContractResolver());
services.AddSingleton<Newtonsoft.Json.JsonSerializerSettings>(s =>
{
// NOTE: These settings may need to be synced in the Elastic Configuration.
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Ignore,
DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset,
ContractResolver = s.GetRequiredService<Newtonsoft.Json.Serialization.IContractResolver>()
};

settings.AddModelConverters(s.GetRequiredService<ILogger<Bootstrapper>>());
return settings;
});

// Register System.Text.Json options with Exceptionless defaults (snake_case, null handling)
services.AddSingleton(_ => new JsonSerializerOptions().ConfigureExceptionlessDefaults());

services.AddSingleton<ISerializer>(s => s.GetRequiredService<ITextSerializer>());
Expand All @@ -90,7 +68,7 @@ public static void RegisterServices(IServiceCollection services, AppOptions appO
}));

services.AddSingleton<ExceptionlessElasticConfiguration>();
services.AddSingleton<Nest.IElasticClient>(s => s.GetRequiredService<ExceptionlessElasticConfiguration>().Client);
services.AddSingleton<ElasticsearchClient>(s => s.GetRequiredService<ExceptionlessElasticConfiguration>().Client);
services.AddSingleton<IElasticConfiguration>(s => s.GetRequiredService<ExceptionlessElasticConfiguration>());
services.AddStartupAction<ExceptionlessElasticConfiguration>();

Expand Down Expand Up @@ -279,13 +257,6 @@ public static void AddHostedJobs(IServiceCollection services, ILoggerFactory log
logger.LogWarning("Jobs running in process");
}

public static DynamicTypeContractResolver GetJsonContractResolver()
{
var resolver = new DynamicTypeContractResolver(new LowerCaseUnderscorePropertyNamesContractResolver());
resolver.UseDefaultResolverFor(typeof(DataDictionary), typeof(SettingsDictionary), typeof(VersionOnePlugin.VersionOneWebHookStack), typeof(VersionOnePlugin.VersionOneWebHookEvent));
return resolver;
}

private static IQueue<T> CreateQueue<T>(IServiceProvider container, TimeSpan? workItemTimeout = null) where T : class
{
var loggerFactory = container.GetRequiredService<ILoggerFactory>();
Expand Down
8 changes: 2 additions & 6 deletions src/Exceptionless.Core/Exceptionless.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
<PackageReference Include="Exceptionless.DateTimeExtensions" Version="6.0.1" />
<PackageReference Include="Exceptionless.RandomData" Version="2.0.1" />
<PackageReference Include="Foundatio.Extensions.Hosting" Version="13.0.1" />
<PackageReference Include="Foundatio.JsonNet" Version="13.0.1" />
<PackageReference Include="MiniValidation" Version="0.10.0" />
<PackageReference Include="NEST.JsonNetSerializer" Version="7.17.5" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
<PackageReference Include="McSherry.SemanticVersioning" Version="1.4.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.7" />
Expand All @@ -34,9 +32,7 @@
<PackageReference Include="Stripe.net" Version="51.1.0" />
<PackageReference Include="System.DirectoryServices" Version="10.0.7" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Foundatio.Repositories.Elasticsearch" Version="7.18.2" Condition="'$(ReferenceFoundatioRepositoriesSource)' == '' OR '$(ReferenceFoundatioRepositoriesSource)' == 'false'" />
<ProjectReference
Include="..\..\..\..\Foundatio\Foundatio.Repositories\src\Foundatio.Repositories.Elasticsearch\Foundatio.Repositories.Elasticsearch.csproj"
Condition="'$(ReferenceFoundatioRepositoriesSource)' == 'true'" />
<PackageReference Include="Foundatio.Repositories.Elasticsearch" Version="8.0.0-preview.elastic-client.0.8" Condition="'$(ReferenceFoundatioRepositoriesSource)' == '' OR '$(ReferenceFoundatioRepositoriesSource)' == 'false'" />
<ProjectReference Include="..\..\..\..\Foundatio\Foundatio.Repositories\src\Foundatio.Repositories.Elasticsearch\Foundatio.Repositories.Elasticsearch.csproj" Condition="'$(ReferenceFoundatioRepositoriesSource)' == 'true'" />
</ItemGroup>
</Project>
Loading
Loading