Skip to content

[automated] Merge branch 'main' => 'dev' #6267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7e7b3ee
Merged PR 48904: Flowing stable internal versions and get ready for 9…
joperezr Apr 2, 2025
3ae00de
Add TextReasoningContent (#6222)
stephentoub Apr 2, 2025
90a1bd8
Use ErrorContent in OpenAIResponseChatClient (#6231)
stephentoub Apr 2, 2025
a9aa283
OpenAI: Parse detail additional property (#6225)
jozkee Apr 2, 2025
4133f2e
In telemetry, treat AdditionalProperties as sensitive (#6239)
SteveSandersonMS Apr 3, 2025
f09ec8c
Use SHA384 and make key more explicit (#6237)
SteveSandersonMS Apr 3, 2025
ae724c1
Disable STJ default reflection and fix a number of failing tests. (#6…
eiriktsarpalis Apr 3, 2025
625ed7b
Merged PR 48946: Backport recent M.E.AI changes from main
stephentoub Apr 4, 2025
e7224fb
Merged PR 48958: Mark release builds as "stable"
RussKie Apr 4, 2025
0143a52
Add button to report to download dataset as JSON (#6243)
peterwald Apr 3, 2025
bc9ef7e
Merged PR 48969: Add button to report to download dataset as JSON (#6…
peterwald Apr 4, 2025
d4094cc
Merged PR 48970: [9.4] [cherry-picked from main] Introduce Content Sa…
Apr 4, 2025
f0bda61
Remove use of ConfigureAwait from Microsoft.Extensions.AI.dll for AIF…
stephentoub Apr 7, 2025
5cec925
Merged PR 49002: Remove use of ConfigureAwait from Microsoft.Extensio…
stephentoub Apr 7, 2025
667d70e
Merged PR 49004: [9.4] [cherry-pick] Only display tags from the lates…
Apr 7, 2025
65564a8
Merge Internal changes
joperezr Apr 8, 2025
398f7f6
[release/9.2] Merging internal changes (#6263)
joperezr Apr 8, 2025
80cb898
Merge changes from release/9.4 branch
joperezr Apr 8, 2025
06b7eba
Merging release/9.4 changes (#6264)
joperezr Apr 9, 2025
3ebeec1
Add logging buffering (#5635)
evgenyfedorov2 Apr 9, 2025
88349b0
Merge branch 'dev' into merge/main-to-dev
RussKie Apr 9, 2025
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
6 changes: 0 additions & 6 deletions eng/MSBuild/ProjectStaging.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@
-->
<_IsStable Condition="('$(Stage)' != 'dev' and '$(Stage)' != 'preview') Or '$(MSBuildProjectName)' == 'Microsoft.AspNetCore.Testing'">true</_IsStable>

<!--
When DotNetFinalVersionKind is set to 'release' (only for the release branches),
the build will produce stable outputs for 'Shipping' packages.
-->
<DotNetFinalVersionKind Condition=" '$(StabilizePackageVersion)' == 'true' And '$(DotNetFinalVersionKind)' == '' And '$(_IsStable)' == 'true' ">release</DotNetFinalVersionKind>

<!-- Preview packages: do not use stable branding and do not warn about lack of [Experimental] -->
<NoWarn Condition="'$(Stage)' == 'dev' or '$(Stage)' == 'preview'">$(NoWarn);LA0003</NoWarn>
<!--
Expand Down
4 changes: 4 additions & 0 deletions eng/MSBuild/Shared.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@
<ItemGroup Condition="'$(InjectStringSplitExtensions)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\StringSplit\*.cs" LinkBase="Shared\StringSplit" />
</ItemGroup>

<ItemGroup Condition="'$(InjectSharedLogBuffering)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\LogBuffering\*.cs" LinkBase="Shared\LogBuffering" />
</ItemGroup>
</Project>
11 changes: 10 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages
-->
<StabilizePackageVersion Condition="'$(StabilizePackageVersion)' == ''">false</StabilizePackageVersion>

<!--
When DotNetFinalVersionKind is set to 'release' (only for the release branches),
the build will produce stable outputs for 'Shipping' packages.

This is used by the Arcade SDK (Publish.proj) to determine if the build is a release build or not.
-->
<DotNetFinalVersionKind />

<!-- Enabling this rule will cause build failures on undocumented public APIs. -->
<SkipArcadeNoWarnCS1591>true</SkipArcadeNoWarnCS1591>
</PropertyGroup>
Expand Down Expand Up @@ -74,7 +83,7 @@
<MicrosoftExtensionsHttpPollyVersion>10.0.0-preview.3.25151.1</MicrosoftExtensionsHttpPollyVersion>
<MicrosoftExtensionsObjectPoolVersion>10.0.0-preview.3.25151.1</MicrosoftExtensionsObjectPoolVersion>
<!-- Dependencies from https://github.com/dotnet/efcore -->
<MicrosoftEntityFrameworkCoreSqliteVersion>9.0.3</MicrosoftEntityFrameworkCoreSqliteVersion>
<MicrosoftEntityFrameworkCoreSqliteVersion>9.0.4</MicrosoftEntityFrameworkCoreSqliteVersion>
<!-- Dependencies from https://github.com/dotnet/arcade -->
<MicrosoftDotNetBuildTasksTemplatingVersion>10.0.0-beta.25126.4</MicrosoftDotNetBuildTasksTemplatingVersion>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NET9_0_OR_GREATER
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Diagnostics.Buffering;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;
using Microsoft.Shared.Pools;

namespace Microsoft.AspNetCore.Diagnostics.Buffering;

internal sealed class IncomingRequestLogBuffer : IDisposable
{
private const int MaxBatchSize = 256;
private static readonly ObjectPool<List<DeserializedLogRecord>> _recordsToEmitListPool =
PoolFactory.CreateListPoolWithCapacity<DeserializedLogRecord>(MaxBatchSize);

private readonly IBufferedLogger _bufferedLogger;
private readonly LogBufferingFilterRuleSelector _ruleSelector;
private readonly IOptionsMonitor<PerRequestLogBufferingOptions> _options;
private readonly TimeProvider _timeProvider = TimeProvider.System;
private readonly LogBufferingFilterRule[] _filterRules;
private readonly Lock _bufferSwapLock = new();
private volatile bool _disposed;
private ConcurrentQueue<SerializedLogRecord> _activeBuffer = new();
private ConcurrentQueue<SerializedLogRecord> _standbyBuffer = new();
private int _activeBufferSize;
private DateTimeOffset _lastFlushTimestamp;

public IncomingRequestLogBuffer(
IBufferedLogger bufferedLogger,
string category,
LogBufferingFilterRuleSelector ruleSelector,
IOptionsMonitor<PerRequestLogBufferingOptions> options)
{
_bufferedLogger = bufferedLogger;
_ruleSelector = ruleSelector;
_options = options;
_filterRules = LogBufferingFilterRuleSelector.SelectByCategory(_options.CurrentValue.Rules.ToArray(), category);
}

public bool TryEnqueue<TState>(LogEntry<TState> logEntry)
{
if (_timeProvider.GetUtcNow() < _lastFlushTimestamp + _options.CurrentValue.AutoFlushDuration)
{
return false;
}

IReadOnlyList<KeyValuePair<string, object?>>? attributes = logEntry.State as IReadOnlyList<KeyValuePair<string, object?>>;
if (attributes is null)
{
// we expect state to be either ModernTagJoiner or LegacyTagJoiner
// which both implement IReadOnlyList<KeyValuePair<string, object?>>
// and if not, we throw an exception
Throw.InvalidOperationException(
$"Unsupported type of log state detected: {typeof(TState)}, expected IReadOnlyList<KeyValuePair<string, object?>>");
}

if (_ruleSelector.Select(_filterRules, logEntry.LogLevel, logEntry.EventId, attributes) is null)
{
// buffering is not enabled for this log entry,
// return false to indicate that the log entry should be logged normally.
return false;
}

SerializedLogRecord serializedLogRecord = SerializedLogRecordFactory.Create(
logEntry.LogLevel,
logEntry.EventId,
_timeProvider.GetUtcNow(),
attributes,
logEntry.Exception,
logEntry.Formatter(logEntry.State, logEntry.Exception));

if (serializedLogRecord.SizeInBytes > _options.CurrentValue.MaxLogRecordSizeInBytes)
{
SerializedLogRecordFactory.Return(serializedLogRecord);
return false;
}

lock (_bufferSwapLock)
{
_activeBuffer.Enqueue(serializedLogRecord);
_ = Interlocked.Add(ref _activeBufferSize, serializedLogRecord.SizeInBytes);

}

TrimExcessRecords();

return true;
}

public void Flush()
{
_lastFlushTimestamp = _timeProvider.GetUtcNow();

ConcurrentQueue<SerializedLogRecord> tempBuffer;
int numItemsToEmit;
lock (_bufferSwapLock)
{
tempBuffer = _activeBuffer;
_activeBuffer = _standbyBuffer;
_standbyBuffer = tempBuffer;

numItemsToEmit = tempBuffer.Count;

_ = Interlocked.Exchange(ref _activeBufferSize, 0);
}

for (int offset = 0; offset < numItemsToEmit && !tempBuffer.IsEmpty; offset += MaxBatchSize)
{
int currentBatchSize = Math.Min(MaxBatchSize, numItemsToEmit - offset);
List<DeserializedLogRecord> recordsToEmit = _recordsToEmitListPool.Get();
try
{
for (int i = 0; i < currentBatchSize && tempBuffer.TryDequeue(out SerializedLogRecord bufferedRecord); i++)
{
recordsToEmit.Add(new DeserializedLogRecord(
bufferedRecord.Timestamp,
bufferedRecord.LogLevel,
bufferedRecord.EventId,
bufferedRecord.Exception,
bufferedRecord.FormattedMessage,
bufferedRecord.Attributes));
}

_bufferedLogger.LogRecords(recordsToEmit);
}
finally
{
_recordsToEmitListPool.Return(recordsToEmit);
}
}
}

public void Dispose()
{
if (_disposed)
{
return;
}

_disposed = true;

_ruleSelector.InvalidateCache();
}

private void TrimExcessRecords()
{
while (_activeBufferSize > _options.CurrentValue.MaxPerRequestBufferSizeInBytes &&
_activeBuffer.TryDequeue(out SerializedLogRecord item))
{
_ = Interlocked.Add(ref _activeBufferSize, -item.SizeInBytes);
SerializedLogRecordFactory.Return(item);
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if NET9_0_OR_GREATER
using System;
using System.Collections.Concurrent;

namespace Microsoft.AspNetCore.Diagnostics.Buffering;

internal sealed class IncomingRequestLogBufferHolder : IDisposable
{
private readonly ConcurrentDictionary<string, IncomingRequestLogBuffer> _buffers = new();
private bool _disposed;

public IncomingRequestLogBuffer GetOrAdd(string category, Func<string, IncomingRequestLogBuffer> valueFactory) =>
_buffers.GetOrAdd(category, valueFactory);

public void Flush()
{
foreach (IncomingRequestLogBuffer buffer in _buffers.Values)
{
buffer.Flush();
}
}

public void Dispose()
{
if (_disposed)
{
return;
}

_disposed = true;

foreach (IncomingRequestLogBuffer buffer in _buffers.Values)
{
buffer.Dispose();
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if NET9_0_OR_GREATER

using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Diagnostics.Buffering;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Diagnostics.Buffering;
using Microsoft.Extensions.Options;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.Logging;

/// <summary>
/// Lets you register per incoming request log buffering in a dependency injection container.
/// </summary>
[Experimental(diagnosticId: DiagnosticIds.Experiments.Telemetry, UrlFormat = DiagnosticIds.UrlFormat)]
public static class PerIncomingRequestLoggingBuilderExtensions
{
/// <summary>
/// Adds per incoming request log buffering to the logging infrastructure.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param>
/// <param name="configuration">The <see cref="IConfiguration" /> to add.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> or <paramref name="configuration"/> is <see langword="null"/>.</exception>
/// <remarks>
/// Matched logs will be buffered in a buffer specific to each incoming request
/// and can optionally be flushed and emitted during the request lifetime.
/// </remarks>
public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder builder, IConfiguration configuration)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configuration);

_ = builder.Services
.AddSingleton<IConfigureOptions<PerRequestLogBufferingOptions>>(new PerRequestLogBufferingConfigureOptions(configuration))
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>();

return builder
.AddPerRequestBufferManager()
.AddGlobalBuffer(configuration);
}

/// <summary>
/// Adds per incoming request log buffering to the logging infrastructure.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param>
/// <param name="configure">The buffering options configuration delegate.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> or <paramref name="configure"/> is <see langword="null"/>.</exception>
/// <remarks>
/// Matched logs will be buffered in a buffer specific to each incoming request
/// and can optionally be flushed and emitted during the request lifetime.
/// </remarks>
public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder builder, Action<PerRequestLogBufferingOptions> configure)
{
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configure);

_ = builder.Services
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>()
.Configure(configure);

PerRequestLogBufferingOptions options = new PerRequestLogBufferingOptions();
configure(options);

return builder
.AddPerRequestBufferManager()
.AddGlobalBuffer(opts => opts.Rules = options.Rules);
}

/// <summary>
/// Adds per incoming request log buffering to the logging infrastructure.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder" />.</param>
/// <param name="logLevel">The level (and below) of logs to buffer.</param>
/// <returns>The value of <paramref name="builder"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <remarks>
/// Matched logs will be buffered in a buffer specific to each incoming request
/// and can optionally be flushed and emitted during the request lifetime.
/// </remarks>
public static ILoggingBuilder AddPerIncomingRequestBuffer(this ILoggingBuilder builder, LogLevel? logLevel = null)
{
_ = Throw.IfNull(builder);

_ = builder.Services
.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsValidator>()
.Services.AddOptionsWithValidateOnStart<PerRequestLogBufferingOptions, PerRequestLogBufferingOptionsCustomValidator>()
.Configure(options =>
{
options.Rules.Add(new LogBufferingFilterRule(logLevel: logLevel));
});

return builder
.AddPerRequestBufferManager()
.AddGlobalBuffer(logLevel);
}

private static ILoggingBuilder AddPerRequestBufferManager(this ILoggingBuilder builder)
{
builder.Services.TryAddScoped<IncomingRequestLogBufferHolder>();
builder.Services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.TryAddSingleton(sp =>
{
var globalBufferManager = sp.GetRequiredService<GlobalLogBufferManager>();
return ActivatorUtilities.CreateInstance<PerRequestLogBufferManager>(sp, globalBufferManager);
});
builder.Services.TryAddSingleton<LogBuffer>(sp => sp.GetRequiredService<PerRequestLogBufferManager>());
builder.Services.TryAddSingleton<PerRequestLogBuffer>(sp => sp.GetRequiredService<PerRequestLogBufferManager>());

return builder;
}
}

#endif
Loading
Loading