Skip to content

[Monitor Ingestion] AOT Support #51563

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

Merged
merged 5 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 2 additions & 7 deletions sdk/monitor/Azure.Monitor.Ingestion/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# Release History

## 1.2.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Bugs Fixed
## 1.2.0 (2025-08-05)

### Other Changes
- 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.

## 1.1.2 (2024-04-03)

Expand Down
38 changes: 38 additions & 0 deletions sdk/monitor/Azure.Monitor.Ingestion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ Response response = await client.UploadAsync(

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.

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).

```C# Snippet:UploadLogDataIEnumerableAsync
var endpoint = new Uri("<data_collection_endpoint_uri>");
var ruleId = "<data_collection_rule_id>";
Expand All @@ -193,6 +195,42 @@ for (int i = 0; i < 100; i++)
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
```

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>`.

For example, if you have the following custom type and AOT serialization context:
```C# Snippet:IngestionAotSerializationTypes
public record Person(string Name, string Department, int EmployeeNumber)
{
}

[JsonSerializable(typeof(Person))]
public partial class ExampleDeserializationContext : JsonSerializerContext
{
}
```

The Ingestion upload is identical, other than serializing prior to the call:
```C# Snippet:UploadLogDataIEnumerableAsyncAot
var endpoint = new Uri("<data_collection_endpoint_uri>");
var ruleId = "<data_collection_rule_id>";
var streamName = "<stream_name>";

var credential = new DefaultAzureCredential();
LogsIngestionClient client = new(endpoint, credential);

DateTimeOffset currentTime = DateTimeOffset.UtcNow;

var entries = new List<BinaryData>();
for (int i = 0; i < 100; i++)
{
entries.Add(BinaryData.FromBytes(
JsonSerializer.SerializeToUtf8Bytes(new Person($"Person{i}", "Department{i}", i))));
}

// Upload our logs
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
```

### Upload custom logs as IEnumerable with EventHandler

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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace Azure.Monitor.Ingestion
{
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
{
public AzureMonitorIngestionContext() { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
{
Expand All @@ -26,7 +30,9 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
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; }
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; }
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; }
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; }
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; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace Azure.Monitor.Ingestion
{
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
{
public AzureMonitorIngestionContext() { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
{
Expand All @@ -26,8 +30,12 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
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; }
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; }
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; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Serialization is performed at runtime without a serialization context available.")]
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; }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Serialization is performed at runtime without a serialization context available.")]
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; }
}
public partial class LogsIngestionClientOptions : Azure.Core.ClientOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace Azure.Monitor.Ingestion
{
public partial class AzureMonitorIngestionContext : System.ClientModel.Primitives.ModelReaderWriterContext
{
public AzureMonitorIngestionContext() { }
}
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct LogsIngestionAudience : System.IEquatable<Azure.Monitor.Ingestion.LogsIngestionAudience>
{
Expand All @@ -26,7 +30,9 @@ public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential crede
public LogsIngestionClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Ingestion.LogsIngestionClientOptions options) { }
public virtual Azure.Core.Pipeline.HttpPipeline Pipeline { get { throw null; } }
public virtual Azure.Response Upload(string ruleId, string streamName, Azure.Core.RequestContent content, string contentEncoding = null, Azure.RequestContext context = null) { throw null; }
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; }
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; }
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; }
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; }
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; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ To use these samples, you'll first need to set up resources. See [getting starte

## Upload custom logs asynchronously

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).
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).

```C# Snippet:UploadCustomLogsAsync
var endpoint = new Uri("<data_collection_endpoint>");
Expand Down Expand Up @@ -52,3 +52,41 @@ Response response = await client.UploadAsync(
streamName,
RequestContent.Create(data)).ConfigureAwait(false);
```

## Upload custom logs asynchronously (AOT)

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>`.

For example, if you have the following custom type and AOT serialization context:
```C# Snippet:IngestionAotSerializationTypes
public record Person(string Name, string Department, int EmployeeNumber)
{
}

[JsonSerializable(typeof(Person))]
public partial class ExampleDeserializationContext : JsonSerializerContext
{
}
```

The Ingestion upload is identical, other than serializing prior to the call:
```C# Snippet:UploadLogDataIEnumerableAsyncAot
var endpoint = new Uri("<data_collection_endpoint_uri>");
var ruleId = "<data_collection_rule_id>";
var streamName = "<stream_name>";

var credential = new DefaultAzureCredential();
LogsIngestionClient client = new(endpoint, credential);

DateTimeOffset currentTime = DateTimeOffset.UtcNow;

var entries = new List<BinaryData>();
for (int i = 0; i < 100; i++)
{
entries.Add(BinaryData.FromBytes(
JsonSerializer.SerializeToUtf8Bytes(new Person($"Person{i}", "Department{i}", i))));
}

// Upload our logs
Response response = await client.UploadAsync(ruleId, streamName, entries).ConfigureAwait(false);
```
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<Description>A library for ingesting data to Azure Monitor.</Description>
<AssemblyTitle>Azure Monitor Ingestion client library</AssemblyTitle>
<Version>1.2.0-beta.1</Version>
<Version>1.2.0</Version>
<!--The ApiCompatVersion is managed automatically and should not generally be modified manually.-->
<ApiCompatVersion>1.1.2</ApiCompatVersion>
<PackageTags>Azure Monitor Ingestion</PackageTags>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading