From 10ce85c636fd40d60508aad835a9f4a38c5c93e9 Mon Sep 17 00:00:00 2001 From: Jose Antonio Silva Date: Tue, 3 Feb 2026 17:01:06 +0000 Subject: [PATCH 1/4] WIP --- .../Forwarder/Web/Api/IngestionEndpoints.cs | 187 ++++++++++++++++-- 1 file changed, 176 insertions(+), 11 deletions(-) diff --git a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs index 5b7e0cda..2344a7c4 100644 --- a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs +++ b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs @@ -14,6 +14,7 @@ using System; using System.Buffers; +using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; @@ -29,6 +30,7 @@ using SeqCli.Config; using SeqCli.Forwarder.Channel; using SeqCli.Forwarder.Diagnostics; +using SeqCli.PlainText.LogEvents; using JsonException = System.Text.Json.JsonException; namespace SeqCli.Forwarder.Web.Api; @@ -45,7 +47,7 @@ public IngestionEndpoints(ForwardingAuthenticationStrategy forwardingChannels, S _forwardingChannels = forwardingChannels; _config = config; } - + public void MapEndpoints(WebApplication app) { app.MapPost("ingest/clef", (Delegate) (async (HttpContext context) => await IngestCompactFormatAsync(context))); @@ -63,15 +65,17 @@ async Task IngestAsync(HttpContext context) if (contentType != null && contentType.StartsWith(clefMediaType)) return await IngestCompactFormatAsync(context); - IngestionLog.ForClient(context.Connection.RemoteIpAddress) - .Error("Client supplied a legacy raw-format (non-CLEF) payload"); - return Error(HttpStatusCode.BadRequest, "Only newline-delimited JSON (CLEF) payloads are supported."); + return await IngestLegacyRawFormatAsync(context); + + // IngestionLog.ForClient(context.Connection.RemoteIpAddress) + // .Error("Client supplied a legacy raw-format (non-CLEF) payload"); + // return Error(HttpStatusCode.BadRequest, "Only newline-delimited JSON (CLEF) payloads are supported."); } - + async Task IngestCompactFormatAsync(HttpContext context) { byte[]? rented = null; - + try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted); @@ -177,6 +181,135 @@ async Task IngestCompactFormatAsync(HttpContext context) } } + async Task IngestLegacyRawFormatAsync(HttpContext context) + { + byte[]? rented = null; + + try + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + + var requestApiKey = GetApiKey(context.Request); + var log = _forwardingChannels.GetForwardingChannel(requestApiKey); + + // Add one for the extra newline that we have to insert at the end of batches. + var bufferSize = _config.Connection.BatchSizeLimitBytes + 1; + rented = ArrayPool.Shared.Rent(bufferSize); + var buffer = new ArraySegment(rented, 0, bufferSize); + var writeHead = 0; + var readHead = 0; + + var done = false; + while (!done) + { + // Fill the memory buffer from as much of the incoming request payload as possible; buffering in memory increases the + // size of write batches. + while (!done) + { + var remaining = buffer.Count - 1 - writeHead; + if (remaining == 0) + { + IngestionLog.ForClient(context.Connection.RemoteIpAddress) + .Error("An incoming request exceeded the configured batch size limit"); + return Error(HttpStatusCode.RequestEntityTooLarge, "the request is too large to process"); + } + + var read = await context.Request.Body.ReadAsync(buffer.AsMemory(writeHead, remaining), cts.Token); + if (read == 0) + { + done = true; + } + + writeHead += read; + + // Ingested batches must be terminated with `\n`, but this isn't an API requirement. + if (done && writeHead > 0 && writeHead < buffer.Count && buffer[writeHead - 1] != (byte)'\n') + { + buffer[writeHead] = (byte)'\n'; + writeHead += 1; + } + } + + // Validate what we read, marking out a batch of one or more complete newline-delimited events. + var batchStart = readHead; + var batchEnd = readHead; + while (batchEnd < writeHead) + { + var eventStart = batchEnd; + var nlIndex = buffer.AsSpan()[eventStart..].IndexOf((byte)'\n'); + + if (nlIndex == -1) + { + break; + } + + var eventEnd = eventStart + nlIndex + 1; + + batchEnd = eventEnd; + readHead = batchEnd; + + if (!ValidateRaw(buffer.AsSpan()[eventStart..eventEnd], out var error)) + { + var payloadText = Encoding.UTF8.GetString(buffer.AsSpan()[eventStart..eventEnd]); + IngestionLog.ForPayload(context.Connection.RemoteIpAddress, payloadText) + .Error("Payload validation failed: {Error}", error); + return Error(HttpStatusCode.BadRequest, $"Payload validation failed: {error}."); + } + } + + if (batchStart != batchEnd) + { + var rawSpan = buffer[batchStart..batchEnd]; + var jsonDoc = JsonDocument.Parse(rawSpan.ToArray()); + var events = new ArrayList(); + // Parse raw json to extract events + foreach (var element in jsonDoc.RootElement.EnumerateObject()) + { + if(element.Name == "events" || element.Name == "Events") + foreach(var rec in element.Value.EnumerateArray()) + events.Add(rec); + } + // Convert to CLEF + foreach (JsonElement evt in events) + { + var serilogEvent = evt.EnumerateObject().ToDictionary(p => p.Name, p => (object?)p.Value); + var eventId = ""; + var eventType = 0u; + Serilog.Events.LogEvent serilogLogEvent = LogEventBuilder.FromProperties(serilogEvent, null); + var logEvent = Apps.Hosting.EventFormat.FromRaw(eventId, eventType, serilogLogEvent); + // serialise logEvent to byte array kind of buffer + var clefArray = Utf8.GetBytes(""); // TODO: serialize logEvent to CLEF byte array + await log.WriteAsync(clefArray, cts.Token); + } + } + + // Copy any unprocessed data into our buffer and continue + if (!done && readHead != 0) + { + var retain = writeHead - readHead; + buffer.AsSpan()[readHead..writeHead].CopyTo(buffer.AsSpan()[..retain]); + readHead = 0; + writeHead = retain; + } + } + + return SuccessfulIngestion(); + } + catch (Exception ex) + { + IngestionLog.ForClient(context.Connection.RemoteIpAddress) + .Error(ex, "Ingestion failed"); + return Error(HttpStatusCode.InternalServerError, "Ingestion failed."); + } + finally + { + if (rented != null) + { + ArrayPool.Shared.Return(rented); + } + } + } static bool DefaultedBoolQuery(HttpRequest request, string queryParameterName) { var parameter = request.Query[queryParameterName]; @@ -207,13 +340,13 @@ bool ValidateClef(Span evt, [NotNullWhen(false)] out string? errorFragment { // Note that `errorFragment` does not include user-supplied values; we opt in to adding this to // the ingestion log and include it using `ForPayload()`. - + if (evt.Length > _config.Connection.EventSizeLimitBytes) { errorFragment = "an event exceeds the configured size limit"; return false; } - + var reader = new Utf8JsonReader(evt); var foundTimestamp = false; @@ -269,7 +402,39 @@ bool ValidateClef(Span evt, [NotNullWhen(false)] out string? errorFragment errorFragment = null; return true; } - + + bool ValidateRaw(Span evt, [NotNullWhen(false)] out string? errorFragment) + { + // Note that `errorFragment` does not include user-supplied values; we opt in to adding this to + // the ingestion log and include it using `ForPayload()`. + + if (evt.Length > _config.Connection.EventSizeLimitBytes) + { + errorFragment = "an event exceeds the configured size limit"; + return false; + } + + try + { + + var jsonDoc = JsonDocument.Parse(evt.ToArray()); + if (!(jsonDoc.RootElement.TryGetProperty("events", out var eventsToken) || + jsonDoc.RootElement.TryGetProperty("Events", out eventsToken))) + { + errorFragment = "events were not found in raw payload"; + return false; + } + } + catch (JsonException) + { + errorFragment = "JSON parsing failure"; + return false; + } + + errorFragment = null; + return true; + } + static IResult Error(HttpStatusCode statusCode, string message) { return Results.Json(new ErrorPart { Error = message }, statusCode: (int)statusCode); @@ -279,8 +444,8 @@ static IResult SuccessfulIngestion() { return TypedResults.Content( "{}", - "application/json", - Utf8, + "application/json", + Utf8, StatusCodes.Status201Created); } } From 52d0de589db8d3eaa5cf1301e8cd05c3adb20c7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:49:47 +0000 Subject: [PATCH 2/4] Initial plan From 419ad354d32d4031a12b47261525817ba3b67c67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:53:48 +0000 Subject: [PATCH 3/4] Implement legacy raw Serilog ingestion conversion to CLEF with end-to-end test Co-authored-by: canoas <69575+canoas@users.noreply.github.com> --- .../Forwarder/Web/Api/IngestionEndpoints.cs | 154 ++++++++++++++++-- .../ForwarderLegacyRawIngestionTestCase.cs | 53 ++++++ 2 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 test/SeqCli.EndToEnd/Forwarder/ForwarderLegacyRawIngestionTestCase.cs diff --git a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs index 2344a7c4..5784d7e5 100644 --- a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs +++ b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs @@ -14,8 +14,8 @@ using System; using System.Buffers; -using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Net; using System.Text; @@ -30,7 +30,6 @@ using SeqCli.Config; using SeqCli.Forwarder.Channel; using SeqCli.Forwarder.Diagnostics; -using SeqCli.PlainText.LogEvents; using JsonException = System.Text.Json.JsonException; namespace SeqCli.Forwarder.Web.Api; @@ -262,25 +261,28 @@ async Task IngestLegacyRawFormatAsync(HttpContext context) { var rawSpan = buffer[batchStart..batchEnd]; var jsonDoc = JsonDocument.Parse(rawSpan.ToArray()); - var events = new ArrayList(); + // Parse raw json to extract events + JsonElement eventsArray = default; foreach (var element in jsonDoc.RootElement.EnumerateObject()) { - if(element.Name == "events" || element.Name == "Events") - foreach(var rec in element.Value.EnumerateArray()) - events.Add(rec); + if (element.Name == "events" || element.Name == "Events") + { + eventsArray = element.Value; + break; + } + } + + // Convert each legacy event to CLEF + using var clefStream = new MemoryStream(); + foreach (var evt in eventsArray.EnumerateArray()) + { + ConvertLegacyEventToClef(evt, clefStream); } - // Convert to CLEF - foreach (JsonElement evt in events) + + if (clefStream.Length > 0) { - var serilogEvent = evt.EnumerateObject().ToDictionary(p => p.Name, p => (object?)p.Value); - var eventId = ""; - var eventType = 0u; - Serilog.Events.LogEvent serilogLogEvent = LogEventBuilder.FromProperties(serilogEvent, null); - var logEvent = Apps.Hosting.EventFormat.FromRaw(eventId, eventType, serilogLogEvent); - // serialise logEvent to byte array kind of buffer - var clefArray = Utf8.GetBytes(""); // TODO: serialize logEvent to CLEF byte array - await log.WriteAsync(clefArray, cts.Token); + await log.WriteAsync(clefStream.ToArray(), cts.Token); } } @@ -310,6 +312,126 @@ async Task IngestLegacyRawFormatAsync(HttpContext context) } } } + + static void ConvertLegacyEventToClef(JsonElement legacyEvent, Stream output) + { + using var writer = new Utf8JsonWriter(output, new JsonWriterOptions { SkipValidation = true }); + writer.WriteStartObject(); + + string? timestamp = null; + string? level = null; + string? messageTemplate = null; + string? exception = null; + JsonElement? properties = null; + + // Extract known fields from legacy event + foreach (var prop in legacyEvent.EnumerateObject()) + { + switch (prop.Name) + { + case "Timestamp": + timestamp = prop.Value.GetString(); + break; + case "Level": + level = prop.Value.GetString(); + break; + case "MessageTemplate": + messageTemplate = prop.Value.GetString(); + break; + case "Exception": + if (prop.Value.ValueKind != JsonValueKind.Null) + exception = prop.Value.GetString(); + break; + case "Properties": + properties = prop.Value; + break; + } + } + + // Write CLEF fields + if (!string.IsNullOrEmpty(timestamp)) + { + writer.WriteString("@t", timestamp); + } + + if (!string.IsNullOrEmpty(level)) + { + writer.WriteString("@l", level); + } + + if (!string.IsNullOrEmpty(messageTemplate)) + { + writer.WriteString("@mt", messageTemplate); + } + + if (!string.IsNullOrEmpty(exception)) + { + writer.WriteString("@x", exception); + } + + // Write properties as top-level fields + if (properties.HasValue) + { + foreach (var prop in properties.Value.EnumerateObject()) + { + writer.WritePropertyName(prop.Name); + WriteJsonElement(writer, prop.Value); + } + } + + writer.WriteEndObject(); + writer.Flush(); + + // Write newline after each event + output.WriteByte((byte)'\n'); + } + + static void WriteJsonElement(Utf8JsonWriter writer, JsonElement element) + { + switch (element.ValueKind) + { + case JsonValueKind.Object: + writer.WriteStartObject(); + foreach (var prop in element.EnumerateObject()) + { + writer.WritePropertyName(prop.Name); + WriteJsonElement(writer, prop.Value); + } + writer.WriteEndObject(); + break; + case JsonValueKind.Array: + writer.WriteStartArray(); + foreach (var item in element.EnumerateArray()) + { + WriteJsonElement(writer, item); + } + writer.WriteEndArray(); + break; + case JsonValueKind.String: + writer.WriteStringValue(element.GetString()); + break; + case JsonValueKind.Number: + if (element.TryGetInt32(out var intValue)) + writer.WriteNumberValue(intValue); + else if (element.TryGetInt64(out var longValue)) + writer.WriteNumberValue(longValue); + else if (element.TryGetDouble(out var doubleValue)) + writer.WriteNumberValue(doubleValue); + else + writer.WriteRawValue(element.GetRawText()); + break; + case JsonValueKind.True: + writer.WriteBooleanValue(true); + break; + case JsonValueKind.False: + writer.WriteBooleanValue(false); + break; + case JsonValueKind.Null: + writer.WriteNullValue(); + break; + } + } + static bool DefaultedBoolQuery(HttpRequest request, string queryParameterName) { var parameter = request.Query[queryParameterName]; diff --git a/test/SeqCli.EndToEnd/Forwarder/ForwarderLegacyRawIngestionTestCase.cs b/test/SeqCli.EndToEnd/Forwarder/ForwarderLegacyRawIngestionTestCase.cs new file mode 100644 index 00000000..ed432ad4 --- /dev/null +++ b/test/SeqCli.EndToEnd/Forwarder/ForwarderLegacyRawIngestionTestCase.cs @@ -0,0 +1,53 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Seq.Api; +using SeqCli.EndToEnd.Support; +using Serilog; +using Xunit; + +namespace SeqCli.EndToEnd.Forwarder; + +public class ForwarderLegacyRawIngestionTestCase : ICliTestCase +{ + public async Task ExecuteAsync(SeqConnection _, ILogger logger, CliCommandRunner runner) + { + var (forwarder, forwarderUri) = await runner.SpawnForwarderAsync(); + using (forwarder) + { + using var connection = new SeqConnection(forwarderUri); + + // Test case from problem statement + var legacyRawPayload = @"{""Events"":[{""Timestamp"":""2026-02-02T18:32:53.0512280+00:00"",""Level"":""Information"",""Exception"":null,""MessageTemplate"":""[{Step}] succeeded ({Duration} seconds)"",""Properties"":{""Application"":""SerilogPS PS2"",""Environment"":""Prod"",""Step"":""tudo pronto"",""Duration"":0.0,""Version"":""2.3"",""Host"":null}}]}"; + + await IngestLegacyRaw(connection, legacyRawPayload, HttpStatusCode.Created); + + // Test with multiple events + var multipleEvents = @"{""Events"":[ + {""Timestamp"":""2026-02-02T18:32:53.0512280+00:00"",""Level"":""Information"",""Exception"":null,""MessageTemplate"":""Event 1"",""Properties"":{""Prop1"":""Value1""}}, + {""Timestamp"":""2026-02-02T18:32:54.0512280+00:00"",""Level"":""Warning"",""Exception"":null,""MessageTemplate"":""Event 2"",""Properties"":{""Prop2"":42}} + ]}"; + + await IngestLegacyRaw(connection, multipleEvents, HttpStatusCode.Created); + + // Test with exception + var withException = @"{""Events"":[{""Timestamp"":""2026-02-02T18:32:53.0512280+00:00"",""Level"":""Error"",""Exception"":""System.Exception: Test exception"",""MessageTemplate"":""An error occurred"",""Properties"":{}}]}"; + + await IngestLegacyRaw(connection, withException, HttpStatusCode.Created); + + // Test with nested properties + var withNestedProps = @"{""Events"":[{""Timestamp"":""2026-02-02T18:32:53.0512280+00:00"",""Level"":""Debug"",""Exception"":null,""MessageTemplate"":""Complex event"",""Properties"":{""Nested"":{""Inner"":""Value""},""Array"":[1,2,3]}}]}"; + + await IngestLegacyRaw(connection, withNestedProps, HttpStatusCode.Created); + } + } + + static async Task IngestLegacyRaw(SeqConnection connection, string rawPayload, HttpStatusCode expectedStatusCode) + { + var content = new StringContent(rawPayload, Encoding.UTF8, "application/json"); + var response = await connection.Client.HttpClient.PostAsync("api/events/raw", content); + Assert.Equal(expectedStatusCode, response.StatusCode); + } +} From 95a1c786710a428f4508bd0feff27accb43c5aa0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:59:26 +0000 Subject: [PATCH 4/4] Simplify legacy raw ingestion to handle single JSON object correctly Co-authored-by: canoas <69575+canoas@users.noreply.github.com> --- .../Forwarder/Web/Api/IngestionEndpoints.cs | 142 +++++------------- 1 file changed, 37 insertions(+), 105 deletions(-) diff --git a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs index 5784d7e5..d08cb1de 100644 --- a/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs +++ b/src/SeqCli/Forwarder/Web/Api/IngestionEndpoints.cs @@ -182,8 +182,6 @@ async Task IngestCompactFormatAsync(HttpContext context) async Task IngestLegacyRawFormatAsync(HttpContext context) { - byte[]? rented = null; - try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted); @@ -192,108 +190,49 @@ async Task IngestLegacyRawFormatAsync(HttpContext context) var requestApiKey = GetApiKey(context.Request); var log = _forwardingChannels.GetForwardingChannel(requestApiKey); - // Add one for the extra newline that we have to insert at the end of batches. - var bufferSize = _config.Connection.BatchSizeLimitBytes + 1; - rented = ArrayPool.Shared.Rent(bufferSize); - var buffer = new ArraySegment(rented, 0, bufferSize); - var writeHead = 0; - var readHead = 0; + // Read the entire request body + using var reader = new StreamReader(context.Request.Body); + var requestBody = await reader.ReadToEndAsync(cts.Token); - var done = false; - while (!done) + // Validate and parse the legacy raw format + JsonDocument jsonDoc; + try { - // Fill the memory buffer from as much of the incoming request payload as possible; buffering in memory increases the - // size of write batches. - while (!done) - { - var remaining = buffer.Count - 1 - writeHead; - if (remaining == 0) - { - IngestionLog.ForClient(context.Connection.RemoteIpAddress) - .Error("An incoming request exceeded the configured batch size limit"); - return Error(HttpStatusCode.RequestEntityTooLarge, "the request is too large to process"); - } - - var read = await context.Request.Body.ReadAsync(buffer.AsMemory(writeHead, remaining), cts.Token); - if (read == 0) - { - done = true; - } - - writeHead += read; - - // Ingested batches must be terminated with `\n`, but this isn't an API requirement. - if (done && writeHead > 0 && writeHead < buffer.Count && buffer[writeHead - 1] != (byte)'\n') - { - buffer[writeHead] = (byte)'\n'; - writeHead += 1; - } - } - - // Validate what we read, marking out a batch of one or more complete newline-delimited events. - var batchStart = readHead; - var batchEnd = readHead; - while (batchEnd < writeHead) - { - var eventStart = batchEnd; - var nlIndex = buffer.AsSpan()[eventStart..].IndexOf((byte)'\n'); - - if (nlIndex == -1) - { - break; - } - - var eventEnd = eventStart + nlIndex + 1; + jsonDoc = JsonDocument.Parse(requestBody); + } + catch (JsonException) + { + IngestionLog.ForPayload(context.Connection.RemoteIpAddress, requestBody) + .Error("Payload validation failed: JSON parsing failure"); + return Error(HttpStatusCode.BadRequest, "Payload validation failed: JSON parsing failure."); + } - batchEnd = eventEnd; - readHead = batchEnd; + // Extract events array + if (!jsonDoc.RootElement.TryGetProperty("Events", out var eventsArray) && + !jsonDoc.RootElement.TryGetProperty("events", out eventsArray)) + { + IngestionLog.ForPayload(context.Connection.RemoteIpAddress, requestBody) + .Error("Payload validation failed: events were not found in raw payload"); + return Error(HttpStatusCode.BadRequest, "Payload validation failed: events were not found in raw payload."); + } - if (!ValidateRaw(buffer.AsSpan()[eventStart..eventEnd], out var error)) - { - var payloadText = Encoding.UTF8.GetString(buffer.AsSpan()[eventStart..eventEnd]); - IngestionLog.ForPayload(context.Connection.RemoteIpAddress, payloadText) - .Error("Payload validation failed: {Error}", error); - return Error(HttpStatusCode.BadRequest, $"Payload validation failed: {error}."); - } - } + if (eventsArray.ValueKind != JsonValueKind.Array) + { + IngestionLog.ForPayload(context.Connection.RemoteIpAddress, requestBody) + .Error("Payload validation failed: events must be an array"); + return Error(HttpStatusCode.BadRequest, "Payload validation failed: events must be an array."); + } - if (batchStart != batchEnd) - { - var rawSpan = buffer[batchStart..batchEnd]; - var jsonDoc = JsonDocument.Parse(rawSpan.ToArray()); - - // Parse raw json to extract events - JsonElement eventsArray = default; - foreach (var element in jsonDoc.RootElement.EnumerateObject()) - { - if (element.Name == "events" || element.Name == "Events") - { - eventsArray = element.Value; - break; - } - } - - // Convert each legacy event to CLEF - using var clefStream = new MemoryStream(); - foreach (var evt in eventsArray.EnumerateArray()) - { - ConvertLegacyEventToClef(evt, clefStream); - } - - if (clefStream.Length > 0) - { - await log.WriteAsync(clefStream.ToArray(), cts.Token); - } - } + // Convert each legacy event to CLEF + using var clefStream = new MemoryStream(); + foreach (var evt in eventsArray.EnumerateArray()) + { + ConvertLegacyEventToClef(evt, clefStream); + } - // Copy any unprocessed data into our buffer and continue - if (!done && readHead != 0) - { - var retain = writeHead - readHead; - buffer.AsSpan()[readHead..writeHead].CopyTo(buffer.AsSpan()[..retain]); - readHead = 0; - writeHead = retain; - } + if (clefStream.Length > 0) + { + await log.WriteAsync(clefStream.ToArray(), cts.Token); } return SuccessfulIngestion(); @@ -304,13 +243,6 @@ async Task IngestLegacyRawFormatAsync(HttpContext context) .Error(ex, "Ingestion failed"); return Error(HttpStatusCode.InternalServerError, "Ingestion failed."); } - finally - { - if (rented != null) - { - ArrayPool.Shared.Return(rented); - } - } } static void ConvertLegacyEventToClef(JsonElement legacyEvent, Stream output)