Skip to content

Commit 2129efc

Browse files
jorgerangel-msftjsquirescottaddie
authored
[Monitor Ingestion] AOT Support (Azure#51563)
* [Ingestion] AOT Support The focus of these changes is to regenerate the Ingestion client using the new AOT-friendly serialization paths and to ensure that the client is compatible with Ahead-of-Time (AOT) compilation. This is critical for inclusion in the Azure.MCP product. Compliance required new overloads for log uploads, as the existing form allowed for runtime serialization in different forms, which were not compatible with AOT. For AOT scenarios, log uploads will accept `BinaryData` directly, allowing callers to prepare their data in a way that is compatible with AOT compilation. * README, sample, and release prep * Fixing API Compat * Update sdk/monitor/Azure.Monitor.Ingestion/samples/Sample1_LogDataAsync.md Co-authored-by: Scott Addie <[email protected]> * Update sdk/monitor/Azure.Monitor.Ingestion/README.md Co-authored-by: Scott Addie <[email protected]> --------- Co-authored-by: Jesse Squire <[email protected]> Co-authored-by: Scott Addie <[email protected]>
1 parent c3d443c commit 2129efc

16 files changed

+305
-62
lines changed

sdk/monitor/Azure.Monitor.Ingestion/CHANGELOG.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
# Release History
22

3-
## 1.2.0-beta.1 (Unreleased)
4-
5-
### Features Added
6-
7-
### Breaking Changes
8-
9-
### Bugs Fixed
3+
## 1.2.0 (2025-08-05)
104

115
### Other Changes
6+
- Added new overloads for `UploadAsync` and `Upload`, enabling callers to upload logs in `BinaryData` form. This allows for callers to use an AOT-compliant serializer for logs, ensuring the Ingestion package can be trimmed.
127

138
## 1.1.2 (2024-04-03)
149

sdk/monitor/Azure.Monitor.Ingestion/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ Response response = await client.UploadAsync(
167167

168168
You can also upload logs using either the `LogsIngestionClient.Upload` or the `LogsIngestionClient.UploadAsync` method in which logs are passed in a generic `IEnumerable` type along with an optional `LogsUploadOptions` parameter. The `LogsUploadOptions` parameter includes a serializer, concurrency, and an EventHandler.
169169

170+
When uploading custom logs as an `IEnumerable` of an application model type, the `LogsIngestionClient` will perform serialization on your behalf, using either the [ObjectSerializer](https://learn.microsoft.com/dotnet/api/azure.core.serialization.objectserializer?view=azure-dotnet) registered with your `LogsUploadOptions` or the serializer used by [BinaryData.FromObjectAsJson](https://learn.microsoft.com/dotnet/api/system.binarydata.fromobjectasjson). Both approaches use a serialization path that is not compatible with [ahead-of-time compilation (AOT)](https://learn.microsoft.com/dotnet/core/deploying/native-aot).
171+
170172
```C# Snippet:UploadLogDataIEnumerableAsync
171173
var endpoint = new Uri("<data_collection_endpoint_uri>");
172174
var ruleId = "<data_collection_rule_id>";
@@ -193,6 +195,42 @@ for (int i = 0; i < 100; i++)
193195
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
194196
```
195197

198+
To upload custom logs in an AOT environment, your application must take ownership of serialization and use the `LogsIngestionClient.Upload` or `LogsIngestionClient.UploadAsync` overload that accepts an `IEnumerable<BinaryData>`.
199+
200+
For example, if you have the following custom type and AOT serialization context:
201+
```C# Snippet:IngestionAotSerializationTypes
202+
public record Person(string Name, string Department, int EmployeeNumber)
203+
{
204+
}
205+
206+
[JsonSerializable(typeof(Person))]
207+
public partial class ExampleDeserializationContext : JsonSerializerContext
208+
{
209+
}
210+
```
211+
212+
The Ingestion upload is identical, other than serializing prior to the call:
213+
```C# Snippet:UploadLogDataIEnumerableAsyncAot
214+
var endpoint = new Uri("<data_collection_endpoint_uri>");
215+
var ruleId = "<data_collection_rule_id>";
216+
var streamName = "<stream_name>";
217+
218+
var credential = new DefaultAzureCredential();
219+
LogsIngestionClient client = new(endpoint, credential);
220+
221+
DateTimeOffset currentTime = DateTimeOffset.UtcNow;
222+
223+
var entries = new List<BinaryData>();
224+
for (int i = 0; i < 100; i++)
225+
{
226+
entries.Add(BinaryData.FromBytes(
227+
JsonSerializer.SerializeToUtf8Bytes(new Person($"Person{i}", "Department{i}", i))));
228+
}
229+
230+
// Upload our logs
231+
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
232+
```
233+
196234
### Upload custom logs as IEnumerable with EventHandler
197235

198236
You can upload logs using either the `LogsIngestionClient.Upload` or the `LogsIngestionClient.UploadAsync` method. In these two methods, logs are passed in a generic `IEnumerable` type. Additionally, there's an `LogsUploadOptions`-typed parameter in which a serializer, concurrency, and EventHandler can be set. The default serializer is set to `System.Text.Json`, but you can pass in the serializer you would like used. The `MaxConcurrency` property sets the number of threads that will be used in the `UploadAsync` method. The default value is 5, and this parameter is unused in the `Upload` method. The EventHandler is used for error handling. It gives the user the option to abort the upload if a batch fails and access the failed logs and corresponding exception. Without the EventHandler, if an upload fails, an `AggregateException` will be thrown.

sdk/monitor/Azure.Monitor.Ingestion/api/Azure.Monitor.Ingestion.net462.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace Azure.Monitor.Ingestion
22
{
3+
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
4+
{
5+
public AzureMonitorIngestionContext() { }
6+
}
37
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
48
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
59
{
@@ -26,7 +30,9 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
2630
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
2731
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
2832
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
33+
public virtual Azure.Response Upload(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
2934
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
35+
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3036
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3137
public virtual Azure.Response Upload<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3238
}

sdk/monitor/Azure.Monitor.Ingestion/api/Azure.Monitor.Ingestion.net8.0.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace Azure.Monitor.Ingestion
22
{
3+
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
4+
{
5+
public AzureMonitorIngestionContext() { }
6+
}
37
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
48
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
59
{
@@ -26,8 +30,12 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
2630
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
2731
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
2832
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
33+
public virtual Azure.Response Upload(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
2934
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
35+
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
36+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Serialization is performed at runtime without a serialization context available.")]
3037
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
38+
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Serialization is performed at runtime without a serialization context available.")]
3139
public virtual Azure.Response Upload<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3240
}
3341
public partial class LogsIngestionClientOptions : Azure.Core.ClientOptions

sdk/monitor/Azure.Monitor.Ingestion/api/Azure.Monitor.Ingestion.netstandard2.0.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
namespace Azure.Monitor.Ingestion
22
{
3+
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
4+
{
5+
public AzureMonitorIngestionContext() { }
6+
}
37
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
48
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
59
{
@@ -26,7 +30,9 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
2630
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
2731
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
2832
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
33+
public virtual Azure.Response Upload(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
2934
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
35+
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync(string ruleId, string streamName, System.Collections.Generic.IEnumerable<System.BinaryData> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3036
public virtual System.Threading.Tasks.Task<Azure.Response> UploadAsync<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3137
public virtual Azure.Response Upload<T>(string ruleId, string streamName, System.Collections.Generic.IEnumerable<T> logs, Azure.Monitor.Ingestion.LogsUploadOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
3238
}

sdk/monitor/Azure.Monitor.Ingestion/samples/Sample1_LogDataAsync.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ To use these samples, you'll first need to set up resources. See [getting starte
44

55
## Upload custom logs asynchronously
66

7-
You can create a client and call the client's `UploadAsync` method. Take note of the data ingestion [limits](https://learn.microsoft.com/azure/azure-monitor/service-limits#custom-logs).
7+
You can create a client and call the client's `UploadAsync` method. Take note of the data ingestion [limits](https://learn.microsoft.com/azure/azure-monitor/service-limits#custom-logs). When uploading custom logs as an `IEnumerable` of an application model type, the `LogsIngestionClient` will perform serialization on your behalf, using either the [ObjectSerializer](https://learn.microsoft.com/dotnet/api/azure.core.serialization.objectserializer?view=azure-dotnet) registered with your `LogsUploadOptions` or the serializer used by [BinaryData.FromObjectAsJson](https://learn.microsoft.com/dotnet/api/system.binarydata.fromobjectasjson). Both approaches use a serialization path that is not compatible with [ahead-of-time compilation (AOT)](https://learn.microsoft.com/dotnet/core/deploying/native-aot).
88

99
```C# Snippet:UploadCustomLogsAsync
1010
var endpoint = new Uri("<data_collection_endpoint>");
@@ -52,3 +52,41 @@ Response response = await client.UploadAsync(
5252
streamName,
5353
RequestContent.Create(data)).ConfigureAwait(false);
5454
```
55+
56+
## Upload custom logs asynchronously (AOT)
57+
58+
To upload custom logs in an AOT environment, your application must take ownership of serialization and use the `LogsIngestionClient.Upload` or `LogsIngestionClient.UploadAsync` overload that accepts an `IEnumerable<BinaryData>`.
59+
60+
For example, if you have the following custom type and AOT serialization context:
61+
```C# Snippet:IngestionAotSerializationTypes
62+
public record Person(string Name, string Department, int EmployeeNumber)
63+
{
64+
}
65+
66+
[JsonSerializable(typeof(Person))]
67+
public partial class ExampleDeserializationContext : JsonSerializerContext
68+
{
69+
}
70+
```
71+
72+
The Ingestion upload is identical, other than serializing prior to the call:
73+
```C# Snippet:UploadLogDataIEnumerableAsyncAot
74+
var endpoint = new Uri("<data_collection_endpoint_uri>");
75+
var ruleId = "<data_collection_rule_id>";
76+
var streamName = "<stream_name>";
77+
78+
var credential = new DefaultAzureCredential();
79+
LogsIngestionClient client = new(endpoint, credential);
80+
81+
DateTimeOffset currentTime = DateTimeOffset.UtcNow;
82+
83+
var entries = new List<BinaryData>();
84+
for (int i = 0; i < 100; i++)
85+
{
86+
entries.Add(BinaryData.FromBytes(
87+
JsonSerializer.SerializeToUtf8Bytes(new Person($"Person{i}", "Department{i}", i))));
88+
}
89+
90+
// Upload our logs
91+
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
92+
```

sdk/monitor/Azure.Monitor.Ingestion/src/Azure.Monitor.Ingestion.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<Description>A library for ingesting data to Azure Monitor.</Description>
44
<AssemblyTitle>Azure Monitor Ingestion client library</AssemblyTitle>
5-
<Version>1.2.0-beta.1</Version>
5+
<Version>1.2.0</Version>
66
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
77
<ApiCompatVersion>1.1.2</ApiCompatVersion>
88
<PackageTags>Azure Monitor Ingestion</PackageTags>

sdk/monitor/Azure.Monitor.Ingestion/src/Generated/Models/AzureMonitorIngestionContext.cs

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)