File: Telemetry\TelemetryDiskLogger.cs
Web Access
Project: src\src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Microsoft.DotNet.Cli.Telemetry;

internal static class TelemetryDiskLogger
{
    private static readonly JsonSerializerOptions s_jsonOptions;

    private static readonly TelemetryDiskLoggerJsonSerializerContext s_jsonContext;

    public record EventModel(
        string name,
        DateTimeOffset timestamp,
        Dictionary<string, object?> tags);

    public record SourceModel(
        string name,
        string? version,
        Dictionary<string, object?>? tags);

    public record IdentifiersModel(
        string? id,
        string traceId,
        string spanId,
        string parentSpanId,
        string? parentId,
        string? rootId);

    public record ActivityModel(
        string operationName,
        string displayName,
        TimeSpan duration,
        IdentifiersModel identifiers,
        SourceModel source,
        Dictionary<string, string?> tags,
        EventModel[] events);

    static TelemetryDiskLogger()
    {
        s_jsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = false };
        s_jsonContext = new(s_jsonOptions);
    }

    public static void WriteLog(string logPath, IEnumerable<Activity> activies)
    {
        try
        {
            var jsonText = !File.Exists(logPath) ? """{"activities":[]}""" : File.ReadAllText(logPath);
            var root = JsonNode.Parse(jsonText)!;
            var activitiesArray = root["activities"]!.AsArray();
            activitiesArray.AddRange(activies.Select(r => JsonNode.Parse(JsonSerializer.Serialize(CreateActivityJsonModel(r), s_jsonContext.ActivityModel))));
            root["activities"] = activitiesArray;
            File.WriteAllText(logPath, root.ToJsonString(s_jsonOptions));
        }
        catch
        {
            // Swallow any exceptions to avoid interfering with telemetry shutdown.
        }
    }

    private static ActivityModel CreateActivityJsonModel(Activity activity) => new(
        operationName: activity.OperationName,
        displayName: activity.DisplayName,
        duration: activity.Duration,
        identifiers: new(
            id: activity.Id,
            traceId: activity.TraceId.ToString(),
            spanId: activity.SpanId.ToString(),
            parentSpanId: activity.ParentSpanId.ToString(),
            parentId: activity.ParentId,
            rootId: activity.RootId
        ),
        source: new(
            name: activity.Source.Name,
            version: activity.Source.Version,
            tags: activity.Source.Tags?.ToDictionary()
        ),
        tags: activity.Tags.ToDictionary(),
        events: [.. activity.Events.Select(e => new EventModel(
            name: e.Name,
            timestamp: e.Timestamp,
            tags: e.Tags.ToDictionary()
        ))]
    );
}

[JsonSerializable(typeof(TelemetryDiskLogger.ActivityModel))]
internal partial class TelemetryDiskLoggerJsonSerializerContext : JsonSerializerContext;