| File: Implementation\BreezeEnvelope.cs | Web Access |
| Project: src\sdk\src\Cli\Microsoft.DotNet.Cli.Telemetry\Microsoft.DotNet.Cli.Telemetry.csproj (Microsoft.DotNet.Cli.Telemetry) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json.Serialization; namespace Microsoft.DotNet.Cli.Telemetry.Implementation; /// <summary> /// POCO model for the Application Insights "Breeze" telemetry envelope, plus the /// source-generated <see cref="System.Text.Json.Serialization.JsonSerializerContext"/> used to /// (de)serialize it. Using source generation instead of hand-rolled /// <c>Utf8JsonWriter</c>/<c>JsonDocument</c> code keeps the wire mapping declarative and /// AOT/trim-safe (required because this assembly is consumed by the Native-AOT /// <c>dotnet-aot</c> CLI). /// /// <para>The envelope is generic over its <c>baseData</c> payload so each concrete payload /// type is serialized without a polymorphic <c>object</c> member (which would not be /// source-gen/AOT friendly).</para> /// </summary> internal sealed class TelemetryEnvelope<TData> where TData : class { [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; [JsonPropertyName("time")] public string Time { get; set; } = string.Empty; [JsonPropertyName("iKey")] public string InstrumentationKey { get; set; } = string.Empty; [JsonPropertyName("tags")] public Dictionary<string, string> Tags { get; set; } = []; [JsonPropertyName("data")] public TelemetryData<TData> Data { get; set; } = new(); } /// <summary>The <c>data</c> object of a telemetry envelope: a type discriminator plus payload.</summary> internal sealed class TelemetryData<TData> where TData : class { [JsonPropertyName("baseType")] public string BaseType { get; set; } = string.Empty; [JsonPropertyName("baseData")] public TData BaseData { get; set; } = default!; } /// <summary>Breeze <c>RequestData</c> payload (Server/Consumer spans).</summary> internal sealed class RequestData { [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("duration")] public string Duration { get; set; } = string.Empty; [JsonPropertyName("success")] public bool Success { get; set; } [JsonPropertyName("responseCode")] public string ResponseCode { get; set; } = "0"; [JsonPropertyName("properties")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary<string, string>? Properties { get; set; } [JsonPropertyName("ver")] public int Ver { get; set; } = BreezeSchema.DataVersion; } /// <summary>Breeze <c>RemoteDependencyData</c> payload (Internal/Client/Producer spans).</summary> internal sealed class RemoteDependencyData { [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; [JsonPropertyName("name")] public string? Name { get; set; } [JsonPropertyName("type")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Type { get; set; } [JsonPropertyName("duration")] public string Duration { get; set; } = string.Empty; [JsonPropertyName("success")] public bool Success { get; set; } [JsonPropertyName("properties")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary<string, string>? Properties { get; set; } [JsonPropertyName("ver")] public int Ver { get; set; } = BreezeSchema.DataVersion; } /// <summary> /// Breeze <c>MessageData</c> payload. Emitted both for an /// <see cref="System.Diagnostics.ActivityEvent"/> and for an OpenTelemetry /// <c>LogRecord</c> (in which case <see cref="SeverityLevel"/> is populated). /// </summary> internal sealed class MessageData { [JsonPropertyName("message")] public string? Message { get; set; } /// <summary> /// Application Insights severity ("Verbose"/"Information"/"Warning"/"Error"/"Critical"). /// Populated for log records; left null (and omitted) for activity events. /// </summary> [JsonPropertyName("severityLevel")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? SeverityLevel { get; set; } [JsonPropertyName("properties")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary<string, string>? Properties { get; set; } [JsonPropertyName("ver")] public int Ver { get; set; } = BreezeSchema.DataVersion; } /// <summary> /// Breeze <c>ExceptionData</c> payload. Emitted both for an OpenTelemetry exception event and /// for a <c>LogRecord</c> that carries an exception (in which case <see cref="SeverityLevel"/> /// is populated). /// </summary> internal sealed class ExceptionData { [JsonPropertyName("ver")] public int Ver { get; set; } = BreezeSchema.DataVersion; [JsonPropertyName("exceptions")] public List<ExceptionDetails> Exceptions { get; set; } = []; /// <summary> /// Application Insights severity ("Verbose"/"Information"/"Warning"/"Error"/"Critical"). /// Populated for log records; left null (and omitted) for activity exception events. /// </summary> [JsonPropertyName("severityLevel")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? SeverityLevel { get; set; } /// <summary> /// Application Insights custom properties. Unlike <see cref="MessageData"/>, the Azure Monitor /// exporter always emits <c>ExceptionData.properties</c> — as an empty object when there are no /// properties — so this is written unconditionally (defaulting to an empty dictionary) to stay /// byte-for-byte compatible with the exporter's <c>TelemetryExceptionData</c> serialization. /// </summary> [JsonPropertyName("properties")] public Dictionary<string, string> Properties { get; set; } = []; } /// <summary>A single exception entry inside <see cref="ExceptionData.Exceptions"/>.</summary> internal sealed class ExceptionDetails { [JsonPropertyName("typeName")] public string? TypeName { get; set; } [JsonPropertyName("message")] public string? Message { get; set; } [JsonPropertyName("hasFullStack")] public bool HasFullStack { get; set; } [JsonPropertyName("stack")] public string? Stack { get; set; } } /// <summary> /// The Breeze ingestion response body, returned on HTTP 206 (Partial Content) and used to /// determine which envelopes were rejected with a retriable status. /// </summary> internal sealed class TrackResponse { [JsonPropertyName("itemsReceived")] public int ItemsReceived { get; set; } [JsonPropertyName("itemsAccepted")] public int ItemsAccepted { get; set; } [JsonPropertyName("errors")] public List<TrackResponseError>? Errors { get; set; } } /// <summary>A per-item error entry in a <see cref="TrackResponse"/>.</summary> internal sealed class TrackResponseError { [JsonPropertyName("index")] public int Index { get; set; } [JsonPropertyName("statusCode")] public int StatusCode { get; set; } [JsonPropertyName("message")] public string? Message { get; set; } } /// <summary> /// Source-generated serialization metadata for the Breeze telemetry types. Each closed /// generic envelope is registered with an explicit property name so callers can reference a /// stable, strongly-typed <c>JsonTypeInfo</c> (for example <c>TelemetryJsonContext.Default.RequestEnvelope</c>). /// </summary> [JsonSerializable(typeof(TelemetryEnvelope<RequestData>), TypeInfoPropertyName = "RequestEnvelope")] [JsonSerializable(typeof(TelemetryEnvelope<RemoteDependencyData>), TypeInfoPropertyName = "DependencyEnvelope")] [JsonSerializable(typeof(TelemetryEnvelope<MessageData>), TypeInfoPropertyName = "MessageEnvelope")] [JsonSerializable(typeof(TelemetryEnvelope<ExceptionData>), TypeInfoPropertyName = "ExceptionEnvelope")] [JsonSerializable(typeof(TrackResponse))] internal sealed partial class TelemetryJsonContext : JsonSerializerContext;